diff options
1254 files changed, 26338 insertions, 10140 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index 302168d845fa..834398e5c2c2 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -824,6 +824,11 @@ java_aconfig_library { defaults: ["framework-minus-apex-aconfig-java-defaults"], } +cc_aconfig_library { + name: "android.media.tv.flags-aconfig-cc", + aconfig_declarations: "android.media.tv.flags-aconfig", +} + // Permissions aconfig_declarations { name: "android.permission.flags-aconfig", diff --git a/Android.bp b/Android.bp index 9d8c8a69fc18..9d3b64d7335b 100644 --- a/Android.bp +++ b/Android.bp @@ -529,11 +529,7 @@ java_library { ], }, jarjar_prefix: "com.android.internal.hidden_from_bootclasspath", - - jarjar_shards: select(release_flag("RELEASE_USE_SHARDED_JARJAR_ON_FRAMEWORK_MINUS_APEX"), { - true: "10", - default: "1", - }), + jarjar_shards: "10", } java_library { diff --git a/apex/jobscheduler/service/aconfig/alarm.aconfig b/apex/jobscheduler/service/aconfig/alarm.aconfig index a6e980726a9a..9181ef0c532a 100644 --- a/apex/jobscheduler/service/aconfig/alarm.aconfig +++ b/apex/jobscheduler/service/aconfig/alarm.aconfig @@ -7,3 +7,13 @@ flag { description: "Persist list of users with alarms scheduled and wakeup stopped users before alarms are due" bug: "314907186" } + +flag { + name: "acquire_wakelock_before_send" + namespace: "backstage_power" + description: "Acquire the userspace alarm wakelock before sending the alarm" + bug: "391413964" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java index 829442aed6ac..f89b13dce307 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java @@ -5334,6 +5334,18 @@ public class AlarmManagerService extends SystemService { public void deliverLocked(Alarm alarm, long nowELAPSED) { final long workSourceToken = ThreadLocalWorkSource.setUid( getAlarmAttributionUid(alarm)); + + if (Flags.acquireWakelockBeforeSend()) { + // Acquire the wakelock before starting the app. This needs to be done to avoid + // random stalls in the receiving app in case a suspend attempt is already in + // progress. See b/391413964 for an incident where this was found to happen. + if (mBroadcastRefCount == 0) { + setWakelockWorkSource(alarm.workSource, alarm.creatorUid, alarm.statsTag, true); + mWakeLock.acquire(); + mHandler.obtainMessage(AlarmHandler.REPORT_ALARMS_ACTIVE, 1, 0).sendToTarget(); + } + } + try { if (alarm.operation != null) { // PendingIntent alarm @@ -5399,14 +5411,16 @@ public class AlarmManagerService extends SystemService { ThreadLocalWorkSource.restore(workSourceToken); } - // The alarm is now in flight; now arrange wakelock and stats tracking if (DEBUG_WAKELOCK) { Slog.d(TAG, "mBroadcastRefCount -> " + (mBroadcastRefCount + 1)); } - if (mBroadcastRefCount == 0) { - setWakelockWorkSource(alarm.workSource, alarm.creatorUid, alarm.statsTag, true); - mWakeLock.acquire(); - mHandler.obtainMessage(AlarmHandler.REPORT_ALARMS_ACTIVE, 1, 0).sendToTarget(); + if (!Flags.acquireWakelockBeforeSend()) { + // The alarm is now in flight; now arrange wakelock and stats tracking + if (mBroadcastRefCount == 0) { + setWakelockWorkSource(alarm.workSource, alarm.creatorUid, alarm.statsTag, true); + mWakeLock.acquire(); + mHandler.obtainMessage(AlarmHandler.REPORT_ALARMS_ACTIVE, 1, 0).sendToTarget(); + } } final InFlight inflight = new InFlight(AlarmManagerService.this, alarm, nowELAPSED); mInFlight.add(inflight); diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp index 787fdee6ee16..3314b4aa0d71 100644 --- a/api/StubLibraries.bp +++ b/api/StubLibraries.bp @@ -648,7 +648,7 @@ java_api_library { java_api_library { name: "android-non-updatable.stubs.module_lib.from-text", - api_surface: "module_lib", + api_surface: "module-lib", api_contributions: [ "api-stubs-docs-non-updatable.api.contribution", "system-api-stubs-docs-non-updatable.api.contribution", @@ -668,7 +668,7 @@ java_api_library { // generated from this module, as this module is strictly used for hiddenapi only. java_api_library { name: "android-non-updatable.stubs.test_module_lib", - api_surface: "module_lib", + api_surface: "module-lib", api_contributions: [ "api-stubs-docs-non-updatable.api.contribution", "system-api-stubs-docs-non-updatable.api.contribution", @@ -689,7 +689,7 @@ java_api_library { java_api_library { name: "android-non-updatable.stubs.system_server.from-text", - api_surface: "system_server", + api_surface: "system-server", api_contributions: [ "api-stubs-docs-non-updatable.api.contribution", "system-api-stubs-docs-non-updatable.api.contribution", diff --git a/core/api/current.txt b/core/api/current.txt index 26c64bd7adbc..3883a9667355 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -452,7 +452,7 @@ package android { field public static final int activityCloseExitAnimation = 16842939; // 0x10100bb field public static final int activityOpenEnterAnimation = 16842936; // 0x10100b8 field public static final int activityOpenExitAnimation = 16842937; // 0x10100b9 - field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final int adServiceTypes; + field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final int adServiceTypes = 16844452; // 0x10106a4 field public static final int addPrintersActivity = 16843750; // 0x10103e6 field public static final int addStatesFromChildren = 16842992; // 0x10100f0 field public static final int adjustViewBounds = 16843038; // 0x101011e @@ -1017,7 +1017,7 @@ package android { field public static final int insetRight = 16843192; // 0x10101b8 field public static final int insetTop = 16843193; // 0x10101b9 field public static final int installLocation = 16843447; // 0x10102b7 - field @FlaggedApi("android.security.enable_intent_matching_flags") public static final int intentMatchingFlags; + field @FlaggedApi("android.security.enable_intent_matching_flags") public static final int intentMatchingFlags = 16844457; // 0x10106a9 field public static final int interactiveUiTimeout = 16844181; // 0x1010595 field public static final int interpolator = 16843073; // 0x1010141 field public static final int intro = 16844395; // 0x101066b @@ -1072,7 +1072,7 @@ package android { field public static final int label = 16842753; // 0x1010001 field public static final int labelFor = 16843718; // 0x10103c6 field @Deprecated public static final int labelTextSize = 16843317; // 0x1010235 - field @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") public static final int languageSettingsActivity; + field @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") public static final int languageSettingsActivity = 16844453; // 0x10106a5 field public static final int languageTag = 16844040; // 0x1010508 field public static final int largeHeap = 16843610; // 0x101035a field public static final int largeScreens = 16843398; // 0x1010286 @@ -1085,7 +1085,7 @@ package android { field public static final int layout = 16842994; // 0x10100f2 field public static final int layoutAnimation = 16842988; // 0x10100ec field public static final int layoutDirection = 16843698; // 0x10103b2 - field @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") public static final int layoutLabel; + field @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") public static final int layoutLabel = 16844458; // 0x10106aa field public static final int layoutMode = 16843738; // 0x10103da field public static final int layout_above = 16843140; // 0x1010184 field public static final int layout_alignBaseline = 16843142; // 0x1010186 @@ -1207,7 +1207,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 @FlaggedApi("android.sdk.major_minor_versioning_scheme") public static final int minSdkVersionFull = 16844461; // 0x10106ad field public static final int minWidth = 16843071; // 0x101013f field public static final int minimumHorizontalAngle = 16843901; // 0x101047d field public static final int minimumVerticalAngle = 16843902; // 0x101047e @@ -1282,7 +1282,7 @@ package android { field public static final int paddingStart = 16843699; // 0x10103b3 field public static final int paddingTop = 16842967; // 0x10100d7 field public static final int paddingVertical = 16844094; // 0x101053e - field @FlaggedApi("android.content.pm.app_compat_option_16kb") public static final int pageSizeCompat; + field @FlaggedApi("android.content.pm.app_compat_option_16kb") public static final int pageSizeCompat = 16844459; // 0x10106ab field public static final int panelBackground = 16842846; // 0x101005e field public static final int panelColorBackground = 16842849; // 0x1010061 field public static final int panelColorForeground = 16842848; // 0x1010060 @@ -1624,7 +1624,7 @@ package android { field public static final int summaryColumn = 16843426; // 0x10102a2 field public static final int summaryOff = 16843248; // 0x10101f0 field public static final int summaryOn = 16843247; // 0x10101ef - field @FlaggedApi("android.view.accessibility.supplemental_description") public static final int supplementalDescription; + field @FlaggedApi("android.view.accessibility.supplemental_description") public static final int supplementalDescription = 16844456; // 0x10106a8 field public static final int supportedTypes = 16844369; // 0x1010651 field public static final int supportsAssist = 16844016; // 0x10104f0 field public static final int supportsBatteryGameMode = 16844374; // 0x1010656 @@ -1871,7 +1871,7 @@ package android { field public static final int wallpaperIntraOpenExitAnimation = 16843416; // 0x1010298 field public static final int wallpaperOpenEnterAnimation = 16843411; // 0x1010293 field public static final int wallpaperOpenExitAnimation = 16843412; // 0x1010294 - field @FlaggedApi("android.nfc.nfc_associated_role_services") public static final int wantsRoleHolderPriority; + field @FlaggedApi("android.nfc.nfc_associated_role_services") public static final int wantsRoleHolderPriority = 16844460; // 0x10106ac field public static final int webTextViewStyle = 16843449; // 0x10102b9 field public static final int webViewStyle = 16842885; // 0x1010085 field public static final int weekDayTextAppearance = 16843592; // 0x1010348 @@ -33468,7 +33468,7 @@ package android.os { public static class Build.VERSION_CODES { ctor public Build.VERSION_CODES(); - field @FlaggedApi("android.sdk.major_minor_versioning_scheme") public static final int BAKLAVA = 10000; // 0x2710 + field @FlaggedApi("android.sdk.major_minor_versioning_scheme") public static final int BAKLAVA = 36; // 0x24 field public static final int BASE = 1; // 0x1 field public static final int BASE_1_1 = 2; // 0x2 field public static final int CUPCAKE = 3; // 0x3 @@ -33508,7 +33508,7 @@ package android.os { } @FlaggedApi("android.sdk.major_minor_versioning_scheme") public static class Build.VERSION_CODES_FULL { - field public static final int BAKLAVA = 1000000000; // 0x3b9aca00 + field public static final int BAKLAVA = 3600000; // 0x36ee80 field public static final int BASE = 100000; // 0x186a0 field public static final int BASE_1_1 = 200000; // 0x30d40 field public static final int CUPCAKE = 300000; // 0x493e0 @@ -42267,6 +42267,7 @@ package android.service.notification { method public int getRank(); method @NonNull public java.util.List<android.app.Notification.Action> getSmartActions(); method @NonNull public java.util.List<java.lang.CharSequence> getSmartReplies(); + method @FlaggedApi("android.app.nm_summarization") @Nullable public String getSummarization(); method public int getSuppressedVisualEffects(); method public int getUserSentiment(); method public boolean isAmbient(); @@ -45145,7 +45146,7 @@ package android.telephony { field public static final String KEY_SHOW_ICCID_IN_SIM_STATUS_BOOL = "show_iccid_in_sim_status_bool"; field public static final String KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL = "show_ims_registration_status_bool"; field public static final String KEY_SHOW_ONSCREEN_DIAL_BUTTON_BOOL = "show_onscreen_dial_button_bool"; - field @FlaggedApi("com.android.internal.telephony.flags.hide_roaming_icon") public static final String KEY_SHOW_ROAMING_INDICATOR_BOOL = "show_roaming_indicator_bool"; + field public static final String KEY_SHOW_ROAMING_INDICATOR_BOOL = "show_roaming_indicator_bool"; field public static final String KEY_SHOW_SIGNAL_STRENGTH_IN_SIM_STATUS_BOOL = "show_signal_strength_in_sim_status_bool"; field public static final String KEY_SHOW_VIDEO_CALL_CHARGES_ALERT_DIALOG_BOOL = "show_video_call_charges_alert_dialog_bool"; field public static final String KEY_SHOW_WFC_LOCATION_PRIVACY_POLICY_BOOL = "show_wfc_location_privacy_policy_bool"; diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 15ae79e34061..8a5276c1ce08 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -464,7 +464,7 @@ package android { public static final class R.attr { field public static final int allowClearUserDataOnFailedRestore = 16844288; // 0x1010600 - field @FlaggedApi("android.permission.flags.replace_body_sensor_permission_enabled") public static final int backgroundPermission; + field @FlaggedApi("android.permission.flags.replace_body_sensor_permission_enabled") public static final int backgroundPermission = 16844455; // 0x10106a7 field @FlaggedApi("android.content.res.manifest_flagging") public static final int featureFlag = 16844428; // 0x101068c field public static final int gameSessionService = 16844373; // 0x1010655 field public static final int hotwordDetectionService = 16844326; // 0x1010626 @@ -543,7 +543,7 @@ package android { field public static final int config_systemCallStreaming = 17039431; // 0x1040047 field public static final int config_systemCompanionDeviceProvider = 17039417; // 0x1040039 field public static final int config_systemContacts = 17039403; // 0x104002b - field @FlaggedApi("android.content.pm.sdk_dependency_installer") public static final int config_systemDependencyInstaller; + field @FlaggedApi("android.content.pm.sdk_dependency_installer") public static final int config_systemDependencyInstaller = 17039434; // 0x104004a field public static final int config_systemFinancedDeviceController = 17039430; // 0x1040046 field public static final int config_systemGallery = 17039399; // 0x1040027 field public static final int config_systemNotificationIntelligence = 17039413; // 0x1040035 @@ -555,7 +555,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 @FlaggedApi("android.permission.flags.system_vendor_intelligence_role_enabled") public static final int config_systemVendorIntelligence = 17039435; // 0x104004b 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 @@ -2224,7 +2224,7 @@ package android.app.contentsuggestions { package android.app.contextualsearch { - @FlaggedApi("android.app.contextualsearch.flags.enable_service") public final class CallbackToken implements android.os.Parcelable { + public final class CallbackToken implements android.os.Parcelable { ctor public CallbackToken(); method public int describeContents(); method public void getContextualSearchState(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.contextualsearch.ContextualSearchState,java.lang.Throwable>); @@ -2232,7 +2232,7 @@ package android.app.contextualsearch { field @NonNull public static final android.os.Parcelable.Creator<android.app.contextualsearch.CallbackToken> CREATOR; } - @FlaggedApi("android.app.contextualsearch.flags.enable_service") public final class ContextualSearchManager { + public final class ContextualSearchManager { method @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXTUAL_SEARCH) public void startContextualSearch(int); field public static final String ACTION_LAUNCH_CONTEXTUAL_SEARCH = "android.app.contextualsearch.action.LAUNCH_CONTEXTUAL_SEARCH"; field public static final int ENTRYPOINT_LONG_PRESS_HOME = 2; // 0x2 @@ -2250,7 +2250,7 @@ package android.app.contextualsearch { field public static final String EXTRA_VISIBLE_PACKAGE_NAMES = "android.app.contextualsearch.extra.VISIBLE_PACKAGE_NAMES"; } - @FlaggedApi("android.app.contextualsearch.flags.enable_service") public final class ContextualSearchState implements android.os.Parcelable { + public final class ContextualSearchState implements android.os.Parcelable { ctor public ContextualSearchState(@Nullable android.app.assist.AssistStructure, @Nullable android.app.assist.AssistContent, @NonNull android.os.Bundle); method public int describeContents(); method @Nullable public android.app.assist.AssistContent getContent(); @@ -3774,7 +3774,7 @@ package android.content { field public static final String CLOUDSEARCH_SERVICE = "cloudsearch"; field public static final String CONTENT_SUGGESTIONS_SERVICE = "content_suggestions"; field public static final String CONTEXTHUB_SERVICE = "contexthub"; - field @FlaggedApi("android.app.contextualsearch.flags.enable_service") public static final String CONTEXTUAL_SEARCH_SERVICE = "contextual_search"; + field public static final String CONTEXTUAL_SEARCH_SERVICE = "contextual_search"; field @FlaggedApi("android.permission.flags.enhanced_confirmation_mode_apis_enabled") public static final String ECM_ENHANCED_CONFIRMATION_SERVICE = "ecm_enhanced_confirmation"; field public static final String ETHERNET_SERVICE = "ethernet"; field public static final String EUICC_CARD_SERVICE = "euicc_card"; @@ -13446,6 +13446,7 @@ package android.service.notification { field public static final String KEY_RANKING_SCORE = "key_ranking_score"; field public static final String KEY_SENSITIVE_CONTENT = "key_sensitive_content"; field public static final String KEY_SNOOZE_CRITERIA = "key_snooze_criteria"; + field @FlaggedApi("android.app.nm_summarization") public static final String KEY_SUMMARIZATION = "key_summarization"; field public static final String KEY_TEXT_REPLIES = "key_text_replies"; field @FlaggedApi("android.service.notification.notification_classification") public static final String KEY_TYPE = "key_type"; field public static final String KEY_USER_SENTIMENT = "key_user_sentiment"; @@ -15450,6 +15451,8 @@ package android.telephony { field public static final int CONDITIONAL_IE_ERROR = 100; // 0x64 field public static final int DESTINATION_OUT_OF_ORDER = 27; // 0x1b field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final int EMERGENCY_PERM_FAILURE = 326; // 0x146 + field @FlaggedApi("com.android.internal.telephony.flags.add_ims_redial_codes_for_emergency_calls") public static final int EMERGENCY_REDIAL_ON_IMS = 3001; // 0xbb9 + field @FlaggedApi("com.android.internal.telephony.flags.add_ims_redial_codes_for_emergency_calls") public static final int EMERGENCY_REDIAL_ON_VOWIFI = 3002; // 0xbba field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final int EMERGENCY_TEMP_FAILURE = 325; // 0x145 field public static final int ERROR_UNSPECIFIED = 65535; // 0xffff field public static final int FACILITY_REJECTED = 29; // 0x1d diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 0126db70296c..e2fe5062d356 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -809,7 +809,7 @@ package android.app.contentsuggestions { package android.app.contextualsearch { - @FlaggedApi("android.app.contextualsearch.flags.enable_service") public final class CallbackToken implements android.os.Parcelable { + public final class CallbackToken implements android.os.Parcelable { method @NonNull public android.os.IBinder getToken(); } @@ -1634,6 +1634,10 @@ package android.hardware.camera2.params { method public void setColorSpace(@NonNull android.graphics.ColorSpace.Named); } + @FlaggedApi("com.android.internal.camera.flags.camera_multi_client") public final class SharedSessionConfiguration { + ctor public SharedSessionConfiguration(int, @NonNull long[]); + } + } package android.hardware.devicestate { diff --git a/core/java/android/app/ActivityTaskManager.java b/core/java/android/app/ActivityTaskManager.java index 16dcf2ad7e45..8d20e46c7df8 100644 --- a/core/java/android/app/ActivityTaskManager.java +++ b/core/java/android/app/ActivityTaskManager.java @@ -25,6 +25,7 @@ import android.annotation.SystemService; import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.Configuration; import android.content.res.Resources; @@ -483,6 +484,19 @@ public class ActivityTaskManager { } /** + * @return Whether the app could be universal resizeable (assuming it's on a large screen and + * ignoring possible overrides) + * @hide + */ + public boolean canBeUniversalResizeable(@NonNull ApplicationInfo appInfo) { + try { + return getService().canBeUniversalResizeable(appInfo); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Detaches the navigation bar from the app it was attached to during a transition. * @hide */ diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 2cfba4b8468f..7b9ec4a7821e 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -7061,7 +7061,7 @@ public final class ActivityThread extends ClientTransactionHandler Slog.w(TAG, "Low overhead tracing feature is not enabled"); break; } - VMDebug.startLowOverheadTrace(); + VMDebug.startLowOverheadTraceForAllMethods(); break; default: try { diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl index c6f62a21641d..4b1afa517122 100644 --- a/core/java/android/app/IActivityTaskManager.aidl +++ b/core/java/android/app/IActivityTaskManager.aidl @@ -158,6 +158,12 @@ interface IActivityTaskManager { void reportAssistContextExtras(in IBinder assistToken, in Bundle extras, in AssistStructure structure, in AssistContent content, in Uri referrer); + /** + * @return whether the app could be universal resizeable (assuming it's on a large screen and + * ignoring possible overrides) + */ + boolean canBeUniversalResizeable(in ApplicationInfo appInfo); + void setFocusedRootTask(int taskId); ActivityTaskManager.RootTaskInfo getFocusedRootTaskInfo(); Rect getTaskBounds(int taskId); diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index fbe5b9449b00..35308ee43dea 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -4393,6 +4393,9 @@ public class Notification implements Parcelable */ @Nullable public Pair<RemoteInput, Action> findRemoteInputActionPair(boolean requiresFreeform) { + if (isPromotedOngoing()) { + return null; + } if (actions == null) { return null; } @@ -6454,6 +6457,11 @@ public class Notification implements Parcelable if (mActions == null) return Collections.emptyList(); List<Notification.Action> standardActions = new ArrayList<>(); for (Notification.Action action : mActions) { + // Actions with RemoteInput are ignored for RONs. + if (mN.isPromotedOngoing() + && hasValidRemoteInput(action)) { + continue; + } if (!action.isContextual()) { standardActions.add(action); } @@ -11932,7 +11940,7 @@ public class Notification implements Parcelable } /** - * Finds steps and points fill color with sufficient contrast over bg (1.3:1) that + * Finds steps and points fill color with sufficient contrast over bg (3:1) that * has the same hue as the original color, but is lightened or darkened depending on * whether the background is dark or light. * @@ -11945,7 +11953,7 @@ public class Notification implements Parcelable return Builder.ensureColorContrast( Color.alpha(color) == 0 ? defaultColor : color, bg, - 1.3); + 3); } /** diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java index b7285c38290c..039f7b6a86ba 100644 --- a/core/java/android/app/StatusBarManager.java +++ b/core/java/android/app/StatusBarManager.java @@ -60,6 +60,7 @@ import com.android.internal.statusbar.NotificationVisibility; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -195,12 +196,40 @@ public class StatusBarManager { */ private static final int DEFAULT_SIM_LOCKED_DISABLED_FLAGS = DISABLE_EXPAND; - /** @hide */ - public static final int NAVIGATION_HINT_BACK_ALT = 1 << 0; - /** @hide */ - public static final int NAVIGATION_HINT_IME_SHOWN = 1 << 1; - /** @hide */ - public static final int NAVIGATION_HINT_IME_SWITCHER_SHOWN = 1 << 2; + /** + * The back button is visually adjusted to indicate that it will dismiss the IME when pressed. + * This only takes effect while the IME is visible. By default, it is set while the IME is + * visible, but may be overridden by the + * {@link android.inputmethodservice.InputMethodService.BackDispositionMode backDispositionMode} + * set by the IME. + * + * @hide + */ + public static final int NAVIGATION_HINT_BACK_ALT = 1 << 0; + /** + * The IME is visible. + * + * @hide + */ + public static final int NAVIGATION_HINT_IME_VISIBLE = 1 << 1; + /** + * The IME Switcher button is visible. This only takes effect while the IME is visible. + * + * @hide + */ + public static final int NAVIGATION_HINT_IME_SWITCHER_BUTTON_VISIBLE = 1 << 2; + /** + * Navigation bar flags related to the IME state. + * + * @hide + */ + @IntDef(flag = true, prefix = { "NAVIGATION_HINT_" }, value = { + NAVIGATION_HINT_BACK_ALT, + NAVIGATION_HINT_IME_VISIBLE, + NAVIGATION_HINT_IME_SWITCHER_BUTTON_VISIBLE, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface NavigationHint {} /** @hide */ public static final int WINDOW_STATUS_BAR = 1; @@ -1325,6 +1354,22 @@ public class StatusBarManager { } /** @hide */ + @NonNull + public static String navigationHintsToString(@NavigationHint int hints) { + final var hintStrings = new ArrayList<String>(); + if ((hints & NAVIGATION_HINT_BACK_ALT) != 0) { + hintStrings.add("NAVIGATION_HINT_BACK_ALT"); + } + if ((hints & NAVIGATION_HINT_IME_VISIBLE) != 0) { + hintStrings.add("NAVIGATION_HINT_IME_VISIBLE"); + } + if ((hints & NAVIGATION_HINT_IME_SWITCHER_BUTTON_VISIBLE) != 0) { + hintStrings.add("NAVIGATION_HINT_IME_SWITCHER_BUTTON_VISIBLE"); + } + return String.join(" | ", hintStrings); + } + + /** @hide */ public static String windowStateToString(int state) { if (state == WINDOW_STATE_HIDING) return "WINDOW_STATE_HIDING"; if (state == WINDOW_STATE_HIDDEN) return "WINDOW_STATE_HIDDEN"; diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java index 6f8e335cff80..063055eb4917 100644 --- a/core/java/android/app/UiAutomation.java +++ b/core/java/android/app/UiAutomation.java @@ -222,7 +222,8 @@ public final class UiAutomation { private OnAccessibilityEventListener mOnAccessibilityEventListener; - private boolean mWaitingForEventDelivery; + // Count the nested clients waiting for data delivery + private int mCurrentEventWatchersCount = 0; private long mLastEventTimeMillis; @@ -1132,75 +1133,75 @@ public final class UiAutomation { */ public AccessibilityEvent executeAndWaitForEvent(Runnable command, AccessibilityEventFilter filter, long timeoutMillis) throws TimeoutException { + int watchersDepth; + // Track events added after the index for this command, it is to support nested calls. + // This doesn't support concurrent calls correctly. + int eventQueueStartIndex; + final long executionStartTimeMillis; + // Acquire the lock and prepare for receiving events. synchronized (mLock) { throwIfNotConnectedLocked(); - mEventQueue.clear(); - // Prepare to wait for an event. - mWaitingForEventDelivery = true; + watchersDepth = ++mCurrentEventWatchersCount; + executionStartTimeMillis = SystemClock.uptimeMillis(); + eventQueueStartIndex = mEventQueue.size(); + } + if (VERBOSE) { + Log.v(LOG_TAG, "executeAndWaitForEvent starts at depth=" + watchersDepth + ", " + + "command=" + command + ", filter=" + filter + ", timeout=" + timeoutMillis); } - // Note: We have to release the lock since calling out with this lock held - // can bite. We will correctly filter out events from other interactions, - // so starting to collect events before running the action is just fine. - - // We will ignore events from previous interactions. - final long executionStartTimeMillis = SystemClock.uptimeMillis(); - // Execute the command *without* the lock being held. - command.run(); - - List<AccessibilityEvent> receivedEvents = new ArrayList<>(); - - // Acquire the lock and wait for the event. try { - // Wait for the event. - final long startTimeMillis = SystemClock.uptimeMillis(); - while (true) { - List<AccessibilityEvent> localEvents = new ArrayList<>(); - synchronized (mLock) { - localEvents.addAll(mEventQueue); - mEventQueue.clear(); - } - // Drain the event queue - while (!localEvents.isEmpty()) { - AccessibilityEvent event = localEvents.remove(0); - // Ignore events from previous interactions. - if (event.getEventTime() < executionStartTimeMillis) { - continue; - } - if (filter.accept(event)) { - return event; - } - receivedEvents.add(event); - } - // Check if timed out and if not wait. - final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; - final long remainingTimeMillis = timeoutMillis - elapsedTimeMillis; - if (remainingTimeMillis <= 0) { - throw new TimeoutException("Expected event not received within: " - + timeoutMillis + " ms among: " + receivedEvents); + // Execute the command *without* the lock being held. + command.run(); + synchronized (mLock) { + if (watchersDepth != mCurrentEventWatchersCount) { + throw new IllegalStateException("Unexpected event watchers count, expected: " + + watchersDepth + ", actual: " + mCurrentEventWatchersCount); } + } + final long startTimeMillis = SystemClock.uptimeMillis(); + List<AccessibilityEvent> receivedEvents = new ArrayList<>(); + long elapsedTimeMillis = 0; + int currentQueueSize = 0; + while (timeoutMillis > elapsedTimeMillis) { + AccessibilityEvent event = null; synchronized (mLock) { - if (mEventQueue.isEmpty()) { + currentQueueSize = mEventQueue.size(); + if (eventQueueStartIndex < currentQueueSize) { + event = mEventQueue.get(eventQueueStartIndex++); + } else { try { - mLock.wait(remainingTimeMillis); + mLock.wait(timeoutMillis - elapsedTimeMillis); } catch (InterruptedException ie) { /* ignore */ } } } + elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; + if (event == null || event.getEventTime() < executionStartTimeMillis) { + continue; + } + if (filter.accept(event)) { + return event; + } + receivedEvents.add(event); } - } finally { - int size = receivedEvents.size(); - for (int i = 0; i < size; i++) { - receivedEvents.get(i).recycle(); + if (eventQueueStartIndex < currentQueueSize) { + Log.w(LOG_TAG, "Timed out before reading all events from the queue"); } - + throw new TimeoutException("Expected event not received before timeout, events: " + + receivedEvents); + } finally { synchronized (mLock) { - mWaitingForEventDelivery = false; - mEventQueue.clear(); + if (--mCurrentEventWatchersCount == 0) { + mEventQueue.clear(); + } mLock.notifyAll(); } + if (VERBOSE) { + Log.v(LOG_TAG, "executeAndWaitForEvent ends at depth=" + watchersDepth); + } } } @@ -1957,7 +1958,7 @@ public final class UiAutomation { // It is not guaranteed that the accessibility framework sends events by the // order of event timestamp. mLastEventTimeMillis = Math.max(mLastEventTimeMillis, event.getEventTime()); - if (mWaitingForEventDelivery) { + if (mCurrentEventWatchersCount > 0) { mEventQueue.add(AccessibilityEvent.obtain(event)); } mLock.notifyAll(); diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index c50452157d74..08719fc549f8 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -4051,10 +4051,13 @@ public class DevicePolicyManager { public static final int EXEMPT_FROM_HIBERNATION = 3; /** - * Exempt an app from all power-related restrictions, including app standby and doze. + * Exempt an app from all power-related restrictions, including app standby. * In addition, the app will be able to start foreground services from the background, * and the user will not be able to stop foreground services run by the app. * + * <p><strong>Note:</strong> This option does NOT exempt apps from Doze mode. In fact, + * DPC apps themselves are not automatically exempted from Doze mode either. + * * @hide */ @SystemApi diff --git a/core/java/android/app/appfunctions/AppFunctionManager.java b/core/java/android/app/appfunctions/AppFunctionManager.java index a731e5085466..6fd8db995368 100644 --- a/core/java/android/app/appfunctions/AppFunctionManager.java +++ b/core/java/android/app/appfunctions/AppFunctionManager.java @@ -42,12 +42,13 @@ import java.util.Objects; import java.util.concurrent.Executor; /** - * Provides access to app functions. + * Provides access to App Functions. App Functions is currently a + * beta/experimental preview feature. * * <p>An app function is a piece of functionality that apps expose to the system for cross-app * orchestration. * - * <p>**Building App Functions:** + * <h3>Building App Functions</h3> * * <p>Most developers should build app functions through the AppFunctions SDK. This SDK library * offers a more convenient and type-safe way to build app functions. The SDK provides predefined @@ -56,7 +57,7 @@ import java.util.concurrent.Executor; * these data classes into {@link ExecuteAppFunctionRequest#getParameters()} and {@link * ExecuteAppFunctionResponse#getResultDocument()}. * - * <p>**Discovering App Functions:** + * <h3>Discovering App Functions</h3> * * <p>When there is a package change or the device starts up, the metadata of available functions is * indexed on-device by {@link AppSearchManager}. AppSearch stores the indexed information as an @@ -66,7 +67,7 @@ import java.util.concurrent.Executor; * document is based on the packages that have visibility to the app providing the app functions. * AppFunction SDK provides a convenient way to achieve this and is the preferred method. * - * <p>**Executing App Functions:** + * <h3>Executing App Functions</h3> * * <p>To execute an app function, the caller app can retrieve the {@code functionIdentifier} from * the {@code AppFunctionStaticMetadata} document and use it to build an {@link @@ -76,7 +77,7 @@ import java.util.concurrent.Executor; * apps. An app can always execute its own app functions and doesn't need these permissions. * AppFunction SDK provides a convenient way to achieve this and is the preferred method. * - * <p>**Example:** + * <h3>Example</h3> * * <p>An assistant app is trying to fulfill the user request "Save XYZ into my note". The assistant * app should first list all available app functions as {@code AppFunctionStaticMetadata} documents diff --git a/core/java/android/app/appfunctions/OWNERS b/core/java/android/app/appfunctions/OWNERS index 6a69e15d00dd..822242de1a81 100644 --- a/core/java/android/app/appfunctions/OWNERS +++ b/core/java/android/app/appfunctions/OWNERS @@ -5,3 +5,4 @@ tonymak@google.com mingweiliao@google.com anothermark@google.com utkarshnigam@google.com +yaraabdullatif@google.com diff --git a/core/java/android/app/contextualsearch/CallbackToken.java b/core/java/android/app/contextualsearch/CallbackToken.java index 94cdc73fcd1d..c0ea1cd3a9de 100644 --- a/core/java/android/app/contextualsearch/CallbackToken.java +++ b/core/java/android/app/contextualsearch/CallbackToken.java @@ -44,7 +44,6 @@ import java.util.concurrent.Executor; * * @hide */ -@FlaggedApi(Flags.FLAG_ENABLE_SERVICE) @SystemApi public final class CallbackToken implements Parcelable { private static final boolean DEBUG = true; diff --git a/core/java/android/app/contextualsearch/ContextualSearchManager.java b/core/java/android/app/contextualsearch/ContextualSearchManager.java index ad43f271a910..2ce431dcb32d 100644 --- a/core/java/android/app/contextualsearch/ContextualSearchManager.java +++ b/core/java/android/app/contextualsearch/ContextualSearchManager.java @@ -43,7 +43,6 @@ import java.lang.annotation.RetentionPolicy; * @hide */ @SystemApi -@FlaggedApi(Flags.FLAG_ENABLE_SERVICE) public final class ContextualSearchManager { /** diff --git a/core/java/android/app/contextualsearch/ContextualSearchState.java b/core/java/android/app/contextualsearch/ContextualSearchState.java index f01ae55d05b6..e1731bf525bf 100644 --- a/core/java/android/app/contextualsearch/ContextualSearchState.java +++ b/core/java/android/app/contextualsearch/ContextualSearchState.java @@ -38,7 +38,6 @@ import androidx.annotation.NonNull; * * @hide */ -@FlaggedApi(Flags.FLAG_ENABLE_SERVICE) @SystemApi public final class ContextualSearchState implements Parcelable { private final @NonNull Bundle mExtras; diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig index 914ca73f1ce4..a4a6a55fc9ab 100644 --- a/core/java/android/app/notification.aconfig +++ b/core/java/android/app/notification.aconfig @@ -310,3 +310,17 @@ flag { description: "removes sbnholder from NLS" bug: "362981561" } + +flag { + name: "nm_summarization" + namespace: "systemui" + description: "Allows the NAS to summarize notifications" + bug: "390417189" +} + +flag { + name: "nm_summarization_ui" + namespace: "systemui" + description: "Shows summarized notifications in the UI" + bug: "390217880" +} diff --git a/core/java/android/app/supervision/flags.aconfig b/core/java/android/app/supervision/flags.aconfig index 18182b804627..232883cbfe00 100644 --- a/core/java/android/app/supervision/flags.aconfig +++ b/core/java/android/app/supervision/flags.aconfig @@ -48,3 +48,19 @@ flag { description: "Flag that enables the Supervision settings screen with top-level Android settings entry point" bug: "383404606" } + +flag { + name: "enable_app_approval" + is_exported: true + namespace: "supervision" + description: "Flag to enable the App Approval settings in Android settings UI" + bug: "390185393" +} + +flag { + name: "enable_supervision_pin_recovery_screen" + is_exported: true + namespace: "supervision" + description: "Flag that enables the Supervision pin recovery screen with Supervision settings entry point" + bug: "390500290" +} diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java index df1028e9e04c..b9b5c6a8bbc3 100644 --- a/core/java/android/appwidget/AppWidgetHostView.java +++ b/core/java/android/appwidget/AppWidgetHostView.java @@ -20,7 +20,6 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Activity; import android.app.ActivityOptions; -import android.app.LoadedApk; import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.Context; @@ -753,9 +752,6 @@ public class AppWidgetHostView extends FrameLayout implements AppWidgetHost.AppW */ protected Context getRemoteContextEnsuringCorrectCachedApkPath() { try { - ApplicationInfo expectedAppInfo = mInfo.providerInfo.applicationInfo; - LoadedApk.checkAndUpdateApkPaths(expectedAppInfo); - // Return if cloned successfully, otherwise default Context newContext = mContext.createApplicationContext( mInfo.providerInfo.applicationInfo, Context.CONTEXT_RESTRICTED); diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index a126363237b8..efcaa0ea6f07 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -2722,10 +2722,10 @@ public abstract class ContentResolver implements ContentInterface { /** @hide - designated user version */ @UnsupportedAppUsage - public final void registerContentObserver(Uri uri, boolean notifyForDescendents, + public final void registerContentObserver(Uri uri, boolean notifyForDescendants, ContentObserver observer, @UserIdInt int userHandle) { try { - getContentService().registerContentObserver(uri, notifyForDescendents, + getContentService().registerContentObserver(uri, notifyForDescendants, observer.getContentObserver(), userHandle, mTargetSdkVersion); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 7abf5600d659..8d54673df74c 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -5593,7 +5593,6 @@ public abstract class Context { * @hide * @see #getSystemService(String) */ - @FlaggedApi(android.app.contextualsearch.flags.Flags.FLAG_ENABLE_SERVICE) @SystemApi public static final String CONTEXTUAL_SEARCH_SERVICE = "contextual_search"; diff --git a/core/java/android/content/pm/RegisteredServicesCache.java b/core/java/android/content/pm/RegisteredServicesCache.java index 82663849f316..74da62c85ed2 100644 --- a/core/java/android/content/pm/RegisteredServicesCache.java +++ b/core/java/android/content/pm/RegisteredServicesCache.java @@ -527,13 +527,14 @@ public abstract class RegisteredServicesCache<V> { lastUpdateTime = packageInfo.lastUpdateTime; } catch (NameNotFoundException | SecurityException e) { Slog.d(TAG, "Fail to get the PackageInfo in generateServicesMap: " + e); - continue; } - ServiceInfo<V> serviceInfo = getServiceInfoFromServiceCache(componentName, - lastUpdateTime); - if (serviceInfo != null) { - serviceInfos.add(serviceInfo); - continue; + if (lastUpdateTime >= 0) { + ServiceInfo<V> serviceInfo = getServiceInfoFromServiceCache(componentName, + lastUpdateTime); + if (serviceInfo != null) { + serviceInfos.add(serviceInfo); + continue; + } } } try { diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java index 8a3a3ad56a7b..582a1a9442ce 100644 --- a/core/java/android/content/pm/UserInfo.java +++ b/core/java/android/content/pm/UserInfo.java @@ -183,6 +183,12 @@ public class UserInfo implements Parcelable { * * <p>This is not necessarily the system user. For example, it will not be the system user on * devices for which {@link UserManager#isHeadlessSystemUserMode()} returns true. + * + * <p>NB: Features should ideally not limit functionality to the main user. Ideally, they + * should either work for all users or for all admin users. If a feature should only work for + * select users, its determination of which user should be done intelligently or be + * customizable. Not all devices support a main user, and the idea of singling out one user as + * special is contrary to overall multiuser goals. */ public static final int FLAG_MAIN = 0x00004000; diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig index 4a579a4c0e85..4e6fb8d3a8e7 100644 --- a/core/java/android/content/pm/multiuser.aconfig +++ b/core/java/android/content/pm/multiuser.aconfig @@ -500,6 +500,16 @@ flag { } flag { + name: "get_user_switchability_permission" + namespace: "multiuser" + description: "Update permissions for getUserSwitchability" + bug: "390458180" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "restrict_quiet_mode_credential_bug_fix_to_managed_profiles" namespace: "profile_experiences" description: "Use user states to check the state of quiet mode for managed profiles only" diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java index 53813012b4b3..71d0a04760ac 100644 --- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java +++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java @@ -541,9 +541,9 @@ public class ApkLiteParseUtils { case TAG_USES_SDK_LIBRARY: String usesSdkLibName = parser.getAttributeValue( ANDROID_RES_NAMESPACE, "name"); - // TODO(b/379219371): Due to a bug in bundletool, some apps can use - // versionMajor as string. Until it is resolved, we are adding a - // workaround here. + // TODO(b/391604666): Due to a bug in bundletool, old apps could be + // using versionMajor as string. Do not remove this workaround until + // b/391604666 is resolved. String usesSdkLibVersionMajorString = parser.getAttributeValue( ANDROID_RES_NAMESPACE, "versionMajor"); long usesSdkLibVersionMajor = XmlUtils.convertValueToInt( diff --git a/core/java/android/credentials/flags.aconfig b/core/java/android/credentials/flags.aconfig index 430ed2b68342..449423f1ea1f 100644 --- a/core/java/android/credentials/flags.aconfig +++ b/core/java/android/credentials/flags.aconfig @@ -133,6 +133,7 @@ flag { metadata { purpose: PURPOSE_BUGFIX } + is_exported: true } flag { diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index 5533a640b9d8..210653bb41e5 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -5256,9 +5256,6 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <p>DYNAMIC_RANGE_PROFILE: {STANDARD, HLG10}</p> * </li> * </ul> - * <p>All of the above configurations can be set up with a SessionConfiguration. The list of - * OutputConfiguration contains the stream configurations and DYNAMIC_RANGE_PROFILE, and - * the AE_TARGET_FPS_RANGE and VIDEO_STABILIZATION_MODE are set as session parameters.</p> * <p>When set to BAKLAVA, the additional stream combinations below are verified * by the compliance tests:</p> * <table> @@ -5268,6 +5265,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <th style="text-align: center;">Size</th> * <th style="text-align: center;">Target 2</th> * <th style="text-align: center;">Size</th> + * <th style="text-align: center;">Target 3</th> + * <th style="text-align: center;">Size</th> * </tr> * </thead> * <tbody> @@ -5276,15 +5275,34 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <td style="text-align: center;">S1080P</td> * <td style="text-align: center;">PRIV</td> * <td style="text-align: center;">S1080P</td> + * <td style="text-align: center;"></td> + * <td style="text-align: center;"></td> * </tr> * <tr> * <td style="text-align: center;">PRIV</td> * <td style="text-align: center;">S1080P</td> * <td style="text-align: center;">PRIV</td> * <td style="text-align: center;">S1440P</td> + * <td style="text-align: center;"></td> + * <td style="text-align: center;"></td> + * </tr> + * <tr> + * <td style="text-align: center;">PRIV</td> + * <td style="text-align: center;">S1080P</td> + * <td style="text-align: center;">YUV</td> + * <td style="text-align: center;">S1080P</td> + * <td style="text-align: center;">S1080P</td> + * <td style="text-align: center;">PRIV</td> * </tr> * </tbody> * </table> + * <ul> + * <li>VIDEO_STABILIZATION_MODE: {OFF, ON} for the newly added stream combinations given the + * presence of dedicated video stream</li> + * </ul> + * <p>All of the above configurations can be set up with a SessionConfiguration. The list of + * OutputConfiguration contains the stream configurations and DYNAMIC_RANGE_PROFILE, and + * the AE_TARGET_FPS_RANGE and VIDEO_STABILIZATION_MODE are set as session parameters.</p> * <p>This key is available on all devices.</p> */ @PublicKey diff --git a/core/java/android/hardware/camera2/params/SharedSessionConfiguration.java b/core/java/android/hardware/camera2/params/SharedSessionConfiguration.java index 365f870ba22d..b40c7d35c06b 100644 --- a/core/java/android/hardware/camera2/params/SharedSessionConfiguration.java +++ b/core/java/android/hardware/camera2/params/SharedSessionConfiguration.java @@ -21,6 +21,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; import android.annotation.SystemApi; +import android.annotation.TestApi; import android.graphics.ColorSpace; import android.graphics.ImageFormat.Format; import android.hardware.DataSpace.NamedDataSpace; @@ -228,6 +229,7 @@ public final class SharedSessionConfiguration { * * @hide */ + @TestApi public SharedSessionConfiguration(int sharedColorSpace, @NonNull long[] sharedOutputConfigurations) { mColorSpace = sharedColorSpace; diff --git a/core/java/android/hardware/display/AmbientDisplayConfiguration.java b/core/java/android/hardware/display/AmbientDisplayConfiguration.java index 7361d4f6b882..063f5545dd71 100644 --- a/core/java/android/hardware/display/AmbientDisplayConfiguration.java +++ b/core/java/android/hardware/display/AmbientDisplayConfiguration.java @@ -44,7 +44,7 @@ public class AmbientDisplayConfiguration { private final Context mContext; private final boolean mAlwaysOnByDefault; private final boolean mPickupGestureEnabledByDefault; - private final boolean mScreenOffUdfpsEnabledByDefault; + private final boolean mScreenOffUdfpsAvailable; /** Copied from android.provider.Settings.Secure since these keys are hidden. */ private static final String[] DOZE_SETTINGS = { @@ -72,7 +72,7 @@ public class AmbientDisplayConfiguration { mAlwaysOnByDefault = mContext.getResources().getBoolean(R.bool.config_dozeAlwaysOnEnabled); mPickupGestureEnabledByDefault = mContext.getResources().getBoolean(R.bool.config_dozePickupGestureEnabled); - mScreenOffUdfpsEnabledByDefault = + mScreenOffUdfpsAvailable = mContext.getResources().getBoolean(R.bool.config_screen_off_udfps_enabled); } @@ -152,7 +152,8 @@ public class AmbientDisplayConfiguration { /** @hide */ public boolean screenOffUdfpsEnabled(int user) { return !TextUtils.isEmpty(udfpsLongPressSensorType()) - && ((mScreenOffUdfpsEnabledByDefault && Flags.screenOffUnlockUdfps()) + && ((mScreenOffUdfpsAvailable && Flags.screenOffUnlockUdfps()) + && mContext.getResources().getBoolean(R.bool.config_screen_off_udfps_default_on) ? boolSettingDefaultOn(SCREEN_OFF_UNLOCK_UDFPS_ENABLED, user) : boolSettingDefaultOff(SCREEN_OFF_UNLOCK_UDFPS_ENABLED, user)); } diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java index d273ddb15cc4..343e4b5668c0 100644 --- a/core/java/android/hardware/display/DisplayManagerInternal.java +++ b/core/java/android/hardware/display/DisplayManagerInternal.java @@ -475,6 +475,12 @@ public abstract class DisplayManagerInternal { */ public abstract boolean isDisplayReadyForMirroring(int displayId); + /** + * Called by {@link com.android.server.display.DisplayBackupHelper} when backup files were + * restored and are ready to be reloaded. + */ + public abstract void reloadTopologies(int userId); + /** * Used by the window manager to override the per-display screen brightness based on the diff --git a/core/java/android/hardware/display/DisplayTopology.java b/core/java/android/hardware/display/DisplayTopology.java index 1d2f133ee759..b39bd8c10022 100644 --- a/core/java/android/hardware/display/DisplayTopology.java +++ b/core/java/android/hardware/display/DisplayTopology.java @@ -108,7 +108,6 @@ public final class DisplayTopology implements Parcelable { public DisplayTopology() {} - @VisibleForTesting public DisplayTopology(TreeNode root, int primaryDisplayId) { mRoot = root; if (mRoot != null) { @@ -275,7 +274,7 @@ public final class DisplayTopology implements Parcelable { float offset; int pos; - if (Math.abs(xOverlap) > Math.abs(yOverlap)) { + if (xOverlap > yOverlap) { // Deviation in each dimension is a penalty in the potential parenting. To // get the X deviation, overlap is subtracted from the lesser width so that // a maximum overlap results in a deviation of zero. @@ -541,6 +540,19 @@ public final class DisplayTopology implements Parcelable { } @Override + public boolean equals(Object obj) { + if (!(obj instanceof DisplayTopology)) { + return false; + } + return obj.toString().equals(toString()); + } + + @Override + public int hashCode() { + return toString().hashCode(); + } + + @Override public String toString() { StringWriter out = new StringWriter(); PrintWriter writer = new PrintWriter(out); @@ -610,7 +622,7 @@ public final class DisplayTopology implements Parcelable { } @Nullable - private static TreeNode findDisplay(int displayId, @Nullable TreeNode startingNode) { + public static TreeNode findDisplay(int displayId, @Nullable TreeNode startingNode) { if (startingNode == null) { return null; } @@ -775,16 +787,22 @@ public final class DisplayTopology implements Parcelable { */ private float mOffset; - private final List<TreeNode> mChildren = new ArrayList<>(); + private final List<TreeNode> mChildren; @VisibleForTesting public TreeNode(int displayId, float width, float height, @Position int position, float offset) { + this(displayId, width, height, position, offset, List.of()); + } + + public TreeNode(int displayId, float width, float height, int position, + float offset, List<TreeNode> children) { mDisplayId = displayId; mWidth = width; mHeight = height; mPosition = position; mOffset = offset; + mChildren = new ArrayList<>(children); } public TreeNode(Parcel source) { diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig index 62126963cba4..79323bf2f2f7 100644 --- a/core/java/android/hardware/input/input_framework.aconfig +++ b/core/java/android/hardware/input/input_framework.aconfig @@ -225,3 +225,13 @@ flag { description: "Removes modifiers from the original key event that activated the fallback, ensuring that only the intended fallback event is sent." bug: "382545048" } + +flag { + name: "abort_slow_multi_press" + namespace: "wear_frameworks" + description: "If a press that's a part of a multipress takes too long, the multipress gesture will be cancelled." + bug: "370095426" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/android/hardware/usb/OWNERS b/core/java/android/hardware/usb/OWNERS index 37604bc2eb65..1de8a242acfc 100644 --- a/core/java/android/hardware/usb/OWNERS +++ b/core/java/android/hardware/usb/OWNERS @@ -1,7 +1,7 @@ # Bug component: 175220 -anothermark@google.com +vmartensson@google.com +nkapron@google.com febinthattil@google.com -aprasath@google.com +shubhankarm@google.com badhri@google.com -kumarashishg@google.com
\ No newline at end of file diff --git a/core/java/android/inputmethodservice/NavigationBarController.java b/core/java/android/inputmethodservice/NavigationBarController.java index 38be8d9f772d..8ca139fdf9b9 100644 --- a/core/java/android/inputmethodservice/NavigationBarController.java +++ b/core/java/android/inputmethodservice/NavigationBarController.java @@ -16,6 +16,9 @@ package android.inputmethodservice; +import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT; +import static android.app.StatusBarManager.NAVIGATION_HINT_IME_VISIBLE; +import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_BUTTON_VISIBLE; import static android.view.WindowInsets.Type.captionBar; import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS; @@ -23,7 +26,6 @@ import android.animation.ValueAnimator; import android.annotation.FloatRange; import android.annotation.NonNull; import android.annotation.Nullable; -import android.app.StatusBarManager; import android.graphics.Color; import android.graphics.Insets; import android.graphics.Rect; @@ -240,11 +242,10 @@ final class NavigationBarController { NavigationBarView.class::isInstance); if (navigationBarView != null) { // TODO(b/213337792): Support InputMethodService#setBackDisposition(). - // TODO(b/213337792): Set NAVIGATION_HINT_IME_SHOWN only when necessary. - final int hints = StatusBarManager.NAVIGATION_HINT_BACK_ALT + // TODO(b/213337792): Set NAVIGATION_HINT_IME_VISIBLE only when necessary. + final int hints = NAVIGATION_HINT_BACK_ALT | NAVIGATION_HINT_IME_VISIBLE | (mShouldShowImeSwitcherWhenImeIsShown - ? StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_SHOWN - : 0); + ? NAVIGATION_HINT_IME_SWITCHER_BUTTON_VISIBLE : 0); navigationBarView.setNavigationIconHints(hints); navigationBarView.prepareNavButtons(this); } @@ -512,13 +513,14 @@ final class NavigationBarController { } final NavigationBarView navigationBarView = mNavigationBarFrame.findViewByPredicate( NavigationBarView.class::isInstance); - if (navigationBarView == null) { - return; + if (navigationBarView != null) { + // TODO(b/213337792): Support InputMethodService#setBackDisposition(). + // TODO(b/213337792): Set NAVIGATION_HINT_IME_VISIBLE only when necessary. + final int hints = NAVIGATION_HINT_BACK_ALT | NAVIGATION_HINT_IME_VISIBLE + | (mShouldShowImeSwitcherWhenImeIsShown + ? NAVIGATION_HINT_IME_SWITCHER_BUTTON_VISIBLE : 0); + navigationBarView.setNavigationIconHints(hints); } - final int hints = StatusBarManager.NAVIGATION_HINT_BACK_ALT - | (shouldShowImeSwitcherWhenImeIsShown - ? StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_SHOWN : 0); - navigationBarView.setNavigationIconHints(hints); } else { uninstallNavigationBarFrameIfNecessary(); } diff --git a/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java b/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java index 209f323d7b34..5c0977115e36 100644 --- a/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java +++ b/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java @@ -16,6 +16,8 @@ package android.inputmethodservice.navigationbar; +import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT; +import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_BUTTON_VISIBLE; import static android.inputmethodservice.navigationbar.NavigationBarConstants.DARK_MODE_ICON_COLOR_SINGLE_TONE; import static android.inputmethodservice.navigationbar.NavigationBarConstants.LIGHT_MODE_ICON_COLOR_SINGLE_TONE; import static android.inputmethodservice.navigationbar.NavigationBarConstants.NAVBAR_BACK_BUTTON_IME_OFFSET; @@ -28,6 +30,7 @@ import android.annotation.DrawableRes; import android.annotation.FloatRange; import android.annotation.NonNull; import android.app.StatusBarManager; +import android.app.StatusBarManager.NavigationHint; import android.content.Context; import android.content.res.Configuration; import android.graphics.Canvas; @@ -63,7 +66,8 @@ public final class NavigationBarView extends FrameLayout { private int mCurrentRotation = -1; int mDisabledFlags = 0; - int mNavigationIconHints = StatusBarManager.NAVIGATION_HINT_BACK_ALT; + @NavigationHint + private int mNavigationIconHints = 0; private final int mNavBarMode = NAV_BAR_MODE_GESTURAL; private KeyButtonDrawable mBackIcon; @@ -241,8 +245,7 @@ public final class NavigationBarView extends FrameLayout { } private void orientBackButton(KeyButtonDrawable drawable) { - final boolean useAltBack = - (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0; + final boolean useAltBack = (mNavigationIconHints & NAVIGATION_HINT_BACK_ALT) != 0; final boolean isRtl = mConfiguration.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; float degrees = useAltBack ? (isRtl ? 90 : -90) : 0; if (drawable.getRotation() == degrees) { @@ -284,13 +287,15 @@ public final class NavigationBarView extends FrameLayout { * * @param hints bit flags defined in {@link StatusBarManager}. */ - public void setNavigationIconHints(int hints) { - if (hints == mNavigationIconHints) return; + public void setNavigationIconHints(@NavigationHint int hints) { + if (hints == mNavigationIconHints) { + return; + } final boolean newBackAlt = (hints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0; final boolean oldBackAlt = (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0; if (newBackAlt != oldBackAlt) { - //onImeVisibilityChanged(newBackAlt); + //onBackAltChanged(newBackAlt); } if (DEBUG) { @@ -311,10 +316,12 @@ public final class NavigationBarView extends FrameLayout { getImeSwitchButton().setImageDrawable(mImeSwitcherIcon); - // Update IME button visibility, a11y and rotate button always overrides the appearance - final boolean imeSwitcherVisible = - (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_SHOWN) != 0; - getImeSwitchButton().setVisibility(imeSwitcherVisible ? View.VISIBLE : View.INVISIBLE); + // Update IME switcher button visibility, a11y and rotate button always overrides + // the appearance. + final boolean isImeSwitcherButtonVisible = + (mNavigationIconHints & NAVIGATION_HINT_IME_SWITCHER_BUTTON_VISIBLE) != 0; + getImeSwitchButton() + .setVisibility(isImeSwitcherButtonVisible ? View.VISIBLE : View.INVISIBLE); getBackButton().setVisibility(View.VISIBLE); getHomeHandle().setVisibility(View.INVISIBLE); diff --git a/core/java/android/os/BaseBundle.java b/core/java/android/os/BaseBundle.java index 1041041b2a27..1cf293d46350 100644 --- a/core/java/android/os/BaseBundle.java +++ b/core/java/android/os/BaseBundle.java @@ -45,8 +45,7 @@ import java.util.function.BiFunction; * {@link PersistableBundle} subclass. */ @android.ravenwood.annotation.RavenwoodKeepWholeClass -@SuppressWarnings("HiddenSuperclass") -public class BaseBundle implements Parcel.ClassLoaderProvider { +public class BaseBundle { /** @hide */ protected static final String TAG = "Bundle"; static final boolean DEBUG = false; @@ -300,9 +299,8 @@ public class BaseBundle implements Parcel.ClassLoaderProvider { /** * Return the ClassLoader currently associated with this Bundle. - * @hide */ - public ClassLoader getClassLoader() { + ClassLoader getClassLoader() { return mClassLoader; } @@ -416,9 +414,6 @@ public class BaseBundle implements Parcel.ClassLoaderProvider { if ((mFlags & Bundle.FLAG_VERIFY_TOKENS_PRESENT) != 0) { Intent.maybeMarkAsMissingCreatorToken(object); } - } else if (object instanceof Bundle) { - Bundle bundle = (Bundle) object; - bundle.setClassLoaderSameAsContainerBundleWhenRetrievedFirstTime(this); } return (clazz != null) ? clazz.cast(object) : (T) object; } @@ -492,7 +487,7 @@ public class BaseBundle implements Parcel.ClassLoaderProvider { int[] numLazyValues = new int[]{0}; try { parcelledData.readArrayMap(map, count, !parcelledByNative, - /* lazy */ ownsParcel, this, numLazyValues); + /* lazy */ ownsParcel, mClassLoader, numLazyValues); } catch (BadParcelableException e) { if (sShouldDefuse) { Log.w(TAG, "Failed to parse Bundle, but defusing quietly", e); diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java index ee62dea7f9e5..6b1e918a3c47 100644 --- a/core/java/android/os/Binder.java +++ b/core/java/android/os/Binder.java @@ -149,6 +149,11 @@ public class Binder implements IBinder { private static volatile boolean sStackTrackingEnabled = false; /** + * The extension binder object + */ + private IBinder mExtension = null; + + /** * Enable Binder IPC stack tracking. If enabled, every binder transaction will be logged to * {@link TransactionTracker}. * @@ -1237,7 +1242,9 @@ public class Binder implements IBinder { /** @hide */ @Override - public final native @Nullable IBinder getExtension(); + public final @Nullable IBinder getExtension() { + return mExtension; + } /** * Set the binder extension. @@ -1245,7 +1252,12 @@ public class Binder implements IBinder { * * @hide */ - public final native void setExtension(@Nullable IBinder extension); + public final void setExtension(@Nullable IBinder extension) { + mExtension = extension; + setExtensionNative(extension); + } + + private final native void setExtensionNative(@Nullable IBinder extension); /** * Default implementation rewinds the parcels and calls onTransact. On diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java index c51ad9e986ef..1e6469c57fa9 100644 --- a/core/java/android/os/Build.java +++ b/core/java/android/os/Build.java @@ -1271,7 +1271,7 @@ public class Build { * Baklava. */ @FlaggedApi(Flags.FLAG_MAJOR_MINOR_VERSIONING_SCHEME) - public static final int BAKLAVA = CUR_DEVELOPMENT; + public static final int BAKLAVA = 36; } /** @hide */ diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java index 55bfd451d97a..819d58d9f059 100644 --- a/core/java/android/os/Bundle.java +++ b/core/java/android/os/Bundle.java @@ -141,8 +141,6 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable { STRIPPED.putInt("STRIPPED", 1); } - private boolean isFirstRetrievedFromABundle = false; - /** * Constructs a new, empty Bundle. */ @@ -1022,9 +1020,7 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable { return null; } try { - Bundle bundle = (Bundle) o; - bundle.setClassLoaderSameAsContainerBundleWhenRetrievedFirstTime(this); - return bundle; + return (Bundle) o; } catch (ClassCastException e) { typeWarning(key, o, "Bundle", e); return null; @@ -1032,21 +1028,6 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable { } /** - * Set the ClassLoader of a bundle to its container bundle. This is necessary so that when a - * bundle's ClassLoader is changed, it can be propagated to its children. Do this only when it - * is retrieved from the container bundle first time though. Once it is accessed outside of its - * container, its ClassLoader should no longer be changed by its container anymore. - * - * @param containerBundle the bundle this bundle is retrieved from. - */ - void setClassLoaderSameAsContainerBundleWhenRetrievedFirstTime(BaseBundle containerBundle) { - if (!isFirstRetrievedFromABundle) { - setClassLoader(containerBundle.getClassLoader()); - isFirstRetrievedFromABundle = true; - } - } - - /** * Returns the value associated with the given key, or {@code null} if * no mapping of the desired type exists for the given key or a {@code null} * value is explicitly associated with the key. diff --git a/core/java/android/os/CombinedMessageQueue/MessageQueue.java b/core/java/android/os/CombinedMessageQueue/MessageQueue.java index 3026609ed5cb..1e663342522b 100644 --- a/core/java/android/os/CombinedMessageQueue/MessageQueue.java +++ b/core/java/android/os/CombinedMessageQueue/MessageQueue.java @@ -39,7 +39,6 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.ravenwood.RavenwoodEnvironment; import dalvik.annotation.optimization.NeverCompile; -import dalvik.annotation.optimization.NeverInline; import java.io.FileDescriptor; import java.lang.annotation.Retention; @@ -238,7 +237,6 @@ public final class MessageQueue { private final MatchDeliverableMessages mMatchDeliverableMessages = new MatchDeliverableMessages(); - @NeverInline private boolean isIdleConcurrent() { final long now = SystemClock.uptimeMillis(); @@ -269,7 +267,6 @@ public final class MessageQueue { return true; } - @NeverInline private boolean isIdleLegacy() { synchronized (this) { final long now = SystemClock.uptimeMillis(); @@ -292,14 +289,12 @@ public final class MessageQueue { } } - @NeverInline private void addIdleHandlerConcurrent(@NonNull IdleHandler handler) { synchronized (mIdleHandlersLock) { mIdleHandlers.add(handler); } } - @NeverInline private void addIdleHandlerLegacy(@NonNull IdleHandler handler) { synchronized (this) { mIdleHandlers.add(handler); @@ -326,15 +321,11 @@ public final class MessageQueue { addIdleHandlerLegacy(handler); } } - - @NeverInline private void removeIdleHandlerConcurrent(@NonNull IdleHandler handler) { synchronized (mIdleHandlersLock) { mIdleHandlers.remove(handler); } } - - @NeverInline private void removeIdleHandlerLegacy(@NonNull IdleHandler handler) { synchronized (this) { mIdleHandlers.remove(handler); @@ -358,14 +349,12 @@ public final class MessageQueue { } } - @NeverInline private boolean isPollingConcurrent() { // If the loop is quitting then it must not be idling. // We can assume mPtr != 0 when sQuitting is false. return !((boolean) sQuitting.getVolatile(this)) && nativeIsPolling(mPtr); } - @NeverInline private boolean isPollingLegacy() { synchronized (this) { return isPollingLocked(); @@ -396,7 +385,6 @@ public final class MessageQueue { // We can assume mPtr != 0 when mQuitting is false. return !mQuitting && nativeIsPolling(mPtr); } - @NeverInline private void addOnFileDescriptorEventListenerConcurrent(@NonNull FileDescriptor fd, @OnFileDescriptorEventListener.Events int events, @NonNull OnFileDescriptorEventListener listener) { @@ -405,7 +393,6 @@ public final class MessageQueue { } } - @NeverInline private void addOnFileDescriptorEventListenerLegacy(@NonNull FileDescriptor fd, @OnFileDescriptorEventListener.Events int events, @NonNull OnFileDescriptorEventListener listener) { @@ -455,14 +442,12 @@ public final class MessageQueue { } } - @NeverInline private void removeOnFileDescriptorEventListenerConcurrent(@NonNull FileDescriptor fd) { synchronized (mFileDescriptorRecordsLock) { updateOnFileDescriptorEventListenerLocked(fd, 0, null); } } - @NeverInline private void removeOnFileDescriptorEventListenerLegacy(@NonNull FileDescriptor fd) { synchronized (this) { updateOnFileDescriptorEventListenerLocked(fd, 0, null); @@ -796,7 +781,6 @@ public final class MessageQueue { } } - @NeverInline private Message nextConcurrent() { final long ptr = mPtr; if (ptr == 0) { @@ -871,7 +855,6 @@ public final class MessageQueue { } } - @NeverInline private Message nextLegacy() { // Return here if the message loop has already quit and been disposed. // This can happen if the application tries to restart a looper after quit @@ -1036,13 +1019,11 @@ public final class MessageQueue { } } - @NeverInline private int postSyncBarrierConcurrent() { return postSyncBarrier(SystemClock.uptimeMillis()); } - @NeverInline private int postSyncBarrierLegacy() { return postSyncBarrier(SystemClock.uptimeMillis()); } @@ -1162,7 +1143,6 @@ public final class MessageQueue { } } - @NeverInline private void removeSyncBarrierConcurrent(int token) { boolean removed; MessageNode first; @@ -1189,7 +1169,6 @@ public final class MessageQueue { } } - @NeverInline private void removeSyncBarrierLegacy(int token) { synchronized (this) { Message prev = null; @@ -1249,7 +1228,6 @@ public final class MessageQueue { } - @NeverInline private boolean enqueueMessageConcurrent(Message msg, long when) { if (msg.isInUse()) { throw new IllegalStateException(msg + " This message is already in use."); @@ -1258,7 +1236,6 @@ public final class MessageQueue { return enqueueMessageUnchecked(msg, when); } - @NeverInline private boolean enqueueMessageLegacy(Message msg, long when) { synchronized (this) { if (msg.isInUse()) { @@ -1474,54 +1451,11 @@ public final class MessageQueue { Iterator<MessageNode> queueIter = mPriorityQueue.iterator(); MessageNode queueNode = iterateNext(queueIter); - if (queueNode != null && 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 != null && queueNode.isBarrier()) { - queueNode = iterateNext(queueIter); - } - if (queueNode != null && now >= queueNode.getWhen()) { - return true; - } - } + return (queueNode != null && queueNode.isBarrier()); } 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 msg != null && msg.target == null; } - return false; } private static final class MatchHandlerWhatAndObject extends MessageCompare { @@ -1538,13 +1472,11 @@ public final class MessageQueue { private final MatchHandlerWhatAndObject mMatchHandlerWhatAndObject = new MatchHandlerWhatAndObject(); - @NeverInline private boolean hasMessagesConcurrent(Handler h, int what, Object object) { return findOrRemoveMessages(h, what, object, null, 0, mMatchHandlerWhatAndObject, false); } - @NeverInline private boolean hasMessagesLegacy(Handler h, int what, Object object) { synchronized (this) { Message p = mMessages; @@ -1583,13 +1515,11 @@ public final class MessageQueue { private final MatchHandlerWhatAndObjectEquals mMatchHandlerWhatAndObjectEquals = new MatchHandlerWhatAndObjectEquals(); - @NeverInline private boolean hasEqualMessagesConcurrent(Handler h, int what, Object object) { return findOrRemoveMessages(h, what, object, null, 0, mMatchHandlerWhatAndObjectEquals, false); } - @NeverInline private boolean hasEqualMessagesLegacy(Handler h, int what, Object object) { synchronized (this) { Message p = mMessages; @@ -1628,13 +1558,11 @@ public final class MessageQueue { private final MatchHandlerRunnableAndObject mMatchHandlerRunnableAndObject = new MatchHandlerRunnableAndObject(); - @NeverInline private boolean hasMessagesConcurrent(Handler h, Runnable r, Object object) { return findOrRemoveMessages(h, -1, object, r, 0, mMatchHandlerRunnableAndObject, false); } - @NeverInline private boolean hasMessagesLegacy(Handler h, Runnable r, Object object) { synchronized (this) { Message p = mMessages; @@ -1669,12 +1597,10 @@ public final class MessageQueue { } private final MatchHandler mMatchHandler = new MatchHandler(); - @NeverInline private boolean hasMessagesConcurrent(Handler h) { return findOrRemoveMessages(h, -1, null, null, 0, mMatchHandler, false); } - @NeverInline private boolean hasMessagesLegacy(Handler h) { synchronized (this) { Message p = mMessages; @@ -1699,12 +1625,10 @@ public final class MessageQueue { } } - @NeverInline private void removeMessagesConcurrent(Handler h, int what, Object object) { findOrRemoveMessages(h, what, object, null, 0, mMatchHandlerWhatAndObject, true); } - @NeverInline private void removeMessagesLegacy(Handler h, int what, Object object) { synchronized (this) { Message p = mMessages; @@ -1759,12 +1683,10 @@ public final class MessageQueue { } } - @NeverInline private void removeEqualMessagesConcurrent(Handler h, int what, Object object) { findOrRemoveMessages(h, what, object, null, 0, mMatchHandlerWhatAndObjectEquals, true); } - @NeverInline private void removeEqualMessagesLegacy(Handler h, int what, Object object) { synchronized (this) { Message p = mMessages; @@ -1820,12 +1742,10 @@ public final class MessageQueue { } } - @NeverInline private void removeMessagesConcurrent(Handler h, Runnable r, Object object) { findOrRemoveMessages(h, -1, object, r, 0, mMatchHandlerRunnableAndObject, true); } - @NeverInline private void removeMessagesLegacy(Handler h, Runnable r, Object object) { synchronized (this) { Message p = mMessages; @@ -1895,12 +1815,10 @@ public final class MessageQueue { private final MatchHandlerRunnableAndObjectEquals mMatchHandlerRunnableAndObjectEquals = new MatchHandlerRunnableAndObjectEquals(); - @NeverInline private void removeEqualMessagesConcurrent(Handler h, Runnable r, Object object) { findOrRemoveMessages(h, -1, object, r, 0, mMatchHandlerRunnableAndObjectEquals, true); } - @NeverInline private void removeEqualMessagesLegacy(Handler h, Runnable r, Object object) { synchronized (this) { Message p = mMessages; @@ -1969,12 +1887,10 @@ public final class MessageQueue { } private final MatchHandlerAndObject mMatchHandlerAndObject = new MatchHandlerAndObject(); - @NeverInline private void removeCallbacksAndMessagesConcurrent(Handler h, Object object) { findOrRemoveMessages(h, -1, object, null, 0, mMatchHandlerAndObject, true); } - @NeverInline private void removeCallbacksAndMessagesLegacy(Handler h, Object object) { synchronized (this) { Message p = mMessages; @@ -2043,12 +1959,10 @@ public final class MessageQueue { private final MatchHandlerAndObjectEquals mMatchHandlerAndObjectEquals = new MatchHandlerAndObjectEquals(); - @NeverInline void removeCallbacksAndEqualMessagesConcurrent(Handler h, Object object) { findOrRemoveMessages(h, -1, object, null, 0, mMatchHandlerAndObjectEquals, true); } - @NeverInline void removeCallbacksAndEqualMessagesLegacy(Handler h, Object object) { synchronized (this) { Message p = mMessages; diff --git a/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java b/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java index d7d8e4199b33..80da487a1358 100644 --- a/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java +++ b/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java @@ -16,7 +16,6 @@ package android.os; -import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -1122,27 +1121,7 @@ public final class MessageQueue { Iterator<MessageNode> queueIter = mPriorityQueue.iterator(); MessageNode queueNode = iterateNext(queueIter); - if (queueNode != null && 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 != null && queueNode.isBarrier()) { - queueNode = iterateNext(queueIter); - } - if (queueNode != null && now >= queueNode.getWhen()) { - return true; - } - } - return false; + return queueNode != null && queueNode.isBarrier(); } private StateNode getStateNode(StackNode node) { diff --git a/core/java/android/os/LegacyMessageQueue/MessageQueue.java b/core/java/android/os/LegacyMessageQueue/MessageQueue.java index d12d99a71251..cde2ba56fcba 100644 --- a/core/java/android/os/LegacyMessageQueue/MessageQueue.java +++ b/core/java/android/os/LegacyMessageQueue/MessageQueue.java @@ -820,33 +820,10 @@ public final class MessageQueue { */ 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; + synchronized (this) { + Message msg = mMessages; + return msg != null && msg.target == null; } - return false; } boolean hasMessages(Handler h, int what, Object object) { diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index c6a63a7ef238..e58934746c14 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -50,7 +50,6 @@ import com.android.internal.util.ArrayUtils; import dalvik.annotation.optimization.CriticalNative; import dalvik.annotation.optimization.FastNative; -import dalvik.annotation.optimization.NeverInline; import libcore.util.SneakyThrow; @@ -629,19 +628,6 @@ public final class Parcel { } } - @NeverInline - private void errorUsedWhileRecycling() { - String error = "Parcel used while recycled. " - + Log.getStackTraceString(new Throwable()) - + " Original recycle call (if DEBUG_RECYCLE): ", mStack; - Log.wtf(TAG, error); - // TODO(b/381155347): harder error - } - - private void assertNotRecycled() { - if (mRecycled) errorUsedWhileRecycling(); - } - /** * Set a {@link ReadWriteHelper}, which can be used to avoid having duplicate strings, for * example. @@ -1194,7 +1180,6 @@ public final class Parcel { * growing dataCapacity() if needed. */ public final void writeInt(int val) { - assertNotRecycled(); int err = nativeWriteInt(mNativePtr, val); if (err != OK) { nativeSignalExceptionForError(err); @@ -3297,7 +3282,6 @@ public final class Parcel { * Read an integer value from the parcel at the current dataPosition(). */ public final int readInt() { - assertNotRecycled(); return nativeReadInt(mNativePtr); } @@ -4639,9 +4623,11 @@ public final class Parcel { object = readValue(type, loader, clazz, itemTypes); int actual = dataPosition() - start; if (actual != length) { - Slog.wtfStack(TAG, - "Unparcelling of " + object + " of type " + Parcel.valueTypeToString(type) - + " consumed " + actual + " bytes, but " + length + " expected."); + String error = "Unparcelling of " + object + " of type " + + Parcel.valueTypeToString(type) + " consumed " + actual + + " bytes, but " + length + " expected."; + Slog.wtfStack(TAG, error); + throw new BadParcelableException(error); } } else { object = readValue(type, loader, clazz, itemTypes); @@ -4675,7 +4661,7 @@ public final class Parcel { * @hide */ @Nullable - private Object readLazyValue(@Nullable ClassLoaderProvider loaderProvider) { + public Object readLazyValue(@Nullable ClassLoader loader) { int start = dataPosition(); int type = readInt(); if (isLengthPrefixed(type)) { @@ -4686,17 +4672,12 @@ public final class Parcel { int end = MathUtils.addOrThrow(dataPosition(), objectLength); int valueLength = end - start; setDataPosition(end); - return new LazyValue(this, start, valueLength, type, loaderProvider); + return new LazyValue(this, start, valueLength, type, loader); } else { - return readValue(type, getClassLoader(loaderProvider), /* clazz */ null); + return readValue(type, loader, /* clazz */ null); } } - @Nullable - private static ClassLoader getClassLoader(@Nullable ClassLoaderProvider loaderProvider) { - return loaderProvider == null ? null : loaderProvider.getClassLoader(); - } - private static final class LazyValue implements BiFunction<Class<?>, Class<?>[], Object> { /** @@ -4710,12 +4691,7 @@ public final class Parcel { private final int mPosition; private final int mLength; private final int mType; - // this member is set when a bundle that includes a LazyValue is unparceled. But it is used - // when apply method is called. Between these 2 events, the bundle's ClassLoader could have - // changed. Let the bundle be a ClassLoaderProvider allows the bundle provides its current - // ClassLoader at the time apply method is called. - @NonNull - private final ClassLoaderProvider mLoaderProvider; + @Nullable private final ClassLoader mLoader; @Nullable private Object mObject; /** @@ -4726,13 +4702,12 @@ public final class Parcel { */ @Nullable private volatile Parcel mSource; - LazyValue(Parcel source, int position, int length, int type, - @NonNull ClassLoaderProvider loaderProvider) { + LazyValue(Parcel source, int position, int length, int type, @Nullable ClassLoader loader) { mSource = requireNonNull(source); mPosition = position; mLength = length; mType = type; - mLoaderProvider = loaderProvider; + mLoader = loader; } @Override @@ -4745,8 +4720,7 @@ public final class Parcel { int restore = source.dataPosition(); try { source.setDataPosition(mPosition); - mObject = source.readValue(mLoaderProvider.getClassLoader(), clazz, - itemTypes); + mObject = source.readValue(mLoader, clazz, itemTypes); } finally { source.setDataPosition(restore); } @@ -4819,8 +4793,7 @@ public final class Parcel { return Objects.equals(mObject, value.mObject); } // Better safely fail here since this could mean we get different objects. - if (!Objects.equals(mLoaderProvider.getClassLoader(), - value.mLoaderProvider.getClassLoader())) { + if (!Objects.equals(mLoader, value.mLoader)) { return false; } // Otherwise compare metadata prior to comparing payload. @@ -4834,24 +4807,10 @@ public final class Parcel { @Override public int hashCode() { // Accessing mSource first to provide memory barrier for mObject - return Objects.hash(mSource == null, mObject, mLoaderProvider.getClassLoader(), mType, - mLength); + return Objects.hash(mSource == null, mObject, mLoader, mType, mLength); } } - /** - * Provides a ClassLoader. - * @hide - */ - public interface ClassLoaderProvider { - /** - * Returns a ClassLoader. - * - * @return ClassLoader - */ - ClassLoader getClassLoader(); - } - /** Same as {@link #readValue(ClassLoader, Class, Class[])} without any item types. */ private <T> T readValue(int type, @Nullable ClassLoader loader, @Nullable Class<T> clazz) { // Avoids allocating Class[0] array @@ -5592,8 +5551,8 @@ public final class Parcel { } private void readArrayMapInternal(@NonNull ArrayMap<? super String, Object> outVal, - int size, @Nullable ClassLoaderProvider loaderProvider) { - readArrayMap(outVal, size, /* sorted */ true, /* lazy */ false, loaderProvider, null); + int size, @Nullable ClassLoader loader) { + readArrayMap(outVal, size, /* sorted */ true, /* lazy */ false, loader, null); } /** @@ -5607,12 +5566,11 @@ public final class Parcel { * @hide */ void readArrayMap(ArrayMap<? super String, Object> map, int size, boolean sorted, - boolean lazy, @Nullable ClassLoaderProvider loaderProvider, int[] lazyValueCount) { + boolean lazy, @Nullable ClassLoader loader, int[] lazyValueCount) { ensureWithinMemoryLimit(SIZE_COMPLEX_TYPE, size); while (size > 0) { String key = readString(); - Object value = (lazy) ? readLazyValue(loaderProvider) : readValue( - getClassLoader(loaderProvider)); + Object value = (lazy) ? readLazyValue(loader) : readValue(loader); if (value instanceof LazyValue) { lazyValueCount[0]++; } @@ -5633,12 +5591,12 @@ public final class Parcel { */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public void readArrayMap(@NonNull ArrayMap<? super String, Object> outVal, - @Nullable ClassLoaderProvider loaderProvider) { + @Nullable ClassLoader loader) { final int N = readInt(); if (N < 0) { return; } - readArrayMapInternal(outVal, N, loaderProvider); + readArrayMapInternal(outVal, N, loader); } /** diff --git a/core/java/android/os/TestLooperManager.java b/core/java/android/os/TestLooperManager.java index 289b98c9b1d4..d451109554fa 100644 --- a/core/java/android/os/TestLooperManager.java +++ b/core/java/android/os/TestLooperManager.java @@ -129,9 +129,6 @@ public class TestLooperManager { /** * 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() { diff --git a/core/java/android/os/UidBatteryConsumer.java b/core/java/android/os/UidBatteryConsumer.java index 976bfe41ba45..62d5015af914 100644 --- a/core/java/android/os/UidBatteryConsumer.java +++ b/core/java/android/os/UidBatteryConsumer.java @@ -147,7 +147,7 @@ public final class UidBatteryConsumer extends BatteryConsumer { for (int screenState = 0; screenState < SCREEN_STATE_COUNT; screenState++) { if (mData.layout.screenStateDataIncluded - && screenState == POWER_STATE_UNSPECIFIED) { + && screenState == SCREEN_STATE_UNSPECIFIED) { continue; } diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 08f68f1874e7..6c21dbf126bb 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -30,6 +30,9 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; +import android.annotation.SpecialUsers.CanBeALL; +import android.annotation.SpecialUsers.CanBeNULL; +import android.annotation.SpecialUsers.CannotBeSpecialUser; import android.annotation.StringDef; import android.annotation.SuppressAutoDoc; import android.annotation.SuppressLint; @@ -2907,10 +2910,18 @@ public class UserManager { * <p>Currently, on most form factors the first human user on the device will be the main user; * in the future, the concept may be transferable, so a different user (or even no user at all) * may be designated the main user instead. On other form factors there might not be a main + * user. In the future, the concept may be removed, i.e. typical future devices may have no main * user. * * <p>Note that this will not be the system user on devices for which * {@link #isHeadlessSystemUserMode()} returns true. + * + * <p>NB: Features should ideally not limit functionality to the main user. Ideally, they + * should either work for all users or for all admin users. If a feature should only work for + * select users, its determination of which user should be done intelligently or be + * customizable. Not all devices support a main user, and the idea of singling out one user as + * special is contrary to overall multiuser goals. + * * @hide */ @SystemApi @@ -2927,6 +2938,12 @@ public class UserManager { /** * Returns the designated "main user" of the device, or {@code null} if there is no main user. * + * <p>NB: Features should ideally not limit functionality to the main user. Ideally, they + * should either work for all users or for all admin users. If a feature should only work for + * select users, its determination of which user should be done intelligently or be + * customizable. Not all devices support a main user, and the idea of singling out one user as + * special is contrary to overall multiuser goals. + * * @see #isMainUser() * @hide */ @@ -3913,7 +3930,8 @@ public class UserManager { android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true) - public @NonNull UserProperties getUserProperties(@NonNull UserHandle userHandle) { + public @NonNull UserProperties getUserProperties( + @CannotBeSpecialUser @NonNull UserHandle userHandle) { final int userId = userHandle.getIdentifier(); if (userId < 0 && android.multiuser.Flags.fixGetUserPropertyCache()) { @@ -6811,7 +6829,7 @@ public class UserManager { */ @SystemApi public static final class EnforcingUser implements Parcelable { - private final @UserIdInt int userId; + private final @CanBeALL @CanBeNULL @UserIdInt int userId; private final @UserRestrictionSource int userRestrictionSource; /** @@ -6856,7 +6874,7 @@ public class UserManager { * * <p> Will be UserHandle.USER_NULL when restriction is set by the system. */ - public UserHandle getUserHandle() { + public @CanBeALL @CanBeNULL UserHandle getUserHandle() { return UserHandle.of(userId); } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 2e231e3957c6..65c857a51b29 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -11008,6 +11008,21 @@ public final class Settings { @Readable public static final String SHOW_NOTIFICATION_SNOOZE = "show_notification_snooze"; + /** + * Controls whether dual shade is enabled. This splits notifications and quick settings to + * have their own independently expandable/collapsible panels, appearing on either side of + * the large screen (including unfolded device) or sharing a space on a narrow screen + * (including a folded device). Both panels will now cover the screen only partially + * (wrapping their content), so a running app or the lockscreen will remain visible in the + * background. + * <p> + * Type: int (0 for false, 1 for true) + * + * @hide + */ + @android.provider.Settings.Readable + public static final String DUAL_SHADE = "dual_shade"; + /** * 1 if it is allowed to remove the primary GAIA account. 0 by default. * @hide @@ -13788,6 +13803,16 @@ public final class Settings { = "enable_freeform_support"; /** + * Whether to override the availability of the desktop experiences features on the + * device. With desktop experiences enabled, secondary displays can be used to run + * apps, in desktop mode by default. Otherwise they can only be used for mirroring. + * @hide + */ + @Readable + public static final String DEVELOPMENT_OVERRIDE_DESKTOP_EXPERIENCE_FEATURES = + "override_desktop_experience_features"; + + /** * Whether to override the availability of the desktop mode on the main display of the * device. If on, users can make move an app to the desktop, allowing a freeform windowing * experience. diff --git a/core/java/android/security/FileIntegrityManager.java b/core/java/android/security/FileIntegrityManager.java index 478435b1ac5e..9e02ecd19aee 100644 --- a/core/java/android/security/FileIntegrityManager.java +++ b/core/java/android/security/FileIntegrityManager.java @@ -170,11 +170,6 @@ public final class FileIntegrityManager { @Deprecated public boolean isAppSourceCertificateTrusted(@NonNull X509Certificate certificate) throws CertificateEncodingException { - try { - return mService.isAppSourceCertificateTrusted( - certificate.getEncoded(), mContext.getOpPackageName()); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + return false; } } diff --git a/core/java/android/security/IFileIntegrityService.aidl b/core/java/android/security/IFileIntegrityService.aidl index ddb662ad42cb..c6def239d59a 100644 --- a/core/java/android/security/IFileIntegrityService.aidl +++ b/core/java/android/security/IFileIntegrityService.aidl @@ -25,7 +25,6 @@ import android.os.IInstalld; */ interface IFileIntegrityService { boolean isApkVeritySupported(); - boolean isAppSourceCertificateTrusted(in byte[] certificateBytes, in String packageName); IInstalld.IFsveritySetupAuthToken createAuthToken(in ParcelFileDescriptor authFd); diff --git a/core/java/android/service/notification/Adjustment.java b/core/java/android/service/notification/Adjustment.java index 073f512a85fb..09ebc9f030c2 100644 --- a/core/java/android/service/notification/Adjustment.java +++ b/core/java/android/service/notification/Adjustment.java @@ -21,6 +21,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.StringDef; import android.annotation.SystemApi; +import android.annotation.TestApi; import android.app.Notification; import android.os.Build; import android.os.Bundle; @@ -223,6 +224,14 @@ public final class Adjustment implements Parcelable { public static final int TYPE_CONTENT_RECOMMENDATION = 4; /** + * Data type: String, the classification type of this notification. The OS may display + * notifications differently depending on the type, and may change the alerting level of the + * notification. + */ + @FlaggedApi(android.app.Flags.FLAG_NM_SUMMARIZATION) + public static final String KEY_SUMMARIZATION = "key_summarization"; + + /** * Create a notification adjustment. * * @param pkg The package of the notification. diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java index 72569075c2ed..f23006584621 100644 --- a/core/java/android/service/notification/NotificationListenerService.java +++ b/core/java/android/service/notification/NotificationListenerService.java @@ -23,6 +23,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SdkConstant; import android.annotation.SystemApi; +import android.annotation.TestApi; import android.annotation.UiThread; import android.app.ActivityManager; import android.app.INotificationManager; @@ -56,6 +57,7 @@ import android.os.Parcelable; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; +import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; import android.widget.RemoteViews; @@ -1824,6 +1826,7 @@ public abstract class NotificationListenerService extends Service { private int mProposedImportance; // Sensitive info detected by the notification assistant private boolean mSensitiveContent; + private String mSummarization; private static final int PARCEL_VERSION = 2; @@ -1864,6 +1867,7 @@ public abstract class NotificationListenerService extends Service { out.writeBoolean(mIsBubble); out.writeInt(mProposedImportance); out.writeBoolean(mSensitiveContent); + out.writeString(mSummarization); } /** @hide */ @@ -1904,6 +1908,7 @@ public abstract class NotificationListenerService extends Service { mIsBubble = in.readBoolean(); mProposedImportance = in.readInt(); mSensitiveContent = in.readBoolean(); + mSummarization = in.readString(); } @@ -2180,6 +2185,16 @@ public abstract class NotificationListenerService extends Service { } /** + * Returns a summary of the content in the notification, or potentially of the current + * notification and related notifications (for example, if this is provided for a group + * summary notification it may be summarizing all the child notifications). + */ + @FlaggedApi(android.app.Flags.FLAG_NM_SUMMARIZATION) + public @Nullable String getSummarization() { + return mSummarization; + } + + /** * Returns the intended transition to ranking passed by {@link NotificationAssistantService} * @hide */ @@ -2201,7 +2216,7 @@ public abstract class NotificationListenerService extends Service { ArrayList<CharSequence> smartReplies, boolean canBubble, boolean isTextChanged, boolean isConversation, ShortcutInfo shortcutInfo, int rankingAdjustment, boolean isBubble, int proposedImportance, - boolean sensitiveContent) { + boolean sensitiveContent, String summarization) { mKey = key; mRank = rank; mIsAmbient = importance < NotificationManager.IMPORTANCE_LOW; @@ -2229,6 +2244,7 @@ public abstract class NotificationListenerService extends Service { mIsBubble = isBubble; mProposedImportance = proposedImportance; mSensitiveContent = sensitiveContent; + mSummarization = TextUtils.nullIfEmpty(summarization); } /** @@ -2271,7 +2287,8 @@ public abstract class NotificationListenerService extends Service { other.mRankingAdjustment, other.mIsBubble, other.mProposedImportance, - other.mSensitiveContent); + other.mSensitiveContent, + other.mSummarization); } /** @@ -2332,7 +2349,8 @@ public abstract class NotificationListenerService extends Service { && Objects.equals(mRankingAdjustment, other.mRankingAdjustment) && Objects.equals(mIsBubble, other.mIsBubble) && Objects.equals(mProposedImportance, other.mProposedImportance) - && Objects.equals(mSensitiveContent, other.mSensitiveContent); + && Objects.equals(mSensitiveContent, other.mSensitiveContent) + && Objects.equals(mSummarization, other.mSummarization); } } diff --git a/core/java/android/service/notification/StatusBarNotification.java b/core/java/android/service/notification/StatusBarNotification.java index 105fa3ffd4cd..79957f411597 100644 --- a/core/java/android/service/notification/StatusBarNotification.java +++ b/core/java/android/service/notification/StatusBarNotification.java @@ -18,6 +18,8 @@ package android.service.notification; import static android.text.TextUtils.formatSimple; +import static com.android.window.flags.Flags.enablePerDisplayPackageContextCacheInStatusbarNotif; + import android.annotation.NonNull; import android.app.Notification; import android.app.NotificationManager; @@ -37,9 +39,9 @@ import android.util.ArrayMap; import com.android.internal.logging.InstanceId; import com.android.internal.logging.nano.MetricsProto; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import static com.android.window.flags.Flags.enablePerDisplayPackageContextCacheInStatusbarNotif; import java.util.ArrayList; +import java.util.Collections; import java.util.Map; /** @@ -81,7 +83,8 @@ public class StatusBarNotification implements Parcelable { @Deprecated private Context mContext; // used for inflation & icon expansion // Maps display id to context used for remote view content inflation and status bar icon. - private final Map<Integer, Context> mContextForDisplayId = new ArrayMap<>(); + private final Map<Integer, Context> mContextForDisplayId = + Collections.synchronizedMap(new ArrayMap<>()); /** @hide */ public StatusBarNotification(String pkg, String opPkg, int id, diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index cb498503f201..a5d52957c40e 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -121,6 +121,7 @@ public class StaticLayout extends Layout { b.mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE; b.mJustificationMode = Layout.JUSTIFICATION_MODE_NONE; b.mLineBreakConfig = LineBreakConfig.NONE; + b.mUseBoundsForWidth = false; b.mMinimumFontMetrics = null; return b; } diff --git a/core/java/android/text/style/TtsSpan.java b/core/java/android/text/style/TtsSpan.java index a337ba2a57fb..e0d4ec1ca826 100644 --- a/core/java/android/text/style/TtsSpan.java +++ b/core/java/android/text/style/TtsSpan.java @@ -108,11 +108,13 @@ public class TtsSpan implements ParcelableSpan { /** * The text associated with this span is a time, consisting of a number of - * hours and minutes, specified with {@link #ARG_HOURS} and - * {@link #ARG_MINUTES}. + * hours, minutes, and seconds specified with {@link #ARG_HOURS}, {@link #ARG_MINUTES}, and + * {@link #ARG_SECONDS}. * Also accepts the arguments {@link #ARG_GENDER}, * {@link #ARG_ANIMACY}, {@link #ARG_MULTIPLICITY} and - * {@link #ARG_CASE}. + * {@link #ARG_CASE}. This is different from {@link #TYPE_DURATION}. This should be used to + * convey a particular moment in time, such as a clock time, while {@link #TYPE_DURATION} should + * be used to convey an interval of time. */ public static final String TYPE_TIME = "android.type.time"; @@ -310,16 +312,18 @@ public class TtsSpan implements ParcelableSpan { public static final String ARG_UNIT = "android.arg.unit"; /** - * Argument used to specify the hours of a time. The hours should be - * provided as an integer in the range from 0 up to and including 24. - * Can be used with {@link #TYPE_TIME}. + * Argument used to specify the hours of a time or duration. The hours should be + * provided as an integer in the range from 0 up to and including 24 for + * {@link #TYPE_TIME}. + * Can be used with {@link #TYPE_TIME} or {@link #TYPE_DURATION}. */ public static final String ARG_HOURS = "android.arg.hours"; /** - * Argument used to specify the minutes of a time. The minutes should be - * provided as an integer in the range from 0 up to and including 59. - * Can be used with {@link #TYPE_TIME}. + * Argument used to specify the minutes of a time or duration. The minutes should be + * provided as an integer in the range from 0 up to and including 59 for + * {@link #TYPE_TIME}. + * Can be used with {@link #TYPE_TIME} or {@link #TYPE_DURATION}. */ public static final String ARG_MINUTES = "android.arg.minutes"; diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 06eb0428bfcf..2ec5dbc5612a 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -1145,4 +1145,17 @@ interface IWindowManager * @param deviceId The id of the {@link InputDevice} that will handle the shortcut. */ KeyboardShortcutGroup getApplicationLaunchKeyboardShortcuts(int deviceId); + + /* + * Notifies about IME insets animation. + * + * @param running Indicates the insets animation state. + * @param animationType Indicates the {@link InsetsController.AnimationType} + */ + oneway void notifyImeInsetsAnimationStateChanged(boolean running, int animationType); + + /** + * Returns whether the display with {@code displayId} ignores orientation request. + */ + boolean getIgnoreOrientationRequest(int displayId); } diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index c174fbe0bbcd..e097a0764512 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -214,9 +214,14 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation * Notifies when the state of running animation is changed. The state is either "running" or * "idle". * - * @param running {@code true} if there is any animation running; {@code false} otherwise. + * @param running {@code true} if the given insets types start running + * {@code false} otherwise. + * @param animationType {@link AnimationType} + * @param insetsTypes {@link Type}. */ - default void notifyAnimationRunningStateChanged(boolean running) {} + default void notifyAnimationRunningStateChanged(boolean running, + @AnimationType int animationType, @InsetsType int insetsTypes) { + } /** @see ViewRootImpl#isHandlingPointerEvent */ default boolean isHandlingPointerEvent() { @@ -744,9 +749,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation final InsetsAnimationControlRunner runner = new InsetsResizeAnimationRunner( mFrame, mFromState, mToState, RESIZE_INTERPOLATOR, ANIMATION_DURATION_RESIZE, mTypes, InsetsController.this); - if (mRunningAnimations.isEmpty()) { - mHost.notifyAnimationRunningStateChanged(true); - } + mHost.notifyAnimationRunningStateChanged(true, + runner.getAnimationType(), mTypes); mRunningAnimations.add(new RunningAnimation(runner, runner.getAnimationType())); } }; @@ -1560,9 +1564,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } } ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_ANIMATION_RUNNING); - if (mRunningAnimations.isEmpty()) { - mHost.notifyAnimationRunningStateChanged(true); - } + mHost.notifyAnimationRunningStateChanged(true, animationType, types); mRunningAnimations.add(new RunningAnimation(runner, animationType)); if (DEBUG) Log.d(TAG, "Animation added to runner. useInsetsAnimationThread: " + useInsetsAnimationThread); @@ -1842,9 +1844,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation break; } } - if (mRunningAnimations.isEmpty()) { - mHost.notifyAnimationRunningStateChanged(false); - } + mHost.notifyAnimationRunningStateChanged( + false, control.getAnimationType(), removedTypes); onAnimationStateChanged(removedTypes, false /* running */); } diff --git a/core/java/android/view/InsetsSourceControl.java b/core/java/android/view/InsetsSourceControl.java index 7f2f0e8863df..cfb4835a13f7 100644 --- a/core/java/android/view/InsetsSourceControl.java +++ b/core/java/android/view/InsetsSourceControl.java @@ -194,7 +194,7 @@ public class InsetsSourceControl implements Parcelable { } public void release(Consumer<SurfaceControl> surfaceReleaseConsumer) { - if (mLeash != null) { + if (mLeash != null && mLeash.isValid()) { surfaceReleaseConsumer.accept(mLeash); } } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index cd8a85a66c1a..7c5b300e9d24 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -24,6 +24,7 @@ import static android.graphics.HardwareRenderer.SYNC_CONTEXT_IS_STOPPED; import static android.graphics.HardwareRenderer.SYNC_LOST_SURFACE_REWARD_IF_FOUND; import static android.os.IInputConstants.INVALID_INPUT_EVENT_ID; import static android.os.Trace.TRACE_TAG_VIEW; +import static android.service.autofill.Flags.improveFillDialogAconfig; import static android.util.SequenceUtils.getInitSeq; import static android.util.SequenceUtils.isIncomingSeqStale; import static android.view.Display.DEFAULT_DISPLAY; @@ -922,6 +923,8 @@ public final class ViewRootImpl implements ViewParent, private boolean mInsetsAnimationRunning; + private int mInsetsAnimatingTypes = 0; + private long mPreviousFrameDrawnTime = -1; // The largest view size percentage to the display size. Used on trace to collect metric. private float mLargestChildPercentage = 0.0f; @@ -2520,17 +2523,49 @@ public final class ViewRootImpl implements ViewParent, * Notify the when the running state of a insets animation changed. */ @VisibleForTesting - public void notifyInsetsAnimationRunningStateChanged(boolean running) { + public void notifyInsetsAnimationRunningStateChanged(boolean running, + @InsetsController.AnimationType int animationType, + @InsetsType int insetsTypes) { + @InsetsType int previousInsetsType = mInsetsAnimatingTypes; + // If improveFillDialogAconfig is disabled, we notify WindowSession of all the updates we + // receive here + boolean notifyWindowSession = !improveFillDialogAconfig(); + if (running) { + mInsetsAnimatingTypes |= insetsTypes; + } else { + mInsetsAnimatingTypes &= ~insetsTypes; + } if (sToolkitSetFrameRateReadOnlyFlagValue) { - if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { - Trace.instant(Trace.TRACE_TAG_VIEW, - TextUtils.formatSimple("notifyInsetsAnimationRunningStateChanged(%s)", - Boolean.toString(running))); - } mInsetsAnimationRunning = running; - try { - mWindowSession.notifyInsetsAnimationRunningStateChanged(mWindow, running); - } catch (RemoteException e) { + // If improveFillDialogAconfig is enabled, we need to confirm other animations aren't + // running to maintain the existing behavior. System server were notified previously + // only when animation started running or stopped when there were no running animations. + if (improveFillDialogAconfig()) { + if ((previousInsetsType == 0 && mInsetsAnimatingTypes != 0) + || (previousInsetsType != 0 && mInsetsAnimatingTypes == 0)) { + notifyWindowSession = true; + } + } + if (notifyWindowSession) { + if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { + Trace.instant(Trace.TRACE_TAG_VIEW, + TextUtils.formatSimple("notifyInsetsAnimationRunningStateChanged(%s)", + Boolean.toString(running))); + } + try { + mWindowSession.notifyInsetsAnimationRunningStateChanged(mWindow, running); + } catch (RemoteException e) { + } + } + } + if (improveFillDialogAconfig()) { + // Update WindowManager for ImeAnimation + if ((insetsTypes & WindowInsets.Type.ime()) != 0) { + try { + WindowManagerGlobal.getWindowManagerService() + .notifyImeInsetsAnimationStateChanged(running, animationType); + } catch (RemoteException e) { + } } } } diff --git a/core/java/android/view/ViewRootInsetsControllerHost.java b/core/java/android/view/ViewRootInsetsControllerHost.java index 889acca4b8b1..f1666dbebd7b 100644 --- a/core/java/android/view/ViewRootInsetsControllerHost.java +++ b/core/java/android/view/ViewRootInsetsControllerHost.java @@ -275,9 +275,12 @@ public class ViewRootInsetsControllerHost implements InsetsController.Host { } @Override - public void notifyAnimationRunningStateChanged(boolean running) { + public void notifyAnimationRunningStateChanged(boolean running, + @InsetsController.AnimationType int animationType, + @WindowInsets.Type.InsetsType int insetsTypes) { if (mViewRoot != null) { - mViewRoot.notifyInsetsAnimationRunningStateChanged(running); + mViewRoot.notifyInsetsAnimationRunningStateChanged( + running, animationType, insetsTypes); } } diff --git a/core/java/android/view/autofill/AutofillFeatureFlags.java b/core/java/android/view/autofill/AutofillFeatureFlags.java index 206e47f13890..0814e23eea87 100644 --- a/core/java/android/view/autofill/AutofillFeatureFlags.java +++ b/core/java/android/view/autofill/AutofillFeatureFlags.java @@ -21,6 +21,7 @@ import static android.service.autofill.Flags.improveFillDialogAconfig; import android.annotation.SuppressLint; import android.annotation.TestApi; import android.provider.DeviceConfig; +import android.service.autofill.Flags; import android.text.TextUtils; import android.util.ArraySet; import android.view.View; @@ -348,6 +349,14 @@ public class AutofillFeatureFlags { // END AUTOFILL REMOVE PRE_TRIGGER FLAGS /** + * Whether per Session FillEventHistory is enabled. + * + * @hide + */ + public static final String DEVICE_CONFIG_SESSION_FILL_EVENT_HISTORY = + "session_fill_event_history"; + + /** * Define the max input length for autofill to show suggesiton UI * * E.g. if flag is set to 3, autofill will only show suggestions when user inputs less than 3 @@ -409,6 +418,13 @@ public class AutofillFeatureFlags { // END AUTOFILL REMOVE PRE_TRIGGER FLAGS DEFAULTS /** + * Default for whether per Session FillEventHistory is enabled + * + * @hide + */ + public static final boolean DEFAULT_SESSION_FILL_EVENT_HISTORY_ENABLED = false; + + /** * @hide */ public static final int DEFAULT_MAX_INPUT_LENGTH_FOR_AUTOFILL = 3; @@ -697,4 +713,20 @@ public class AutofillFeatureFlags { DEVICE_CONFIG_FILL_DIALOG_MIN_WAIT_AFTER_IME_ANIMATION_END_MS, DEFAULT_FILL_DIALOG_MIN_WAIT_AFTER_IME_ANIMATION_END_MS); } + + /** + * Whether tracking FillEventHistory per Session is enabled + * + * @hide + */ + public static boolean isMultipleFillEventHistoryEnabled() { + if (!Flags.multipleFillHistory()) { + return false; + } + + return DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_AUTOFILL, + DEVICE_CONFIG_SESSION_FILL_EVENT_HISTORY, + DEFAULT_SESSION_FILL_EVENT_HISTORY_ENABLED); + } } diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 7c75d7b30037..0e329c2859db 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -44,7 +44,6 @@ import android.app.Activity; import android.app.ActivityOptions; import android.app.ActivityThread; import android.app.Application; -import android.app.LoadedApk; import android.app.PendingIntent; import android.app.RemoteInput; import android.appwidget.AppWidgetHostView; @@ -8484,8 +8483,14 @@ public class RemoteViews implements Parcelable, Filter { return context; } try { - LoadedApk.checkAndUpdateApkPaths(mApplication); - Context applicationContext = context.createApplicationContext(mApplication, + // Use PackageManager as the source of truth for application information, rather + // than the parceled ApplicationInfo provided by the app. + ApplicationInfo sanitizedApplication = + context.getPackageManager().getApplicationInfoAsUser( + mApplication.packageName, 0, + UserHandle.getUserId(mApplication.uid)); + Context applicationContext = context.createApplicationContext( + sanitizedApplication, Context.CONTEXT_RESTRICTED); // Get the correct apk paths while maintaining the current context's configuration. return applicationContext.createConfigurationContext( diff --git a/core/java/android/window/DesktopExperienceFlags.java b/core/java/android/window/DesktopExperienceFlags.java new file mode 100644 index 000000000000..0d1bb77ae8a2 --- /dev/null +++ b/core/java/android/window/DesktopExperienceFlags.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.window; + +import static com.android.server.display.feature.flags.Flags.enableDisplayContentModeManagement; + +import android.annotation.Nullable; +import android.os.SystemProperties; +import android.util.Log; + +import com.android.window.flags.Flags; + +import java.util.function.BooleanSupplier; + +/** + * Checks Desktop Experience flag state. + * + * <p>This enum provides a centralized way to control the behavior of flags related to desktop + * experience features which are aiming for developer preview before their release. It allows + * developer option to override the default behavior of these flags. + * + * <p>The flags here will be controlled by the {@code + * persist.wm.debug.desktop_experience_devopts} system property. + * + * <p>NOTE: Flags should only be added to this enum when they have received Product and UX alignment + * that the feature is ready for developer preview, otherwise just do a flag check. + * + * @hide + */ +public enum DesktopExperienceFlags { + ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT(() -> enableDisplayContentModeManagement(), true); + + /** + * Flag class, to be used in case the enum cannot be used because the flag is not accessible. + * + * <p>This class will still use the process-wide cache. + */ + public static class DesktopExperienceFlag { + // Function called to obtain aconfig flag value. + private final BooleanSupplier mFlagFunction; + // Whether the flag state should be affected by developer option. + private final boolean mShouldOverrideByDevOption; + + public DesktopExperienceFlag(BooleanSupplier flagFunction, boolean shouldOverrideByDevOption) { + this.mFlagFunction = flagFunction; + this.mShouldOverrideByDevOption = shouldOverrideByDevOption; + } + + /** + * Determines state of flag based on the actual flag and desktop experience developer option + * overrides. + */ + public boolean isTrue() { + return isFlagTrue(mFlagFunction, mShouldOverrideByDevOption); + } + } + + private static final String TAG = "DesktopExperienceFlags"; + // Function called to obtain aconfig flag value. + private final BooleanSupplier mFlagFunction; + // Whether the flag state should be affected by developer option. + private final boolean mShouldOverrideByDevOption; + + // Local cache for toggle override, which is initialized once on its first access. It needs to + // be refreshed only on reboots as overridden state is expected to take effect on reboots. + @Nullable private static Boolean sCachedToggleOverride; + + public static final String SYSTEM_PROPERTY_NAME = "persist.wm.debug.desktop_experience_devopts"; + + DesktopExperienceFlags(BooleanSupplier flagFunction, boolean shouldOverrideByDevOption) { + this.mFlagFunction = flagFunction; + this.mShouldOverrideByDevOption = shouldOverrideByDevOption; + } + + /** + * Determines state of flag based on the actual flag and desktop experience developer option + * overrides. + */ + public boolean isTrue() { + return isFlagTrue(mFlagFunction, mShouldOverrideByDevOption); + } + + private static boolean isFlagTrue( + BooleanSupplier flagFunction, boolean shouldOverrideByDevOption) { + if (shouldOverrideByDevOption + && Flags.showDesktopExperienceDevOption() + && getToggleOverride()) { + return true; + } + return flagFunction.getAsBoolean(); + } + + private static boolean getToggleOverride() { + // If cached, return it + if (sCachedToggleOverride != null) { + return sCachedToggleOverride; + } + + // Otherwise, fetch and cache it + boolean override = getToggleOverrideFromSystem(); + sCachedToggleOverride = override; + Log.d(TAG, "Toggle override initialized to: " + override); + return override; + } + + /** Returns the {@link ToggleOverride} from the system property.. */ + private static boolean getToggleOverrideFromSystem() { + return SystemProperties.getBoolean(SYSTEM_PROPERTY_NAME, false); + } +} diff --git a/core/java/android/window/DesktopModeFlags.java b/core/java/android/window/DesktopModeFlags.java index be69d3da3874..82055afda8c1 100644 --- a/core/java/android/window/DesktopModeFlags.java +++ b/core/java/android/window/DesktopModeFlags.java @@ -20,12 +20,13 @@ import android.annotation.Nullable; import android.app.ActivityThread; import android.app.Application; import android.content.ContentResolver; +import android.os.SystemProperties; import android.provider.Settings; import android.util.Log; import com.android.window.flags.Flags; -import java.util.function.Supplier; +import java.util.function.BooleanSupplier; /** * Checks desktop mode flag state. @@ -34,6 +35,10 @@ import java.util.function.Supplier; * windowing features which are aiming for developer preview before their release. It allows * developer option to override the default behavior of these flags. * + * <p> The flags here will be controlled by either {@link + * Settings.Global#DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES} or the {@code + * persyst.wm.debug.desktop_experience_devopts} system property. + * * <p>NOTE: Flags should only be added to this enum when they have received Product and UX * alignment that the feature is ready for developer preview, otherwise just do a flag check. * @@ -88,11 +93,40 @@ public enum DesktopModeFlags { ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX( Flags::enableDesktopAppLaunchTransitionsBugfix, false), INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC( - Flags::includeTopTransparentFullscreenTaskInDesktopHeuristic, true); + Flags::includeTopTransparentFullscreenTaskInDesktopHeuristic, true), + ENABLE_MINIMIZE_BUTTON(Flags::enableMinimizeButton, true), + ENABLE_RESIZING_METRICS(Flags::enableResizingMetrics, true), + ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS(Flags::enableTaskResizingKeyboardShortcuts, true); + + /** + * Flag class, to be used in case the enum cannot be used because the flag is not accessible. + * + * <p> This class will still use the process-wide cache. + */ + public static class DesktopModeFlag { + // Function called to obtain aconfig flag value. + private final BooleanSupplier mFlagFunction; + // Whether the flag state should be affected by developer option. + private final boolean mShouldOverrideByDevOption; + + public DesktopModeFlag(BooleanSupplier flagFunction, boolean shouldOverrideByDevOption) { + this.mFlagFunction = flagFunction; + this.mShouldOverrideByDevOption = shouldOverrideByDevOption; + } + + /** + * Determines state of flag based on the actual flag and desktop mode developer option + * or desktop experience developer option overrides. + */ + public boolean isTrue() { + return isFlagTrue(mFlagFunction, mShouldOverrideByDevOption); + } + + } - private static final String TAG = "DesktopModeFlagsUtil"; + private static final String TAG = "DesktopModeFlags"; // Function called to obtain aconfig flag value. - private final Supplier<Boolean> mFlagFunction; + private final BooleanSupplier mFlagFunction; // Whether the flag state should be affected by developer option. private final boolean mShouldOverrideByDevOption; @@ -100,7 +134,9 @@ public enum DesktopModeFlags { // be refreshed only on reboots as overridden state is expected to take effect on reboots. private static ToggleOverride sCachedToggleOverride; - DesktopModeFlags(Supplier<Boolean> flagFunction, boolean shouldOverrideByDevOption) { + public static final String SYSTEM_PROPERTY_NAME = "persist.wm.debug.desktop_experience_devopts"; + + DesktopModeFlags(BooleanSupplier flagFunction, boolean shouldOverrideByDevOption) { this.mFlagFunction = flagFunction; this.mShouldOverrideByDevOption = shouldOverrideByDevOption; } @@ -110,24 +146,42 @@ public enum DesktopModeFlags { * overrides. */ public boolean isTrue() { - Application application = ActivityThread.currentApplication(); - if (!Flags.showDesktopWindowingDevOption() - || !mShouldOverrideByDevOption - || application == null) { - return mFlagFunction.get(); - } else { + return isFlagTrue(mFlagFunction, mShouldOverrideByDevOption); + } + + private static boolean isFlagTrue(BooleanSupplier flagFunction, + boolean shouldOverrideByDevOption) { + if (!shouldOverrideByDevOption) return flagFunction.getAsBoolean(); + if (Flags.showDesktopExperienceDevOption()) { + return switch (getToggleOverride(null)) { + case OVERRIDE_UNSET, OVERRIDE_OFF -> flagFunction.getAsBoolean(); + case OVERRIDE_ON -> true; + }; + } + if (Flags.showDesktopWindowingDevOption()) { + Application application = ActivityThread.currentApplication(); + if (application == null) { + Log.w(TAG, "Could not get the current application."); + return flagFunction.getAsBoolean(); + } + ContentResolver contentResolver = application.getContentResolver(); + if (contentResolver == null) { + Log.w(TAG, "Could not get the content resolver for the application."); + return flagFunction.getAsBoolean(); + } boolean shouldToggleBeEnabledByDefault = Flags.enableDesktopWindowingMode(); - return switch (getToggleOverride(application.getContentResolver())) { - case OVERRIDE_UNSET -> mFlagFunction.get(); + return switch (getToggleOverride(contentResolver)) { + case OVERRIDE_UNSET -> flagFunction.getAsBoolean(); // When toggle override matches its default state, don't override flags. This // helps users reset their feature overrides. - case OVERRIDE_OFF -> !shouldToggleBeEnabledByDefault && mFlagFunction.get(); - case OVERRIDE_ON -> shouldToggleBeEnabledByDefault ? mFlagFunction.get() : true; + case OVERRIDE_OFF -> !shouldToggleBeEnabledByDefault && flagFunction.getAsBoolean(); + case OVERRIDE_ON -> !shouldToggleBeEnabledByDefault || flagFunction.getAsBoolean(); }; } + return flagFunction.getAsBoolean(); } - private ToggleOverride getToggleOverride(ContentResolver contentResolver) { + private static ToggleOverride getToggleOverride(@Nullable ContentResolver contentResolver) { // If cached, return it if (sCachedToggleOverride != null) { return sCachedToggleOverride; @@ -143,12 +197,21 @@ public enum DesktopModeFlags { /** * Returns {@link ToggleOverride} from Settings.Global set by toggle. */ - private ToggleOverride getToggleOverrideFromSystem(ContentResolver contentResolver) { - int settingValue = Settings.Global.getInt( - contentResolver, - Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES, - ToggleOverride.OVERRIDE_UNSET.getSetting() - ); + private static ToggleOverride getToggleOverrideFromSystem( + @Nullable ContentResolver contentResolver) { + int settingValue; + if (Flags.showDesktopExperienceDevOption()) { + settingValue = SystemProperties.getInt( + SYSTEM_PROPERTY_NAME, + ToggleOverride.OVERRIDE_UNSET.getSetting() + ); + } else { + settingValue = Settings.Global.getInt( + contentResolver, + Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES, + ToggleOverride.OVERRIDE_UNSET.getSetting() + ); + } return ToggleOverride.fromSetting(settingValue, ToggleOverride.OVERRIDE_UNSET); } diff --git a/core/java/android/window/OWNERS b/core/java/android/window/OWNERS index 77c99b98cf4a..82d37244dc70 100644 --- a/core/java/android/window/OWNERS +++ b/core/java/android/window/OWNERS @@ -3,3 +3,4 @@ set noparent include /services/core/java/com/android/server/wm/OWNERS per-file DesktopModeFlags.java = file:/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS +per-file DesktopExperienceFlags.java = file:/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig index b4e7675402b9..222088e8a8b9 100644 --- a/core/java/android/window/flags/lse_desktop_experience.aconfig +++ b/core/java/android/window/flags/lse_desktop_experience.aconfig @@ -555,4 +555,41 @@ flag { metadata { purpose: PURPOSE_BUGFIX } -}
\ No newline at end of file +} + +flag { + name: "show_desktop_experience_dev_option" + namespace: "lse_desktop_experience" + description: "Replace the freeform windowing dev options with a desktop experience one." + bug: "389092752" +} + +flag { + name: "enable_quickswitch_desktop_split_bugfix" + namespace: "lse_desktop_experience" + description: "Enables splitting QuickSwitch between fullscreen apps and Desktop workspaces." + bug: "345296916" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + name: "enable_desktop_windowing_exit_by_minimize_transition_bugfix" + namespace: "lse_desktop_experience" + description: "Enables exit desktop windowing by minimize transition & motion polish changes" + bug: "390161102" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + name: "enable_start_launch_transition_from_taskbar_bugfix" + namespace: "lse_desktop_experience" + description: "Enables starting a launch transition directly from the taskbar if desktop tasks are visible." + bug: "361366053" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig index c2e305d72dd0..9f6ea42c6fc4 100644 --- a/core/java/android/window/flags/windowing_frontend.aconfig +++ b/core/java/android/window/flags/windowing_frontend.aconfig @@ -180,6 +180,16 @@ flag { } flag { + name: "app_handle_no_relayout_on_exclusion_change" + namespace: "windowing_frontend" + description: "Remove unnecessary relayouts for app handle when exclusion regions change" + bug: "383672263" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "respect_non_top_visible_fixed_orientation" namespace: "windowing_frontend" description: "If top activity is not opaque, respect the fixed orientation of activity behind it" diff --git a/core/java/com/android/internal/protolog/ProcessedPerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/ProcessedPerfettoProtoLogImpl.java index e0a77d2be724..1f9df3cc842a 100644 --- a/core/java/com/android/internal/protolog/ProcessedPerfettoProtoLogImpl.java +++ b/core/java/com/android/internal/protolog/ProcessedPerfettoProtoLogImpl.java @@ -28,6 +28,7 @@ import com.android.internal.protolog.common.IProtoLogGroup; import java.io.FileInputStream; import java.io.FileNotFoundException; +import java.io.IOException; import java.util.ArrayList; public class ProcessedPerfettoProtoLogImpl extends PerfettoProtoLogImpl { @@ -161,15 +162,39 @@ public class ProcessedPerfettoProtoLogImpl extends PerfettoProtoLogImpl { messageString = message.getMessage(mViewerConfigReader); if (messageString == null) { - throw new RuntimeException("Failed to decode message for logcat. " - + "Message hash (" + message.getMessageHash() + ") either not available in " - + "viewerConfig file (" + mViewerConfigFilePath + ") or " - + "not loaded into memory from file before decoding."); + // Either we failed to load the config for this log message from the viewer config file + // into memory, or the message hash is simply not available in the viewer config file. + // We want to confirm that the message hash is not available in the viewer config file + // before throwing an exception. + throw new RuntimeException(getReasonForFailureToGetMessageString(message)); } return messageString; } + private String getReasonForFailureToGetMessageString(Message message) { + if (message.getMessageHash() == null) { + return "Trying to get message from null message hash"; + } + + try { + if (mViewerConfigReader.messageHashIsAvailableInFile(message.getMessageHash())) { + return "Failed to decode message for logcat logging. " + + "Message hash (" + message.getMessageHash() + ") is not available in " + + "viewerConfig file (" + mViewerConfigFilePath + "). This might be due " + + "to the viewer config file and the executing code being out of sync."; + } else { + return "Failed to decode message for logcat. " + + "Message hash (" + message.getMessageHash() + ") was available in the " + + "viewerConfig file (" + mViewerConfigFilePath + ") but wasn't loaded " + + "into memory from file before decoding! This is likely a bug."; + } + } catch (IOException e) { + return "Failed to get string message to log but could not identify the root cause due " + + "to an IO error in reading the viewer config file."; + } + } + private void loadLogcatGroupsViewerConfig(@NonNull IProtoLogGroup[] protoLogGroups) { final var groupsLoggingToLogcat = new ArrayList<String>(); for (IProtoLogGroup protoLogGroup : protoLogGroups) { diff --git a/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java b/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java index 524f64225084..f77179949fbf 100644 --- a/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java +++ b/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java @@ -100,6 +100,36 @@ public class ProtoLogViewerConfigReader { } } + /** + * Return whether or not the viewer config file contains a message with the specified hash. + * @param messageHash The hash message we are looking for in the viewer config file + * @return True iff the message with message hash is contained in the viewer config. + * @throws IOException if there was an issue reading the viewer config file. + */ + public boolean messageHashIsAvailableInFile(long messageHash) + throws IOException { + try (var pisWrapper = mViewerConfigInputStreamProvider.getInputStream()) { + final var pis = pisWrapper.get(); + while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + if (pis.getFieldNumber() == (int) MESSAGES) { + final long inMessageToken = pis.start(MESSAGES); + + while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + if (pis.getFieldNumber() == (int) MESSAGE_ID) { + if (pis.readLong(MESSAGE_ID) == messageHash) { + return true; + } + } + } + + pis.end(inMessageToken); + } + } + } + + return false; + } + @NonNull private Map<Long, String> loadViewerConfigMappingForGroup(@NonNull String group) throws IOException { diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl index 555374a05592..72cb9d1a20ac 100644 --- a/core/java/com/android/internal/statusbar/IStatusBar.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl @@ -51,6 +51,14 @@ oneway interface IStatusBar void showWirelessChargingAnimation(int batteryLevel); + /** + * Sets the new IME window status. + * + * @param displayId The id of the display to which the IME is bound. + * @param vis The IME window visibility. + * @param backDisposition The IME back disposition mode. + * @param showImeSwitcher Whether the IME Switcher button should be shown. + */ void setImeWindowStatus(int displayId, int vis, int backDisposition, boolean showImeSwitcher); void setWindowState(int display, int window, int state); @@ -214,6 +222,11 @@ oneway interface IStatusBar void onDisplayReady(int displayId); /** + * Notifies System UI that the system decorations should be removed from the display. + */ + void onDisplayRemoveSystemDecorations(int displayId); + + /** * Notifies System UI side of system bar attribute change on the specified display. * * @param displayId the ID of the display to notify. @@ -395,4 +408,7 @@ oneway interface IStatusBar * @param displayId the id of the current display. */ void moveFocusedTaskToDesktop(int displayId); + + /** Set whether the display should have a navigation bar. */ + void setHasNavigationBar(int displayId, boolean hasNavigationBar); } diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl index ec0954d5590a..1fa1e0bbc69a 100644 --- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl @@ -61,6 +61,14 @@ interface IStatusBarService void setIconVisibility(String slot, boolean visible); @UnsupportedAppUsage void removeIcon(String slot); + /** + * Sets the new IME window status. + * + * @param displayId The id of the display to which the IME is bound. + * @param vis The IME window visibility. + * @param backDisposition The IME back disposition mode. + * @param showImeSwitcher Whether the IME Switcher button should be shown. + */ void setImeWindowStatus(int displayId, int vis, int backDisposition, boolean showImeSwitcher); void expandSettingsPanel(String subPanel); diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index 74707703f5f2..3f9650773211 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -1055,7 +1055,7 @@ public class LockPatternUtils { } final int patternSize = pattern.size(); - byte[] res = new byte[patternSize]; + byte[] res = newNonMovableByteArray(patternSize); for (int i = 0; i < patternSize; i++) { LockPatternView.Cell cell = pattern.get(i); res[i] = (byte) (cell.getRow() * 3 + cell.getColumn() + '1'); diff --git a/core/java/com/android/internal/widget/LockscreenCredential.java b/core/java/com/android/internal/widget/LockscreenCredential.java index 92ce990c67df..2a12c986ab04 100644 --- a/core/java/com/android/internal/widget/LockscreenCredential.java +++ b/core/java/com/android/internal/widget/LockscreenCredential.java @@ -81,9 +81,9 @@ public class LockscreenCredential implements Parcelable, AutoCloseable { /** * Private constructor, use static builder methods instead. * - * <p> Builder methods should create a private copy of the credential bytes and pass in here. - * LockscreenCredential will only store the reference internally without copying. This is to - * minimize the number of extra copies introduced. + * <p> Builder methods should create a private copy of the credential bytes using a non-movable + * array and pass it in here. LockscreenCredential will only store the reference internally + * without copying. This is to minimize the number of extra copies introduced. */ private LockscreenCredential(int type, byte[] credential, boolean hasInvalidChars) { Objects.requireNonNull(credential); @@ -141,7 +141,7 @@ public class LockscreenCredential implements Parcelable, AutoCloseable { */ public static LockscreenCredential createUnifiedProfilePassword(@NonNull byte[] password) { return new LockscreenCredential(CREDENTIAL_TYPE_PASSWORD, - Arrays.copyOf(password, password.length), /* hasInvalidChars= */ false); + copyOfArrayNonMovable(password), /* hasInvalidChars= */ false); } /** @@ -237,7 +237,7 @@ public class LockscreenCredential implements Parcelable, AutoCloseable { /** Create a copy of the credential */ public LockscreenCredential duplicate() { return new LockscreenCredential(mType, - mCredential != null ? Arrays.copyOf(mCredential, mCredential.length) : null, + mCredential != null ? copyOfArrayNonMovable(mCredential) : null, mHasInvalidChars); } @@ -252,6 +252,15 @@ public class LockscreenCredential implements Parcelable, AutoCloseable { } /** + * Copies the given array into a new non-movable array. + */ + private static byte[] copyOfArrayNonMovable(byte[] array) { + byte[] copy = LockPatternUtils.newNonMovableByteArray(array.length); + System.arraycopy(array, 0, copy, 0, array.length); + return copy; + } + + /** * Checks whether the credential meets basic requirements for setting it as a new credential. * * This is redundant if {@link android.app.admin.PasswordMetrics#validateCredential()}, which @@ -440,7 +449,7 @@ public class LockscreenCredential implements Parcelable, AutoCloseable { * @return A byte array representing the input */ private static byte[] charsToBytesTruncating(CharSequence chars) { - byte[] bytes = new byte[chars.length()]; + byte[] bytes = LockPatternUtils.newNonMovableByteArray(chars.length()); for (int i = 0; i < chars.length(); i++) { bytes[i] = (byte) chars.charAt(i); } diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp index 639f5bff7614..91b25c2bda06 100644 --- a/core/jni/android_util_Binder.cpp +++ b/core/jni/android_util_Binder.cpp @@ -74,6 +74,7 @@ static struct bindernative_offsets_t jmethodID mExecTransact; jmethodID mGetInterfaceDescriptor; jmethodID mTransactionCallback; + jmethodID mGetExtension; // Object state. jfieldID mObject; @@ -489,8 +490,12 @@ public: if (mVintf) { ::android::internal::Stability::markVintf(b.get()); } - if (mExtension != nullptr) { - b.get()->setExtension(mExtension); + if (mSetExtensionCalled) { + jobject javaIBinderObject = env->CallObjectMethod(obj, gBinderOffsets.mGetExtension); + sp<IBinder> extensionFromJava = ibinderForJavaObject(env, javaIBinderObject); + if (extensionFromJava != nullptr) { + b.get()->setExtension(extensionFromJava); + } } mBinder = b; ALOGV("Creating JavaBinder %p (refs %p) for Object %p, weakCount=%" PRId32 "\n", @@ -516,21 +521,12 @@ public: mVintf = false; } - sp<IBinder> getExtension() { - AutoMutex _l(mLock); - sp<JavaBBinder> b = mBinder.promote(); - if (b != nullptr) { - return b.get()->getExtension(); - } - return mExtension; - } - void setExtension(const sp<IBinder>& extension) { AutoMutex _l(mLock); - mExtension = extension; + mSetExtensionCalled = true; sp<JavaBBinder> b = mBinder.promote(); if (b != nullptr) { - b.get()->setExtension(mExtension); + b.get()->setExtension(extension); } } @@ -542,8 +538,7 @@ private: // is too much binder state here, we can think about making JavaBBinder an // sp here (avoid recreating it) bool mVintf = false; - - sp<IBinder> mExtension; + bool mSetExtensionCalled = false; }; // ---------------------------------------------------------------------------- @@ -1249,10 +1244,6 @@ static void android_os_Binder_blockUntilThreadAvailable(JNIEnv* env, jobject cla return IPCThreadState::self()->blockUntilThreadAvailable(); } -static jobject android_os_Binder_getExtension(JNIEnv* env, jobject obj) { - JavaBBinderHolder* jbh = (JavaBBinderHolder*) env->GetLongField(obj, gBinderOffsets.mObject); - return javaObjectForIBinder(env, jbh->getExtension()); -} static void android_os_Binder_setExtension(JNIEnv* env, jobject obj, jobject extensionObject) { JavaBBinderHolder* jbh = (JavaBBinderHolder*) env->GetLongField(obj, gBinderOffsets.mObject); @@ -1295,8 +1286,7 @@ static const JNINativeMethod gBinderMethods[] = { { "getNativeBBinderHolder", "()J", (void*)android_os_Binder_getNativeBBinderHolder }, { "getNativeFinalizer", "()J", (void*)android_os_Binder_getNativeFinalizer }, { "blockUntilThreadAvailable", "()V", (void*)android_os_Binder_blockUntilThreadAvailable }, - { "getExtension", "()Landroid/os/IBinder;", (void*)android_os_Binder_getExtension }, - { "setExtension", "(Landroid/os/IBinder;)V", (void*)android_os_Binder_setExtension }, + { "setExtensionNative", "(Landroid/os/IBinder;)V", (void*)android_os_Binder_setExtension }, }; // clang-format on @@ -1313,6 +1303,8 @@ static int int_register_android_os_Binder(JNIEnv* env) gBinderOffsets.mTransactionCallback = GetStaticMethodIDOrDie(env, clazz, "transactionCallback", "(IIII)V"); gBinderOffsets.mObject = GetFieldIDOrDie(env, clazz, "mObject", "J"); + gBinderOffsets.mGetExtension = GetMethodIDOrDie(env, clazz, "getExtension", + "()Landroid/os/IBinder;"); return RegisterMethodsOrDie( env, kBinderPathName, diff --git a/core/res/res/color-night/surface_effect_0_color.xml b/core/res/res/color-night/surface_effect_0_color.xml new file mode 100644 index 000000000000..f71ed46cc014 --- /dev/null +++ b/core/res/res/color-night/surface_effect_0_color.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:color="@android:color/system_accent1_800" + android:alpha="0.5"/> +</selector> diff --git a/core/res/res/color-night/surface_effect_1_color.xml b/core/res/res/color-night/surface_effect_1_color.xml new file mode 100644 index 000000000000..80b95ae31d1c --- /dev/null +++ b/core/res/res/color-night/surface_effect_1_color.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:color="@android:color/system_neutral1_500" + android:lStar="6" android:alpha="0.54"/> +</selector> diff --git a/core/res/res/color/resolver_profile_tab_text.xml b/core/res/res/color/resolver_profile_tab_text.xml index 6b8c42d70cdf..13bdd14e0280 100644 --- a/core/res/res/color/resolver_profile_tab_text.xml +++ b/core/res/res/color/resolver_profile_tab_text.xml @@ -13,8 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. --> -<selector xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"> - <item android:color="?androidprv:attr/textColorPrimary" android:state_selected="true"/> - <item android:color="?androidprv:attr/textColorSecondary"/> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:color="@color/materialColorOnPrimary" android:state_selected="true"/> + <item android:color="@color/materialColorOnSurface"/> </selector> diff --git a/core/res/res/color/surface_effect_0_color.xml b/core/res/res/color/surface_effect_0_color.xml new file mode 100644 index 000000000000..b6a607c6ee5e --- /dev/null +++ b/core/res/res/color/surface_effect_0_color.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:color="@android:color/system_accent1_100" + android:alpha="0.5"/> +</selector> diff --git a/core/res/res/color/surface_effect_1_color.xml b/core/res/res/color/surface_effect_1_color.xml new file mode 100644 index 000000000000..332130ae6f91 --- /dev/null +++ b/core/res/res/color/surface_effect_1_color.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:color="@android:color/system_neutral1_500" + android:lStar="98" android:alpha="0.54"/> +</selector> diff --git a/core/res/res/drawable/chooser_row_layer_list.xml b/core/res/res/drawable/chooser_row_layer_list.xml index f5ba1e9d633b..3361fccb70a0 100644 --- a/core/res/res/drawable/chooser_row_layer_list.xml +++ b/core/res/res/drawable/chooser_row_layer_list.xml @@ -19,7 +19,7 @@ <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <item> <shape android:shape="rectangle"> - <solid android:color="?android:attr/colorAccentSecondary"/> + <solid android:color="@color/materialColorSecondary"/> <size android:width="128dp" android:height="2dp"/> <corners android:radius="2dp" /> </shape> diff --git a/core/res/res/drawable/inset_resolver_profile_tab_bg.xml b/core/res/res/drawable/inset_resolver_profile_tab_bg.xml new file mode 100644 index 000000000000..8c7b2c09e0f6 --- /dev/null +++ b/core/res/res/drawable/inset_resolver_profile_tab_bg.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ https://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<inset xmlns:android="http://schemas.android.com/apk/res/android" + android:drawable="@drawable/resolver_profile_tab_bg" + android:insetLeft="0dp" + android:insetRight="0dp" + android:insetTop="6dp" + android:insetBottom="6dp" /> diff --git a/core/res/res/drawable/resolver_profile_tab_bg.xml b/core/res/res/drawable/resolver_profile_tab_bg.xml index bc9634580007..e36ccc692c6b 100644 --- a/core/res/res/drawable/resolver_profile_tab_bg.xml +++ b/core/res/res/drawable/resolver_profile_tab_bg.xml @@ -29,14 +29,14 @@ <item android:state_selected="false"> <shape android:shape="rectangle"> <corners android:radius="12dp" /> - <solid android:color="?androidprv:attr/colorSurface" /> + <solid android:color="@color/materialColorSurfaceBright" /> </shape> </item> <item android:state_selected="true"> <shape android:shape="rectangle"> <corners android:radius="12dp" /> - <solid android:color="@color/resolver_profile_tab_selected_bg" /> + <solid android:color="@android:color/materialColorPrimary" /> </shape> </item> </selector> diff --git a/core/res/res/layout/resolver_profile_tab_button.xml b/core/res/res/layout/resolver_profile_tab_button.xml index fd168e6414f1..7404dc33be7f 100644 --- a/core/res/res/layout/resolver_profile_tab_button.xml +++ b/core/res/res/layout/resolver_profile_tab_button.xml @@ -18,11 +18,10 @@ <Button xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="0dp" - android:layout_height="36dp" + android:layout_height="48dp" android:layout_weight="1" - android:layout_marginVertical="6dp" android:layout_marginHorizontal="@dimen/resolver_profile_tab_margin" - android:background="@drawable/resolver_profile_tab_bg" + android:background="@drawable/inset_resolver_profile_tab_bg" android:textColor="@color/resolver_profile_tab_text" android:textSize="@dimen/resolver_tab_text_size" android:textAppearance="@android:style/TextAppearance.DeviceDefault.DialogWindowTitle" diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml index 59414771c36c..1c7ce89dc0ad 100644 --- a/core/res/res/values-af/strings.xml +++ b/core/res/res/values-af/strings.xml @@ -2459,8 +2459,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Appinhoud is weens sekuriteit van skermdeling verberg"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Outomaties aan satelliet gekoppel"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"Jy kan boodskappe stuur en ontvang sonder ’n selfoon- of wi-fi-netwerk"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"Jy kan boodskappe stuur en ontvang en beperkte data via satelliet gebruik"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"Gebruik satellietboodskappe?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"Stuur en ontvang boodskappe sonder ’n selfoon- of wi-fi-netwerk"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"Maak Boodskappe oop"</string> diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml index 0ca5ab4a338d..0f5fee4cf9da 100644 --- a/core/res/res/values-am/strings.xml +++ b/core/res/res/values-am/strings.xml @@ -2459,8 +2459,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"ለደኅንነት ሲባል የመተግበሪያ ይዘት ከማያ ገጽ ማጋራት ተደብቋል"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"ከሳተላይት ጋር በራስ-ሰር ተገናኝቷል"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"ያለ ሞባይል ወይም የWi-Fi አውታረ መረብ መልዕክቶችን መላክ እና መቀበል ይችላሉ"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"በሳተላይት መልዕክቶችን መላክ እና መቀበል እና የተገደበ ውሂብ መጠቀም ይችላሉ"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"የሳተላይት መልዕክት መላላክን ይጠቀማሉ?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"ያለ ሞባይል ወይም የWi-Fi አውታረ መረብ መልዕክቶችን ይላኩ እና ይቀበሉ"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"መልዕክቶች ይክፈቱ"</string> diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml index cb765e71b801..1fdbe8c35f3e 100644 --- a/core/res/res/values-ar/strings.xml +++ b/core/res/res/values-ar/strings.xml @@ -2463,8 +2463,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"تم إخفاء محتوى التطبيق بعد تفعيل ميزة \"مشاركة الشاشة\" للحفاظ على أمانك"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"تم الاتصال تلقائيًا بالقمر الصناعي"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"يمكنك إرسال الرسائل واستلامها بدون شبكة الجوّال أو شبكة Wi-Fi."</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"يمكنك إرسال الرسائل وتلقّيها واستخدام بيانات محدودة عبر القمر الصناعي"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"هل تريد المراسلة عبر القمر الصناعي؟"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"يمكنك إرسال الرسائل واستلامها بدون شبكة الجوّال أو شبكة Wi-Fi"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"فتح تطبيق \"الرسائل\""</string> diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml index 0a4597ec589e..9084407b1c6d 100644 --- a/core/res/res/values-az/strings.xml +++ b/core/res/res/values-az/strings.xml @@ -2459,8 +2459,7 @@ <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> <string name="satellite_notification_title" msgid="4026338973463121526">"Peykə avtomatik qoşulub"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"Mobil və ya Wi-Fi şəbəkəsi olmadan mesaj göndərə və qəbul edə bilərsiniz"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"Peyk vasitəsilə mesaj göndərə və qəbul edə, eləcə də məhdud datadan istifadə edə bilərsiniz"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"Peyk mesajlaşmasından istifadə edilsin?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"Mobil və ya Wi-Fi şəbəkəsi olmadan mesajlar göndərin və qəbul edin"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"Mesajı açın"</string> diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml index 5fb744ee2fa9..2abb7f4da9af 100644 --- a/core/res/res/values-b+sr+Latn/strings.xml +++ b/core/res/res/values-b+sr+Latn/strings.xml @@ -2460,8 +2460,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Sadržaj aplikacije je skriven za deljenje sadržaja ekrana zbog bezbednosti"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Automatski povezano sa satelitom"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"Možete da šaljete i primate poruke bez mobilne ili WiFi mreže"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"Možete da šaljete i primate poruke, kao i da koristite ograničenu količinu podataka preko satelita"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"Želite da koristite satelitsku razmenu poruka?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"Šaljite i primajte poruke bez mobilne ili WiFi mreže"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"Otvori Messages"</string> diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml index 74c0b46a8045..7f57c8747e23 100644 --- a/core/res/res/values-be/strings.xml +++ b/core/res/res/values-be/strings.xml @@ -2461,8 +2461,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Змесціва праграмы выключана з абагульвання экрана ў мэтах бяспекі"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Аўтаматычна падключана да сістэм спадарожнікавай сувязі"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"Вы можаце адпраўляць і атрымліваць паведамленні без доступу да мабільнай сеткі або Wi-Fi"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"З дапамогай спадарожнікавай сувязі вы можаце адпраўляць і атрымліваць паведамленні, а таксама выкарыстоўваць абмежаваную колькасць мабільных даных"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"Выкарыстоўваць абмен паведамленнямі па спадарожнікавай сувязі?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"Вы можаце адпраўляць і атрымліваць паведамленні, калі падключэнне да мабільнай сеткі або сеткі Wi-Fi адсутнічае"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"Адкрыць Паведамленні"</string> diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml index b857fb52b418..6bff361516e5 100644 --- a/core/res/res/values-bg/strings.xml +++ b/core/res/res/values-bg/strings.xml @@ -2459,8 +2459,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Съдържанието на приложението е скрито от функцията за споделяне на екрана от съображения за сигурност"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Автоматично установена връзка със сателит"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"Можете да изпращате и получавате съобщения без мобилна или Wi-Fi мрежа"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"Можете да изпращате и получавате съобщения и да използвате ограничени данни чрез сателит"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"Да се използват ли сателитни съобщения?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"Изпращайте и получавайте съобщения без мобилна или Wi-Fi мрежа"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"Отваряне на Messages"</string> diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml index 8da67849c84d..82d1da312905 100644 --- a/core/res/res/values-bn/strings.xml +++ b/core/res/res/values-bn/strings.xml @@ -2459,8 +2459,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"নিরাপত্তার জন্য স্ক্রিন শেয়ার করা থেকে লুকানো অ্যাপের কন্টেন্ট"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"স্যাটেলাইটের সাথে অটোমেটিক কানেক্ট করা হয়েছে"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"আপনি কোনও মেবাইল বা ওয়াই-ফাই নেটওয়ার্ক ছাড়াই মেসেজ পাঠাতে ও পেতে পারবেন"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"আপনি স্যাটেলাইটের মাধ্যমে মেসেজ পাঠাতে ও পেতে এবং সীমিত ডেটা ব্যবহার করতে পারেন"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"\'স্যাটেলাইট মেসেজিং\' ব্যবহার করবেন?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"কোনও মেবাইল বা ওয়াই-ফাই নেটওয়ার্ক ছাড়াই মেসেজ পাঠান ও রিসিভ করুন"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages খুলুন"</string> diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml index 639972915f76..d055f762858b 100644 --- a/core/res/res/values-bs/strings.xml +++ b/core/res/res/values-bs/strings.xml @@ -2460,8 +2460,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Sadržaj aplikacije je sakriven od dijeljenja ekrana radi sigurnosti"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Automatski je povezano sa satelitom"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"Možete slati i primati poruke bez mobilne ili WiFi mreže"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"Možete slati i primati poruke te koristiti ograničeni prenos podataka putem satelita"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"Koristiti satelitsku razmjenu poruka?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"Šaljite i primajte poruke bez mobilne ili WiFi mreže"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"Otvorite Messages"</string> diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml index 49d80c25cbfc..2b0e6fca8caa 100644 --- a/core/res/res/values-ca/strings.xml +++ b/core/res/res/values-ca/strings.xml @@ -2460,8 +2460,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Contingut de l\'aplicació amagat de la compartició de pantalla per seguretat"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"S\'ha connectat automàticament a un satèl·lit"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"Pots enviar i rebre missatges sense una xarxa mòbil o Wi‑Fi"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"Pots enviar i rebre missatges i utilitzar dades limitades per satèl·lit"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"Vols utilitzar els missatges per satèl·lit?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"Envia i rep missatges sense una xarxa mòbil o Wi‑Fi"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"Obre Missatges"</string> diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml index 8b45d72f9131..e8f75f3b957f 100644 --- a/core/res/res/values-cs/strings.xml +++ b/core/res/res/values-cs/strings.xml @@ -304,8 +304,7 @@ <string name="notification_channel_network_alerts" msgid="6312366315654526528">"Síťová upozornění"</string> <string name="notification_channel_network_available" msgid="6083697929214165169">"K dispozici je síť"</string> <string name="notification_channel_vpn" msgid="1628529026203808999">"Stav sítě VPN"</string> - <!-- no translation found for notification_channel_system_time (1660313368058030441) --> - <skip /> + <string name="notification_channel_system_time" msgid="1660313368058030441">"Čas a časová pásma"</string> <string name="notification_channel_device_admin" msgid="6384932669406095506">"Upozornění od vašeho administrátora IT"</string> <string name="notification_channel_alerts" msgid="5070241039583668427">"Upozornění"</string> <string name="notification_channel_retail_mode" msgid="3732239154256431213">"Prodejní ukázka"</string> @@ -313,8 +312,7 @@ <string name="notification_channel_heavy_weight_app" msgid="17455756500828043">"Aplikace je spuštěna"</string> <string name="notification_channel_foreground_service" msgid="7102189948158885178">"Aplikace spotřebovávají baterii"</string> <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Zvětšení"</string> - <!-- no translation found for notification_channel_accessibility_hearing_device (7816963856388758952) --> - <skip /> + <string name="notification_channel_accessibility_hearing_device" msgid="7816963856388758952">"Naslouchátko"</string> <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Využití přístupnosti"</string> <string name="notification_channel_display" msgid="6905032605735615090">"Displej"</string> <string name="foreground_service_app_in_background" msgid="1439289699671273555">"Aplikace <xliff:g id="APP_NAME">%1$s</xliff:g> využívá baterii"</string> @@ -1412,10 +1410,8 @@ <string name="install_carrier_app_notification_button" msgid="6257740533102594290">"Stáhnout aplikaci"</string> <string name="carrier_app_notification_title" msgid="5815477368072060250">"Byla vložena nová SIM karta"</string> <string name="carrier_app_notification_text" msgid="6567057546341958637">"Klepnutím zahájíte nastavení"</string> - <!-- no translation found for time_zone_change_notification_title (5232503069219193218) --> - <skip /> - <!-- no translation found for time_zone_change_notification_body (6135793674904665585) --> - <skip /> + <string name="time_zone_change_notification_title" msgid="5232503069219193218">"Vaše časové pásmo se změnilo"</string> + <string name="time_zone_change_notification_body" msgid="6135793674904665585">"Jste v časovém pásmu <xliff:g id="TIME_ZONE_DISPLAY_NAME">%1$s</xliff:g> (<xliff:g id="TIME_ZONE_OFFSET">%2$s</xliff:g>)"</string> <string name="time_picker_dialog_title" msgid="9053376764985220821">"Nastavit čas"</string> <string name="date_picker_dialog_title" msgid="5030520449243071926">"Nastavení data"</string> <string name="date_time_set" msgid="4603445265164486816">"Nastavit"</string> @@ -1803,18 +1799,12 @@ <string name="accessibility_gesture_instructional_text" msgid="4133877896011098550">"Funkce se otevře při příštím použití této zkratky. Přejeďte dvěma prsty nahoru ze spodní části obrazovky a rychle je zvedněte."</string> <string name="accessibility_gesture_3finger_instructional_text" msgid="1124458279366968154">"Funkce se otevře při příštím použití této zkratky. Přejeďte třemi prsty nahoru ze spodní části obrazovky a rychle je zvedněte."</string> <string name="accessibility_magnification_chooser_text" msgid="1502075582164931596">"Zvětšení"</string> - <!-- no translation found for hearing_device_switch_phone_mic_notification_title (6645178038359708836) --> - <skip /> - <!-- no translation found for hearing_device_switch_hearing_mic_notification_title (4612074852145289569) --> - <skip /> - <!-- no translation found for hearing_device_switch_phone_mic_notification_text (1332426273666077412) --> - <skip /> - <!-- no translation found for hearing_device_switch_hearing_mic_notification_text (8288368365767284208) --> - <skip /> - <!-- no translation found for hearing_device_notification_switch_button (3619524619430941300) --> - <skip /> - <!-- no translation found for hearing_device_notification_settings_button (6673651052880279178) --> - <skip /> + <string name="hearing_device_switch_phone_mic_notification_title" msgid="6645178038359708836">"Přepnout na mikrofon telefonu?"</string> + <string name="hearing_device_switch_hearing_mic_notification_title" msgid="4612074852145289569">"Přepnout na mikrofon naslouchátka?"</string> + <string name="hearing_device_switch_phone_mic_notification_text" msgid="1332426273666077412">"Pro lepší zvuk, nebo pokud je baterie naslouchátka téměř vybitá. Mikrofon se přepne jen během hovoru."</string> + <string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"K handsfree telefonování můžete použít mikrofon naslouchátka. Mikrofon se přepne jen během hovoru."</string> + <string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Přepnout"</string> + <string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Nastavení"</string> <string name="user_switched" msgid="7249833311585228097">"Aktuální uživatel je <xliff:g id="NAME">%1$s</xliff:g>."</string> <string name="user_switching_message" msgid="1912993630661332336">"Přepínání na uživatele <xliff:g id="NAME">%1$s</xliff:g>…"</string> <string name="user_logging_out_message" msgid="7216437629179710359">"Odhlašování uživatele <xliff:g id="NAME">%1$s</xliff:g>…"</string> @@ -2471,8 +2461,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Obsah aplikace je z bezpečnostních důvodů při sdílení obrazovky skryt"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Automaticky připojeno k satelitu"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"Zprávy můžete odesílat a přijímat bez mobilní sítě nebo sítě Wi-Fi"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"Můžete dostávat a odesílat zprávy a používat omezená data přes satelit"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"Použít satelitní zprávy?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"Odesílejte a přijímejte zprávy bez mobilní sítě nebo Wi-Fi"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"Otevřít Zprávy"</string> diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml index edd910c9fe6d..288053ee1632 100644 --- a/core/res/res/values-da/strings.xml +++ b/core/res/res/values-da/strings.xml @@ -2459,8 +2459,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Af sikkerhedsmæssige årsager vises appindhold ikke ved skærmdeling"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Der blev automatisk oprettet forbindelse til satellit"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"Du kan sende og modtage beskeder uden et mobil- eller Wi-Fi-netværk"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"Du kan sende og modtage beskeder samt bruge en begrænset mængde data via satellit"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"Vil du bruge satellitbeskeder?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"Send og modtag beskeder uden et mobil- eller Wi-Fi-netværk"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"Åbn Beskeder"</string> diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml index 5262da98d277..10659eb7e42e 100644 --- a/core/res/res/values-de/strings.xml +++ b/core/res/res/values-de/strings.xml @@ -2459,8 +2459,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Aus Sicherheitsgründen werden bei der Bildschirmfreigabe App-Inhalte ausgeblendet"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Automatisch mit Satellit verbunden"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"Du kannst Nachrichten ohne Mobilfunknetz oder WLAN senden und empfangen"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"Du kannst Nachrichten senden und empfangen und begrenzte Datenmengen per Satellit nutzen"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"Nachrichten per Satellit verwenden?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"Du kannst ohne Mobilgerät oder WLAN Nachrichten senden und empfangen"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages öffnen"</string> diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml index 1d194f4e3b22..77a13ed65700 100644 --- a/core/res/res/values-el/strings.xml +++ b/core/res/res/values-el/strings.xml @@ -2459,8 +2459,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Για λόγους ασφάλειας, έγινε απόκρυψη του περιεχομένου της εφαρμογής από την κοινή χρήση οθόνης"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Συνδέθηκε αυτόματα με δορυφόρο"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"Μπορείτε να στέλνετε και να λαμβάνετε μηνύματα χωρίς δίκτυο κινητής τηλεφωνίας ή Wi-Fi."</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"Μπορείτε να στέλνετε και να λαμβάνετε μηνύματα και να χρησιμοποιείτε περιορισμένα δεδομένα μέσω δορυφόρου"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"Χρήση της ανταλλαγής μηνυμάτων μέσω δορυφόρου;"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"Αποστολή και λήψη μηνυμάτων χωρίς δίκτυο κινητής τηλεφωνίας ή Wi-Fi"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"Άνοιγμα Messages"</string> diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml index 75432f8b59a6..667f978701f9 100644 --- a/core/res/res/values-en-rAU/strings.xml +++ b/core/res/res/values-en-rAU/strings.xml @@ -2459,8 +2459,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"App content hidden from screen share for security"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Auto-connected to satellite"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"You can send and receive messages without a mobile or Wi-Fi network"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"You can send and receive messages and use limited data by satellite"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"Use satellite messaging?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"Send and receive messages without a mobile or Wi-Fi network"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"Open Messages"</string> diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml index 7d642b71028d..4dcece1eb448 100644 --- a/core/res/res/values-en-rGB/strings.xml +++ b/core/res/res/values-en-rGB/strings.xml @@ -2459,8 +2459,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"App content hidden from screen share for security"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Auto-connected to satellite"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"You can send and receive messages without a mobile or Wi-Fi network"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"You can send and receive messages and use limited data by satellite"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"Use satellite messaging?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"Send and receive messages without a mobile or Wi-Fi network"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"Open Messages"</string> diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml index dd8e69550fff..a1f6f7615aba 100644 --- a/core/res/res/values-en-rIN/strings.xml +++ b/core/res/res/values-en-rIN/strings.xml @@ -2459,8 +2459,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"App content hidden from screen share for security"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Auto-connected to satellite"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"You can send and receive messages without a mobile or Wi-Fi network"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"You can send and receive messages and use limited data by satellite"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"Use satellite messaging?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"Send and receive messages without a mobile or Wi-Fi network"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"Open Messages"</string> diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml index 3451d58fe747..f34aa97b424a 100644 --- a/core/res/res/values-es-rUS/strings.xml +++ b/core/res/res/values-es-rUS/strings.xml @@ -2460,8 +2460,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Se ocultó el contenido de la app durante el uso compartido de la pantalla por motivos de seguridad"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Conexión automática a satélite"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"Puedes enviar y recibir mensajes incluso si no tienes conexión a una red móvil o Wi-Fi"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"Puedes enviar y recibir mensajes, y usar datos limitados por satélite"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"¿Quieres usar la mensajería satelital?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"Envía y recibe mensajes sin una red móvil ni Wi-Fi"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"Abrir Mensajes"</string> diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml index 7b9dd4675800..612d86f14ef8 100644 --- a/core/res/res/values-es/strings.xml +++ b/core/res/res/values-es/strings.xml @@ -2460,8 +2460,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Contenido de la aplicación oculto en pantalla compartida por seguridad"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Conectado automáticamente al satélite"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"Puedes enviar y recibir mensajes sin una red móvil o Wi-Fi"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"Puedes enviar y recibir mensajes y usar datos limitados por satélite"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"¿Usar mensajes por satélite?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"Envía y recibe mensajes sin una red móvil ni Wi-Fi"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"Abre Mensajes"</string> diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml index cf875408ed14..6d5448908b02 100644 --- a/core/res/res/values-et/strings.xml +++ b/core/res/res/values-et/strings.xml @@ -2459,8 +2459,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Rakenduse sisu on ekraani jagamisel turvalisuse huvides peidetud"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Satelliidiga loodi automaatselt ühendus"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"Teil on võimalik sõnumeid saata ja vastu võtta ilma mobiilside- ja WiFi-võrguta"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"Saate sõnumeid vahetada ja kasutada piiratud andmeid satelliidi kaudu"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"Kas soovite kasutada satelliidipõhist sõnumsidet?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"Sõnumite saatmine ja vastuvõtmine ilma mobiilside- või WiFi-võrguta"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"Ava rakendus Messages"</string> diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml index 8e689594ea2a..9f62847bb710 100644 --- a/core/res/res/values-eu/strings.xml +++ b/core/res/res/values-eu/strings.xml @@ -2459,8 +2459,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Aplikazioko edukia ezkutatu egin da pantaila partekatzeko eginbidetik, segurtasuna bermatzeko"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Automatikoki konektatu da satelitera"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"Mezuak bidal eta jaso ditzakezu sare mugikorrik edo wifi-sarerik gabe"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"Satelite bidez mezuak bidali eta jasotzeko nahiz datuak muga batekin erabiltzeko aukera duzu"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"Satelite bidezko mezularitza erabili nahi duzu?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"Bidali eta jaso mezuak sare mugikorrik edo wifi-sarerik gabe"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"Ireki Mezuak"</string> diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml index 9f2c7bb43c69..e8e0f48a966e 100644 --- a/core/res/res/values-fa/strings.xml +++ b/core/res/res/values-fa/strings.xml @@ -169,7 +169,7 @@ <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"درحالحاضر تماسها، پیامها، و دادهها هنگام استفاده از سیمکارت <xliff:g id="NETWORK_NAME">%1$s</xliff:g> آسیبپذیرتر هستند.\n\nوقتی اتصال شما دوباره رمزگذاری شود، اعلان دیگری دریافت خواهید کرد."</string> <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"تنظیمات امنیت شبکه تلفن همراه"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"بیشتر بدانید"</string> - <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"متوجهام"</string> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"متوجهم"</string> <string name="fcComplete" msgid="1080909484660507044">"کد ویژگی کامل شد."</string> <string name="fcError" msgid="5325116502080221346">"مشکل در اتصال یا کد ویژگی نامعتبر."</string> <string name="httpErrorOk" msgid="6206751415788256357">"تأیید"</string> @@ -2459,8 +2459,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"بهدلایل امنیتی، محتوای برنامه پساز همرسانی صفحهنمایش پنهان میشود"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"بهطور خودکار به ماهواره متصل شد"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"میتوانید بدون شبکه تلفن همراه یا Wi-Fi پیام ارسال و دریافت کنید"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"ازطریق ماهواره میتوانید پیام ارسال و دریافت کنید و از دادههای محدود استفاده کنید"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"از «پیامرسانی ماهوارهای» استفاده شود؟"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"ارسال و دریافت پیام بدون شبکه تلفن همراه یا Wi-Fi"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"باز کردن «پیامنگار»"</string> diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml index 0b65ee2c5403..5da5844cd9c3 100644 --- a/core/res/res/values-fi/strings.xml +++ b/core/res/res/values-fi/strings.xml @@ -2459,8 +2459,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Sovelluksen sisältö piilotettu näytön jakamiselta turvallisuussyistä"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Yhdistetty automaattisesti satelliittiin"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"Voit lähettää ja vastaanottaa viestejä ilman mobiili‑ tai Wi-Fi-verkkoa"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"Voit lähettää ja vastaanottaa viestejä ja käyttää rajoitetusti dataa satelliitin kautta"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"Käytetäänkö satelliittiviestintää?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"Lähetä ja vastaanota viestejä ilman mobiili- tai Wi-Fi-verkkoa"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"Avaa Messages"</string> diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml index e9331bb826c4..9d81f4f978bd 100644 --- a/core/res/res/values-fr-rCA/strings.xml +++ b/core/res/res/values-fr-rCA/strings.xml @@ -2460,8 +2460,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Le contenu de l\'appli est masqué du Partage d\'écran par mesure de sécurité"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Connecté au satellite automatiquement"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"Vous pouvez envoyer et recevoir des messages sans avoir recours à un appareil mobile ou à un réseau Wi-Fi"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"Vous pouvez envoyer et recevoir des messages, et utiliser des données limitées par satellite"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"Utiliser la messagerie par satellite?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"Envoyez et recevez des messages sans réseau cellulaire ou Wi-Fi"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"Ouvrir Messages"</string> diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml index cbcc961f309a..9ef2758b4eee 100644 --- a/core/res/res/values-fr/strings.xml +++ b/core/res/res/values-fr/strings.xml @@ -2460,8 +2460,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Le contenu de l\'appli est masqué lors du partage d\'écran par mesure de sécurité"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Connecté automatiquement au réseau satellite"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"Vous pouvez envoyer et recevoir des messages sans connexion au réseau mobile ou Wi-Fi"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"Vous pouvez envoyer ou recevoir des messages et utiliser une certaine quantité de données par satellite"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"Utiliser la messagerie par satellite ?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"Envoyer et recevoir des messages sans réseau mobile ou Wi-Fi"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"Ouvrir Messages"</string> diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml index 25aac2cd0615..d775f448d2ce 100644 --- a/core/res/res/values-gl/strings.xml +++ b/core/res/res/values-gl/strings.xml @@ -2459,8 +2459,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Por seguranza, ocultouse o contido da aplicación na pantalla compartida"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Conexión automática ao satélite"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"Podes enviar e recibir mensaxes sen unha rede de telefonía móbil ou wifi"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"Podes enviar e recibir mensaxes, ademais de usar datos limitados por satélite"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"Queres usar a mensaxaría por satélite?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"Envía e recibe mensaxes sen ter acceso a redes de telefonía móbil ou wifi"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"Abrir Mensaxes"</string> diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml index c38768dc3e30..f4d6099fffe8 100644 --- a/core/res/res/values-gu/strings.xml +++ b/core/res/res/values-gu/strings.xml @@ -2459,8 +2459,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"સુરક્ષા માટે સ્ક્રીન શેર કરતી વખતે ઍપનું કન્ટેન્ટ છુપાવેલું છે"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"સેટેલાઇટ સાથે ઑટોમૅટિક રીતે કનેક્ટેડ"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"તમે મોબાઇલ અથવા વાઇ-ફાઇ નેટવર્ક વિના મેસેજ મોકલી અને પ્રાપ્ત કરી શકો છો"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"તમે મેસેજ મોકલી અને પ્રાપ્ત કરી શકો છો અને સૅટલાઇટ દ્વારા મર્યાદિત ડેટાનો ઉપયોગ કરી શકો છો"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"શું સૅટલાઇટ મેસેજિંગનો ઉપયોગ કરીએ?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"મોબાઇલ કે વાઇ-ફાઇ નેટવર્ક વિના મેસેજ મોકલો અને મેળવો"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages ખોલો"</string> diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml index 6199c806d5e2..d750b39a0e41 100644 --- a/core/res/res/values-hi/strings.xml +++ b/core/res/res/values-hi/strings.xml @@ -291,7 +291,7 @@ <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> <string name="notification_compact_heads_up_reply" msgid="2425293958371284340">"जवाब दें"</string> <string name="notification_hidden_text" msgid="2835519769868187223">"नई सूचना"</string> - <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"सामान्य कीबोर्ड"</string> + <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"फ़िज़िकल कीबोर्ड"</string> <string name="notification_channel_security" msgid="8516754650348238057">"सुरक्षा"</string> <string name="notification_channel_car_mode" msgid="2123919247040988436">"कार मोड"</string> <string name="notification_channel_account" msgid="6436294521740148173">"खाते की स्थिति"</string> diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml index 5b3dc7c86658..b5fc19a3e961 100644 --- a/core/res/res/values-hr/strings.xml +++ b/core/res/res/values-hr/strings.xml @@ -2460,8 +2460,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Sadržaj aplikacije sakriven je od dijeljenja zaslona radi sigurnosti"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Automatski povezano sa satelitom"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"Možete slati i primati poruke bez mobilne mreže ili Wi-Fi mreže"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"Možete slati i primati poruke te koristiti ograničene podatke putem satelita"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"Želite li slati poruke putem satelita?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"Šaljite i primajte poruke kad nije dostupna mobilna ili Wi-Fi mreža"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"Otvori Poruke"</string> diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml index 117509955341..f42567ed1fe6 100644 --- a/core/res/res/values-hu/strings.xml +++ b/core/res/res/values-hu/strings.xml @@ -2459,8 +2459,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Apptartalom elrejtve a megosztástól a biztonság érdekében"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Automatikusan csatlakozva a műholdhoz"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"Küldhet és fogadhat üzeneteket mobil- és Wi-Fi-hálózat nélkül is"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"Küldhet és fogadhat üzeneteket, valamint korlátozott mennyiségű adatot használhat műholdon keresztül"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"Műholdas üzenetváltást szeretne használni?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"Küldhet és fogadhat üzeneteket mobil- és Wi-Fi-hálózat nélkül is"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"A Messages megnyitása"</string> diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml index c89544823314..e3fa611ce135 100644 --- a/core/res/res/values-hy/strings.xml +++ b/core/res/res/values-hy/strings.xml @@ -2459,8 +2459,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Անվտանգության նկատառումներով՝ բովանդակությունը թաքցվել է ցուցադրումից"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Ավտոմատ միացել է արբանյակին"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"Դուք կարող եք հաղորդագրություններ ուղարկել և ստանալ առանց բջջային կամ Wi-Fi կապի"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"Դուք կարող եք արբանյակի միջոցով ուղարկել և ստանալ հաղորդագրություններ և օգտագործել սահմանափակ ծավալի ինտերնետ թրաֆիկ"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"Օգտագործե՞լ արբանյակային հաղորդագրումը"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"Ուղարկեք և ստացեք հաղորդագրություններ առանց բջջային կամ Wi-Fi ցանցի"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"Բացել Messages-ը"</string> diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml index 26a79e0b53a9..5f60ba2de8e4 100644 --- a/core/res/res/values-in/strings.xml +++ b/core/res/res/values-in/strings.xml @@ -2459,8 +2459,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Konten aplikasi disembunyikan dari berbagi layar karena alasan keamanan"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Menghubungkan otomatis ke satelit"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"Anda dapat mengirim dan menerima pesan tanpa jaringan seluler atau Wi-Fi"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"Anda dapat mengirim dan menerima pesan serta menggunakan data terbatas melalui satelit"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"Gunakan fitur pesan satelit?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"Mengirim dan menerima pesan tanpa jaringan seluler atau Wi-Fi"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"Buka Message"</string> diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml index f6d043b170ed..92567427b0b9 100644 --- a/core/res/res/values-is/strings.xml +++ b/core/res/res/values-is/strings.xml @@ -2459,8 +2459,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Efni forrits falið í skjádeilingu af öryggisástæðum"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Tengdist sjálfkrafa við gervihnött"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"Þú getur sent og móttekið skilaboð án tengingar við farsímakerfi eða Wi-Fi"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"Þú getur sent og móttekið skilaboð og notað takmörkuð gögn gegnum gervihnött"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"Nota skilaboð í gegnum gervihnött?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"Senda og fá skilaboð án tengingar við farsímakerfi eða Wi-Fi-net"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"Opna Messages"</string> diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml index 96075003f0b2..34b179fae4ed 100644 --- a/core/res/res/values-it/strings.xml +++ b/core/res/res/values-it/strings.xml @@ -2460,8 +2460,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Contenuti dell\'app nascosti dalla condivisione schermo per sicurezza"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Connessione automatica al satellite"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"Puoi inviare e ricevere messaggi senza una rete mobile o Wi-Fi"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"Puoi inviare e ricevere messaggi e utilizzare dati limitati via satellite"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"Utilizzare i messaggi via satellite?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"Invia e ricevi messaggi senza una rete mobile o Wi-Fi"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"Apri Messaggi"</string> diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml index d25cf01602e4..ffca8688ac51 100644 --- a/core/res/res/values-iw/strings.xml +++ b/core/res/res/values-iw/strings.xml @@ -303,8 +303,7 @@ <string name="notification_channel_network_alerts" msgid="6312366315654526528">"התראות רשת"</string> <string name="notification_channel_network_available" msgid="6083697929214165169">"יש רשת זמינה"</string> <string name="notification_channel_vpn" msgid="1628529026203808999">"סטטוס ה-VPN"</string> - <!-- no translation found for notification_channel_system_time (1660313368058030441) --> - <skip /> + <string name="notification_channel_system_time" msgid="1660313368058030441">"זמן ואזורי זמן"</string> <string name="notification_channel_device_admin" msgid="6384932669406095506">"התראות ממנהל ה-IT"</string> <string name="notification_channel_alerts" msgid="5070241039583668427">"התראות"</string> <string name="notification_channel_retail_mode" msgid="3732239154256431213">"הדגמה לקמעונאים"</string> @@ -312,8 +311,7 @@ <string name="notification_channel_heavy_weight_app" msgid="17455756500828043">"אפליקציה פועלת"</string> <string name="notification_channel_foreground_service" msgid="7102189948158885178">"אפליקציות שמרוקנות את הסוללה"</string> <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"הגדלה"</string> - <!-- no translation found for notification_channel_accessibility_hearing_device (7816963856388758952) --> - <skip /> + <string name="notification_channel_accessibility_hearing_device" msgid="7816963856388758952">"מכשיר שמיעה"</string> <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"שימוש בנגישות"</string> <string name="notification_channel_display" msgid="6905032605735615090">"מסך"</string> <string name="foreground_service_app_in_background" msgid="1439289699671273555">"האפליקציה <xliff:g id="APP_NAME">%1$s</xliff:g> משתמשת בסוללה"</string> @@ -1411,10 +1409,8 @@ <string name="install_carrier_app_notification_button" msgid="6257740533102594290">"להורדת האפליקציה"</string> <string name="carrier_app_notification_title" msgid="5815477368072060250">"ה-SIM החדש הוכנס"</string> <string name="carrier_app_notification_text" msgid="6567057546341958637">"יש ללחוץ כדי להגדיר"</string> - <!-- no translation found for time_zone_change_notification_title (5232503069219193218) --> - <skip /> - <!-- no translation found for time_zone_change_notification_body (6135793674904665585) --> - <skip /> + <string name="time_zone_change_notification_title" msgid="5232503069219193218">"אזור הזמן שלך השתנה"</string> + <string name="time_zone_change_notification_body" msgid="6135793674904665585">"אזור הזמן הנוכחי שלך הוא <xliff:g id="TIME_ZONE_DISPLAY_NAME">%1$s</xliff:g> (<xliff:g id="TIME_ZONE_OFFSET">%2$s</xliff:g>)"</string> <string name="time_picker_dialog_title" msgid="9053376764985220821">"הגדרת שעה"</string> <string name="date_picker_dialog_title" msgid="5030520449243071926">"הגדרת תאריך"</string> <string name="date_time_set" msgid="4603445265164486816">"הגדרה"</string> @@ -1463,7 +1459,7 @@ <string name="select_input_method" msgid="3971267998568587025">"בחירה של שיטת הזנה"</string> <string name="input_method_language_settings" msgid="8069089418056819437">"הגדרות שפה"</string> <string name="show_ime" msgid="6406112007347443383">"להשאיר במסך בזמן שהמקלדת הפיזית פעילה"</string> - <string name="hardware" msgid="3611039921284836033">"שימוש במקלדת שמופיעה במסך"</string> + <string name="hardware" msgid="3611039921284836033">"שימוש במקלדת הווירטואלית"</string> <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"הגדרה של <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string> <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"הגדרת מקלדות פיזיות"</string> <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"יש ללחוץ כדי לבחור שפה ופריסה"</string> @@ -1802,18 +1798,12 @@ <string name="accessibility_gesture_instructional_text" msgid="4133877896011098550">"התכונה תיפתח בפעם הבאה שייעשה שימוש במקש הקיצור הזה. צריך להחליק למעלה עם 2 אצבעות מהחלק התחתון של המסך ולשחרר במהירות."</string> <string name="accessibility_gesture_3finger_instructional_text" msgid="1124458279366968154">"התכונה תיפתח בפעם הבאה שייעשה שימוש במקש הקיצור הזה. צריך להחליק למעלה עם 3 אצבעות מהחלק התחתון של המסך ולשחרר במהירות."</string> <string name="accessibility_magnification_chooser_text" msgid="1502075582164931596">"הגדלה"</string> - <!-- no translation found for hearing_device_switch_phone_mic_notification_title (6645178038359708836) --> - <skip /> - <!-- no translation found for hearing_device_switch_hearing_mic_notification_title (4612074852145289569) --> - <skip /> - <!-- no translation found for hearing_device_switch_phone_mic_notification_text (1332426273666077412) --> - <skip /> - <!-- no translation found for hearing_device_switch_hearing_mic_notification_text (8288368365767284208) --> - <skip /> - <!-- no translation found for hearing_device_notification_switch_button (3619524619430941300) --> - <skip /> - <!-- no translation found for hearing_device_notification_settings_button (6673651052880279178) --> - <skip /> + <string name="hearing_device_switch_phone_mic_notification_title" msgid="6645178038359708836">"להחליף למיקרופון של הטלפון?"</string> + <string name="hearing_device_switch_hearing_mic_notification_title" msgid="4612074852145289569">"להחליף למיקרופון של מכשיר השמיעה?"</string> + <string name="hearing_device_switch_phone_mic_notification_text" msgid="1332426273666077412">"כדי לשמוע טוב יותר או אם הסוללה של מכשיר השמיעה נחלשת. במצב הזה המיקרופון מוחלף רק במהלך השיחה."</string> + <string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"אפשר לשוחח במצב דיבורית באמצעות המיקרופון של מכשיר השמיעה. במצב הזה המיקרופון מוחלף רק במהלך השיחה."</string> + <string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"החלפה"</string> + <string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"הגדרות"</string> <string name="user_switched" msgid="7249833311585228097">"המשתמש הנוכחי <xliff:g id="NAME">%1$s</xliff:g>."</string> <string name="user_switching_message" msgid="1912993630661332336">"מעבר אל <xliff:g id="NAME">%1$s</xliff:g>…"</string> <string name="user_logging_out_message" msgid="7216437629179710359">"מתבצע ניתוק של <xliff:g id="NAME">%1$s</xliff:g>…"</string> @@ -2470,8 +2460,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"תוכן האפליקציה מוסתר משיתוף המסך מטעמי אבטחה"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"חיבור אוטומטי ללוויין"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"אפשר לשלוח ולקבל הודעות ללא רשת סלולרית או רשת Wi-Fi"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"אפשר לשלוח ולקבל הודעות ולהשתמש בחבילת הגלישה באופן מוגבל באמצעות לוויין"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"רוצה לשלוח הודעות באמצעות תקשורת לוויינית?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"אפשר לשלוח ולקבל הודעות ללא רשת סלולרית או Wi-Fi"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"לפתיחת Messages"</string> diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml index e1fd8d1a8d82..3f3881b0d598 100644 --- a/core/res/res/values-kk/strings.xml +++ b/core/res/res/values-kk/strings.xml @@ -2459,8 +2459,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Қауіпсіздік мақсатында қолданба контенті экранды көрсету кезінде жасырылды."</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Жерсерік қызметіне автоматты түрде қосылды"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"Мобильдік не Wi-Fi желісіне қосылмастан хабар жібере аласыз және ала аласыз."</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"Жерсерік арқылы хабар жіберуге және алуға, деректерді шектеулі көлемде пайдалануға болады."</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"Жерсерік арқылы хабар алмасасыз ба?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"Хабарландыруларды мобильдік желіге немесе Wi-Fi желісіне қосылмай жіберіңіз және алыңыз."</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages қолданбасын ашу"</string> diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml index 190693976be1..0ad4f09d1cfa 100644 --- a/core/res/res/values-km/strings.xml +++ b/core/res/res/values-km/strings.xml @@ -2459,8 +2459,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"បានលាក់ខ្លឹមសារកម្មវិធីពីការបង្ហាញអេក្រង់ដើម្បីសុវត្ថិភាព"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"ភ្ជាប់ដោយស្វ័យប្រវត្តិទៅផ្កាយរណប"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"អ្នកអាចផ្ញើ និងទទួលសារដោយមិនប្រើបណ្តាញទូរសព្ទចល័ត ឬ Wi-Fi"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"អ្នកអាចផ្ញើ និងទទួលសារ ព្រមទាំងប្រើទិន្នន័យមានកំណត់តាមរយៈផ្កាយរណប"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"ប្រើការផ្ញើសារតាមផ្កាយរណបឬ?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"ផ្ញើ និងទទួលសារដោយគ្មានបណ្ដាញ Wi-Fi ឬបណ្ដាញទូរសព្ទចល័ត"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"បើកកម្មវិធី Messages"</string> diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml index 9c11160f502d..c65dd276be8d 100644 --- a/core/res/res/values-kn/strings.xml +++ b/core/res/res/values-kn/strings.xml @@ -2459,8 +2459,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"ಭದ್ರತೆಗಾಗಿ ಸ್ಕ್ರೀನ್ ಹಂಚಿಕೊಳ್ಳುವಿಕೆಯಲ್ಲಿ ಆ್ಯಪ್ ಕಂಟೆಂಟ್ ಅನ್ನು ಮರೆಮಾಡಲಾಗಿದೆ"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"ಸ್ಯಾಟಲೈಟ್ಗೆ ಸ್ವಯಂಚಾಲಿತವಾಗಿ ಕನೆಕ್ಟ್ ಆಗಿದೆ"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"ನೀವು ಮೊಬೈಲ್ ಅಥವಾ ವೈ-ಫೈ ನೆಟ್ವರ್ಕ್ ಇಲ್ಲದೆಯೇ ಸಂದೇಶಗಳನ್ನು ಕಳುಹಿಸಬಹುದು ಮತ್ತು ಸ್ವೀಕರಿಸಬಹುದು"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"ನೀವು ಸ್ಯಾಟಲೈಟ್ ಮೂಲಕ ಸಂದೇಶಗಳನ್ನು ಕಳುಹಿಸಬಹುದು ಮತ್ತು ಸ್ವೀಕರಿಸಬಹುದು ಹಾಗೂ ಸೀಮಿತ ಡೇಟಾವನ್ನು ಬಳಸಬಹುದು"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"ಸ್ಯಾಟಲೈಟ್ ಮೆಸೇಜಿಂಗ್ ಅನ್ನು ಬಳಸಬೇಕೆ?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"ಮೊಬೈಲ್ ಅಥವಾ ವೈ-ಫೈ ನೆಟ್ವರ್ಕ್ ಇಲ್ಲದೆಯೇ ಸಂದೇಶಗಳನ್ನು ಕಳುಹಿಸಿ ಮತ್ತು ಸ್ವೀಕರಿಸಿ"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages ಅನ್ನು ತೆರೆಯಿರಿ"</string> diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml index 1b302e633fa7..a52da3c6b91e 100644 --- a/core/res/res/values-ko/strings.xml +++ b/core/res/res/values-ko/strings.xml @@ -2459,8 +2459,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"보안을 위해 화면 공유에서 앱 콘텐츠가 숨겨집니다."</string> <string name="satellite_notification_title" msgid="4026338973463121526">"위성에 자동 연결됨"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"모바일 또는 Wi-Fi 네트워크 없이 메시지를 주고 받을 수 있습니다"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"위성으로 메시지를 주고받을 수 있으며 데이터를 제한적으로 사용할 수도 있습니다."</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"위성 메시지를 사용하시겠습니까?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"모바일 또는 Wi-Fi 네트워크 없이 메시지 주고받기"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"메시지 열기"</string> diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml index 58a6d8486caf..ff743fc42bd3 100644 --- a/core/res/res/values-ky/strings.xml +++ b/core/res/res/values-ky/strings.xml @@ -2459,8 +2459,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Коопсуздук үчүн колдонмодогу контент бөлүшүлгөн экрандан жашырылды"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Спутникке автоматтык түрдө туташтырылган"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"Сиз мобилдик же Wi-Fi тармагы жок эле билдирүүлөрдү жөнөтүп, ала аласыз"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"Спутник аркылуу билдирүүлөрдү жөнөтүп жана алып, чектелген трафикти колдоно аласыз"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"Спутник аркылуу байланышасызбы?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"Мобилдик же Wi-Fi тармагына туташпай эле билдирүүлөрдү жөнөтүп, алыңыз"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"Жазышуулар колдонмосун ачуу"</string> diff --git a/core/res/res/values-lo/strings.xml b/core/res/res/values-lo/strings.xml index 3c3c58ba281a..6f32d875c2c8 100644 --- a/core/res/res/values-lo/strings.xml +++ b/core/res/res/values-lo/strings.xml @@ -2459,8 +2459,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"ເນື້ອຫາແອັບຖືກເຊື່ອງໄວ້ຈາກການແບ່ງປັນໜ້າຈໍເພື່ອຄວາມປອດໄພ"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"ເຊື່ອມຕໍ່ກັບດາວທຽມໂດຍອັດຕະໂນມັດ"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"ທ່ານສາມາດສົ່ງ ແລະ ຮັບຂໍ້ຄວາມໂດຍບໍ່ຕ້ອງໃຊ້ເຄືອຂ່າຍມືຖື ຫຼື Wi-Fi"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"ທ່ານສາມາດສົ່ງ ແລະ ຮັບຂໍ້ຄວາມ ແລະ ໃຊ້ຂໍ້ມູນທີ່ບໍ່ຈໍາກັດຜ່ານດາວທຽມ"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"ໃຊ້ການຮັບສົ່ງຂໍ້ຄວາມຜ່ານດາວທຽມບໍ?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"ຮັບ ແລະ ສົ່ງຂໍ້ຄວາມໂດຍບໍ່ຕ້ອງໃຊ້ເຄືອຂ່າຍໂທລະສັບມືຖື ຫຼື Wi-Fi"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"ເປີດ Messages"</string> diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml index 3c56808d883e..1035fed764f4 100644 --- a/core/res/res/values-lt/strings.xml +++ b/core/res/res/values-lt/strings.xml @@ -2461,8 +2461,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Programos turinys paslėptas bendrinant ekraną saugumo sumetimais"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Automatiškai prisijungta prie palydovinio ryšio"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"Galite siųsti ir gauti pranešimus be mobiliojo ryšio ar „Wi-Fi“ tinklo"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"Galite siųsti ir gauti pranešimus bei naudoti ribotus duomenis per palydovą"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"Naudoti susirašinėjimą palydoviniais pranešimais?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"Siųskite ir gaukite pranešimus be mobiliojo ryšio ar „Wi-Fi“ tinklo"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"Atidaryti programą „Messages“"</string> diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml index 1870883fcc5e..a643b9861312 100644 --- a/core/res/res/values-lv/strings.xml +++ b/core/res/res/values-lv/strings.xml @@ -2460,8 +2460,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Drošības nolūkos lietotnes saturs kopīgotajā ekrānā ir paslēpts"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Automātiski izveidots savienojums ar satelītu"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"Varat sūtīt un saņemt ziņojumus bez mobilā vai Wi-Fi tīkla."</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"Varat sūtīt un saņemt ziņojumus un izmantot ierobežotu datu apjomu, lietojot satelītu."</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"Vai izmantot satelīta ziņojumapmaiņu?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"Sūtiet un saņemiet ziņojumus bez mobilā vai Wi‑Fi tīkla."</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"Atvērt lietotni Ziņojumi"</string> diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml index 1c9e022ca3aa..774e4edc1cd6 100644 --- a/core/res/res/values-mk/strings.xml +++ b/core/res/res/values-mk/strings.xml @@ -2459,8 +2459,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Од безбедносни причини, содржините на апликацијата се скриени од споделувањето екран"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Поврзано со сателит автоматски"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"Може да испраќате и примате пораки без мобилна или Wi-Fi мрежа"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"Може да испраќате и да примате пораки и да користите ограничен интернет преку сателит"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"Да се користи „Сателитска размена на пораки“?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"Испраќајте и примајте пораки без мобилна или Wi-Fi мрежа"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"Отворете ја Messages"</string> diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml index e70a95ce77d2..9dbfc1e58e89 100644 --- a/core/res/res/values-mn/strings.xml +++ b/core/res/res/values-mn/strings.xml @@ -2459,8 +2459,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Аюулгүй байдлын үүднээс аппын контентыг дэлгэц хуваалцахаас нуусан"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Хиймэл дагуулд автоматаар холбогдсон"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"Та мобайл эсвэл Wi-Fi сүлжээгүйгээр мессеж илгээх болон хүлээн авах боломжтой"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"Та хиймэл дагуулаар мессеж илгээх, хүлээн авах, хязгаарлагдмал дата ашиглах боломжтой"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"Хиймэл дагуулаар дамжин мессеж бичихийг ашиглах уу?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"Хөдөлгөөнт холбооны эсвэл Wi-Fi сүлжээгүйгээр мессеж илгээх болон хүлээн авах"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"Мессежийг нээх"</string> diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml index cbe6b979d1d8..750988a242b7 100644 --- a/core/res/res/values-mr/strings.xml +++ b/core/res/res/values-mr/strings.xml @@ -2459,8 +2459,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"स्क्रीन शेअर करताना सुरक्षेसाठी अॅपमधील आशय लपवला आहे"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"उपग्रहाशी आपोआप कनेक्ट केलेले आहे"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"तुम्ही मोबाइल किंवा वाय-फाय नेटवर्कशिवाय मेसेज पाठवू आणि मिळवू शकता"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"तुम्ही उपग्रहाद्वारे मेसेज पाठवू आणि मिळवू शकता व मर्यादित डेटा वापरू शकता"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"सॅटेलाइट मेसेजिंग वापरायचे आहे का?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"मोबाइल किंवा वाय-फाय नेटवर्कशिवाय मेसेज पाठवणे आणि मिळवणे"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages उघडा"</string> diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml index 432f7f9c6aa0..76086336514f 100644 --- a/core/res/res/values-my/strings.xml +++ b/core/res/res/values-my/strings.xml @@ -2459,8 +2459,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"လုံခြုံရေးအတွက် အက်ပ်အကြောင်းအရာကို ဖန်သားပြင် မျှဝေခြင်းတွင် ဖျောက်ထားသည်"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"ဂြိုဟ်တုနှင့် အလိုအလျောက် ချိတ်ဆက်ထားသည်"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"မိုဘိုင်း (သို့) Wi-Fi ကွန်ရက်မရှိဘဲ မက်ဆေ့ဂျ်များကို ပို့နိုင်၊ လက်ခံနိုင်သည်"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"ဂြိုဟ်တုဖြင့် မက်ဆေ့ဂျ်များ ပေးပို့လက်ခံနိုင်ပြီး အကန့်အသတ်ဖြင့် ဒေတာသုံးနိုင်သည်"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"ဂြိုဟ်တုမှတစ်ဆင့် မက်ဆေ့ဂျ်ပို့ခြင်း သုံးမလား။"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"မိုဘိုင်း (သို့) Wi-Fi ကွန်ရက်မရှိဘဲ မက်ဆေ့ဂျ်များ ပို့နိုင်၊ လက်ခံနိုင်သည်"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages ဖွင့်ရန်"</string> diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml index c80a9605fcde..6bd4598709e6 100644 --- a/core/res/res/values-nb/strings.xml +++ b/core/res/res/values-nb/strings.xml @@ -2459,8 +2459,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Av sikkerhetsgrunner er appinnholdet skjult for skjermdelingen"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Automatisk tilkoblet satellitt"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"Du kan sende og motta meldinger uten mobil- eller wifi-nettverk"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"Du kan sende og motta meldinger og bruke begrensede data via satellitt"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"Vil du bruke satellittmeldinger?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"Send og motta meldinger uten mobil- eller wifi-nettverk"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"Åpne Meldinger"</string> diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml index bf7bbaf872be..b94c2f618d6b 100644 --- a/core/res/res/values-ne/strings.xml +++ b/core/res/res/values-ne/strings.xml @@ -2459,8 +2459,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"स्क्रिन सेयर गर्दा सुरक्षाका लागि एपमा भएको सामग्री लुकाइएको छ"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"स्याटलाइटमा स्वतः कनेक्ट गरियो"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"तपाईं मोबाइल वा Wi-Fi नेटवर्कविनै म्यासेज पठाउन र प्राप्त गर्न सक्नुहुन्छ"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"तपाईं स्याटलाइटमार्फत म्यासेजहरू पठाउन तथा प्राप्त गर्न र सीमित डेटा प्रयोग गर्न सक्नुहुन्छ"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"स्याटलाइटमार्फत म्यासेज पठाउने सुविधा प्रयोग गर्ने हो?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"मोबाइल वा Wi-Fi नेटवर्कविनै म्यासेजहरू पठाउनुहोस् र प्राप्त गर्नुहोस्"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages खोल्नुहोस्"</string> diff --git a/core/res/res/values-night/colors.xml b/core/res/res/values-night/colors.xml index d3f998fb70cf..7b9b2d666292 100644 --- a/core/res/res/values-night/colors.xml +++ b/core/res/res/values-night/colors.xml @@ -47,6 +47,10 @@ <color name="language_picker_item_selected_bg">#92B3F2</color> <color name="language_picker_item_selected_stroke">#185ABC</color> + <!-- Color for various surfaces related to system-wide blur --> + <color name="surface_effect_0">@color/surface_effect_0_color</color> + <color name="surface_effect_1">@color/surface_effect_1_color</color> + <!-- Color for side fps toast dark theme--> <color name="side_fps_toast_background">#2E3132</color> <color name="side_fps_text_color">#EFF1F2</color> diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml index 695a706dcdb4..52d9106cebfd 100644 --- a/core/res/res/values-nl/strings.xml +++ b/core/res/res/values-nl/strings.xml @@ -2459,8 +2459,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Vanwege beveiligingsrisico\'s is app-content verborgen voor scherm delen"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Automatisch verbonden met satelliet"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"Je kunt berichten sturen en krijgen zonder een mobiel of wifi-netwerk"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"Je kunt berichten sturen en krijgen en beperkte data via satelliet gebruiken"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"Satellietberichten gebruiken?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"Stuur en krijg berichten zonder mobiel of wifi-netwerk"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"Berichten openen"</string> diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml index de95a033ec20..ba2560f66ae5 100644 --- a/core/res/res/values-or/strings.xml +++ b/core/res/res/values-or/strings.xml @@ -1408,7 +1408,7 @@ <string name="install_carrier_app_notification_button" msgid="6257740533102594290">"ଆପ୍ ଡାଉନଲୋଡ କରନ୍ତୁ"</string> <string name="carrier_app_notification_title" msgid="5815477368072060250">"ନୂଆ SIM କାର୍ଡ ଭର୍ତ୍ତି କରାଗଲା"</string> <string name="carrier_app_notification_text" msgid="6567057546341958637">"ଏହା ସେଟଅପ୍ କରିବା ପାଇଁ ଟାପ୍ କରନ୍ତୁ"</string> - <string name="time_zone_change_notification_title" msgid="5232503069219193218">"ଆପଣଙ୍କର ଟାଇମ ଜୋନ ବଦଳିଛି"</string> + <string name="time_zone_change_notification_title" msgid="5232503069219193218">"ଆପଣଙ୍କ ଟାଇମ ଜୋନ ପରିବର୍ତ୍ତନ ହୋଇଛି"</string> <string name="time_zone_change_notification_body" msgid="6135793674904665585">"ଆପଣ ବର୍ତ୍ତମାନ <xliff:g id="TIME_ZONE_DISPLAY_NAME">%1$s</xliff:g> (<xliff:g id="TIME_ZONE_OFFSET">%2$s</xliff:g>)ରେ ଅଛନ୍ତି"</string> <string name="time_picker_dialog_title" msgid="9053376764985220821">"ସମୟ ସେଟ୍ କରନ୍ତୁ"</string> <string name="date_picker_dialog_title" msgid="5030520449243071926">"ତାରିଖ ସେଟ୍ କରନ୍ତୁ"</string> @@ -1799,9 +1799,9 @@ <string name="accessibility_magnification_chooser_text" msgid="1502075582164931596">"ମେଗ୍ନିଫିକେସନ"</string> <string name="hearing_device_switch_phone_mic_notification_title" msgid="6645178038359708836">"ଫୋନ ମାଇକକୁ ସୁଇଚ କରିବେ?"</string> <string name="hearing_device_switch_hearing_mic_notification_title" msgid="4612074852145289569">"ଶ୍ରବଣ ଯନ୍ତ୍ର ମାଇକକୁ ସୁଇଚ କରିବେ?"</string> - <string name="hearing_device_switch_phone_mic_notification_text" msgid="1332426273666077412">"ଭଲ ସାଉଣ୍ଡ ପାଇଁ କିମ୍ବା ଯଦି ଆପଣଙ୍କର ଶ୍ରବଣ ଯନ୍ତ୍ର ବ୍ୟାଟେରୀ କମ ଥିଲେ। କଲ ସମୟରେ ଏହା କେବଳ ଆପଣଙ୍କର ମାଇକ ସୁଇଚ କରିଥାଏ।"</string> - <string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"ହେଣ୍ଡସ-ଫ୍ରି କଲିଂ ପାଇଁ ଆପଣ ଆପଣଙ୍କର ଶ୍ରବଣ ଯନ୍ତ୍ର ମାଇକ୍ରୋଫୋନ ବ୍ୟବହାର କରିପାରିବେ। କଲ ସମୟରେ ଏହା କେବଳ ଆପଣଙ୍କର ମାଇକ ସୁଇଚ କରିଥାଏ।"</string> - <string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"ସ୍ୱିଚ କରନ୍ତୁ"</string> + <string name="hearing_device_switch_phone_mic_notification_text" msgid="1332426273666077412">"ଭଲ ସାଉଣ୍ଡ ପାଇଁ କିମ୍ବା ଯଦି ଆପଣଙ୍କର ଶ୍ରବଣ ଯନ୍ତ୍ର ବେଟେରୀ କମ ଥିଲେ। କଲ ସମୟରେ ଏହା କେବଳ ଆପଣଙ୍କର ମାଇକକୁ ସୁଇଚ କରିଥାଏ।"</string> + <string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"ହେଣ୍ଡସ-ଫ୍ରି କଲିଂ ପାଇଁ ଆପଣ ଆପଣଙ୍କର ଶ୍ରବଣ ଯନ୍ତ୍ର ମାଇକ୍ରୋଫୋନ ବ୍ୟବହାର କରିପାରିବେ। କଲ ସମୟରେ ଏହା କେବଳ ଆପଣଙ୍କର ମାଇକକୁ ସୁଇଚ କରିଥାଏ।"</string> + <string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"ସୁଇଚ କରନ୍ତୁ"</string> <string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"ସେଟିଂସ"</string> <string name="user_switched" msgid="7249833311585228097">"ବର୍ତ୍ତମାନର ୟୁଜର୍ ହେଉଛନ୍ତି <xliff:g id="NAME">%1$s</xliff:g>।"</string> <string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g>ରେ ସ୍ୱିଚ କରନ୍ତୁ…"</string> @@ -2459,8 +2459,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"ସୁରକ୍ଷା ପାଇଁ ସ୍କ୍ରିନ ସେୟାରରୁ ଆପ ବିଷୟବସ୍ତୁକୁ ଲୁଚାଯାଇଛି"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"ସାଟେଲାଇଟ ସହ ସ୍ୱତଃ କନେକ୍ଟ ହୋଇଛି"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"ଏକ ମୋବାଇଲ କିମ୍ବା ୱାଇ-ଫାଇ ନେଟୱାର୍କ ବିନା ଆପଣ ମେସେଜ ପଠାଇପାରିବେ ଏବଂ ପାଇପାରିବେ"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"ସେଟେଲାଇଟ ମାଧ୍ୟମରେ ଆପଣ ମେସେଜ ପଠାଇପାରିବେ ଓ ପାଇପାରିବେ ଏବଂ ସୀମିତ ଡାଟା ବ୍ୟବହାର କରିପାରିବେ"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"ସେଟେଲାଇଟ ମେସେଜିଂକୁ ବ୍ୟବହାର କରିବେ?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"ଏକ ମୋବାଇଲ କିମ୍ବା ୱାଇ-ଫାଇ ନେଟୱାର୍କ ବିନା ମେସେଜ ପଠାନ୍ତୁ ଏବଂ ପାଆନ୍ତୁ"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages ଖୋଲନ୍ତୁ"</string> diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml index 4d4b10bb25db..2d2f9c01a4c3 100644 --- a/core/res/res/values-pa/strings.xml +++ b/core/res/res/values-pa/strings.xml @@ -2459,8 +2459,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"ਐਪ ਸਮੱਗਰੀ ਨੂੰ ਸੁਰੱਖਿਆ ਲਈ ਸਕ੍ਰੀਨ ਸਾਂਝਾਕਰਨ ਤੋਂ ਲੁਕਾਇਆ ਗਿਆ ਹੈ"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"ਸੈਟੇਲਾਈਟ ਨਾਲ ਸਵੈ-ਕਨੈਕਟ ਹੋਇਆ"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"ਤੁਸੀਂ ਮੋਬਾਈਲ ਜਾਂ ਵਾਈ-ਫਾਈ ਨੈੱਟਵਰਕ ਤੋਂ ਬਿਨਾਂ ਸੁਨੇਹੇ ਭੇਜ ਅਤੇ ਪ੍ਰਾਪਤ ਕਰ ਸਕਦੇ ਹੋ"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"ਤੁਸੀਂ ਸੁਨੇਹੇ ਭੇਜ ਅਤੇ ਪ੍ਰਾਪਤ ਕਰ ਸਕਦੇ ਹੋ ਅਤੇ ਸੈਟੇਲਾਈਟ ਰਾਹੀਂ ਸੀਮਤ ਡਾਟਾ ਵਰਤ ਸਕਦੇ ਹੋ"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"ਕੀ ਸੈਟੇਲਾਈਟ ਸੁਨੇਹੇ ਦੀ ਵਰਤੋਂ ਕਰਨੀ ਹੈ?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"ਮੋਬਾਈਲ ਜਾਂ ਵਾਈ-ਫਾਈ ਨੈੱਟਵਰਕ ਤੋਂ ਬਿਨਾਂ ਸੁਨੇਹੇ ਭੇਜੋ ਅਤੇ ਪ੍ਰਾਪਤ ਕਰੋ"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages ਐਪ ਖੋਲ੍ਹੋ"</string> diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml index cc4b251c7713..91c7adc58eb3 100644 --- a/core/res/res/values-pt-rBR/strings.xml +++ b/core/res/res/values-pt-rBR/strings.xml @@ -2460,8 +2460,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Conteúdo oculto no compartilhamento de tela por segurança"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Conectado automaticamente ao satélite"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"Você pode enviar e receber mensagens sem um dispositivo móvel ou uma rede Wi-Fi"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"É possível enviar e receber mensagens e usar dados limitados via satélite"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"Usar mensagens via satélite?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"Enviar e receber mensagens sem uma rede móvel ou Wi-Fi"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"Abrir o app Mensagens"</string> diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml index cc4b251c7713..91c7adc58eb3 100644 --- a/core/res/res/values-pt/strings.xml +++ b/core/res/res/values-pt/strings.xml @@ -2460,8 +2460,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Conteúdo oculto no compartilhamento de tela por segurança"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Conectado automaticamente ao satélite"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"Você pode enviar e receber mensagens sem um dispositivo móvel ou uma rede Wi-Fi"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"É possível enviar e receber mensagens e usar dados limitados via satélite"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"Usar mensagens via satélite?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"Enviar e receber mensagens sem uma rede móvel ou Wi-Fi"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"Abrir o app Mensagens"</string> diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml index 25b0471347c9..0fcd12bfbc01 100644 --- a/core/res/res/values-ro/strings.xml +++ b/core/res/res/values-ro/strings.xml @@ -2460,8 +2460,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Conținutul aplicației este ascuns de permiterea accesului la ecran din motive de securitate"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"S-a conectat automat la satelit"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"Poți să trimiți și să primești mesaje fără o rețea mobilă sau Wi-Fi"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"Poți să trimiți și să primești mesaje și să folosești date limitate prin satelit"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"Folosești mesajele prin satelit?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"Trimite și primește mesaje fără o rețea mobilă sau Wi-Fi"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"Deschide Mesaje"</string> diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml index 1efa10593edc..85177030d45a 100644 --- a/core/res/res/values-ru/strings.xml +++ b/core/res/res/values-ru/strings.xml @@ -2461,8 +2461,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Для безопасности содержимое приложения при демонстрации экрана скрыто."</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Автоматически подключено к системам спутниковой связи"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"Вы можете отправлять и получать сообщения без доступа к мобильной сети или Wi-Fi."</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"Вы можете обмениваться сообщениями и использовать ограниченный объем трафика по спутниковой связи."</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"Включить спутниковый обмен сообщениями?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"Отправляйте и получайте сообщения без подключения к мобильной сети или Wi-Fi."</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"Открыть Сообщения"</string> diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml index 57f89a230384..863f0cc18ff8 100644 --- a/core/res/res/values-si/strings.xml +++ b/core/res/res/values-si/strings.xml @@ -302,8 +302,7 @@ <string name="notification_channel_network_alerts" msgid="6312366315654526528">"ජාල ඇඟවීම්"</string> <string name="notification_channel_network_available" msgid="6083697929214165169">"ජාලය ලබා ගැනීමට හැකිය"</string> <string name="notification_channel_vpn" msgid="1628529026203808999">"VPN තත්ත්වය"</string> - <!-- no translation found for notification_channel_system_time (1660313368058030441) --> - <skip /> + <string name="notification_channel_system_time" msgid="1660313368058030441">"වේලාව සහ කාල කලාප"</string> <string name="notification_channel_device_admin" msgid="6384932669406095506">"ඔබේ තොරතුරු තාක්ෂණ පරිපාලක වෙතින් ඇඟවීම්"</string> <string name="notification_channel_alerts" msgid="5070241039583668427">"ඇඟවීම්"</string> <string name="notification_channel_retail_mode" msgid="3732239154256431213">"සිල්ලර ආදර්ශනය"</string> @@ -311,8 +310,7 @@ <string name="notification_channel_heavy_weight_app" msgid="17455756500828043">"යෙදුම ධාවනය කරමින්"</string> <string name="notification_channel_foreground_service" msgid="7102189948158885178">"බැටරිය භාවිත කරන යෙදුම්"</string> <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"විශාලනය"</string> - <!-- no translation found for notification_channel_accessibility_hearing_device (7816963856388758952) --> - <skip /> + <string name="notification_channel_accessibility_hearing_device" msgid="7816963856388758952">"ශ්රවණ උපාංගය"</string> <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"ප්රවේශ්යතා භාවිතය"</string> <string name="notification_channel_display" msgid="6905032605735615090">"සංදර්ශකය"</string> <string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> බැටරිය භාවිත කරයි"</string> @@ -1410,10 +1408,8 @@ <string name="install_carrier_app_notification_button" msgid="6257740533102594290">"යෙදුම බාගන්න"</string> <string name="carrier_app_notification_title" msgid="5815477368072060250">"නව SIM ඇතුළු කරන්න"</string> <string name="carrier_app_notification_text" msgid="6567057546341958637">"එය පිහිටුවීමට තට්ටු කරන්න"</string> - <!-- no translation found for time_zone_change_notification_title (5232503069219193218) --> - <skip /> - <!-- no translation found for time_zone_change_notification_body (6135793674904665585) --> - <skip /> + <string name="time_zone_change_notification_title" msgid="5232503069219193218">"ඔබේ වේලා කලාපය වෙනස් විය"</string> + <string name="time_zone_change_notification_body" msgid="6135793674904665585">"ඔබ දැන් <xliff:g id="TIME_ZONE_DISPLAY_NAME">%1$s</xliff:g> (<xliff:g id="TIME_ZONE_OFFSET">%2$s</xliff:g>) හි වේ"</string> <string name="time_picker_dialog_title" msgid="9053376764985220821">"වේලාව සකසන්න"</string> <string name="date_picker_dialog_title" msgid="5030520449243071926">"දිනය සැකසීම"</string> <string name="date_time_set" msgid="4603445265164486816">"සකසන්න"</string> @@ -1801,18 +1797,12 @@ <string name="accessibility_gesture_instructional_text" msgid="4133877896011098550">"ඔබ මෙම කෙටිමඟ භාවිතා කරන මීළඟ වතාවේ විශේෂාංගය විවෘත වනු ඇත. ඔබේ තිරයෙහි පහළ සිට ඇඟිලි 2කින් ඉහළට ස්වයිප් කර ඉක්මනින් නිදහස් කරන්න."</string> <string name="accessibility_gesture_3finger_instructional_text" msgid="1124458279366968154">"ඔබ මෙම කෙටිමඟ භාවිතා කරන මීළඟ වතාවේ විශේෂාංගය විවෘත වනු ඇත. ඔබේ තිරයෙහි පහළ සිට ඇඟිලි 3කින් ඉහළට ස්වයිප් කර ඉක්මනින් නිදහස් කරන්න."</string> <string name="accessibility_magnification_chooser_text" msgid="1502075582164931596">"විශාලනය"</string> - <!-- no translation found for hearing_device_switch_phone_mic_notification_title (6645178038359708836) --> - <skip /> - <!-- no translation found for hearing_device_switch_hearing_mic_notification_title (4612074852145289569) --> - <skip /> - <!-- no translation found for hearing_device_switch_phone_mic_notification_text (1332426273666077412) --> - <skip /> - <!-- no translation found for hearing_device_switch_hearing_mic_notification_text (8288368365767284208) --> - <skip /> - <!-- no translation found for hearing_device_notification_switch_button (3619524619430941300) --> - <skip /> - <!-- no translation found for hearing_device_notification_settings_button (6673651052880279178) --> - <skip /> + <string name="hearing_device_switch_phone_mic_notification_title" msgid="6645178038359708836">"දුරකථන මයික්රෆෝනයට මාරු වෙන්න ද?"</string> + <string name="hearing_device_switch_hearing_mic_notification_title" msgid="4612074852145289569">"ශ්රවණාධාර මයික්රෆෝනයට මාරු වෙන්න ද?"</string> + <string name="hearing_device_switch_phone_mic_notification_text" msgid="1332426273666077412">"වඩා හොඳ ශබ්දයක් සඳහා හෝ ඔබේ ශ්රවණාධාර බැටරිය අඩු නම්. මෙය ඇමතුම අතරතුර පමණක් ඔබේ මයික්රෆෝනය මාරු කරයි."</string> + <string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"දෑත් නොයොදන ඇමතුම් සඳහා ඔබට ඔබේ ශ්රවණාධාර මයික්රෆෝනය භාවිතා කළ හැක. මෙය ඇමතුම අතරතුර පමණක් ඔබේ මයික්රෆෝනය මාරු කරයි."</string> + <string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"මාරු කරන්න"</string> + <string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"සැකසීම්"</string> <string name="user_switched" msgid="7249833311585228097">"දැනට සිටින පරිශීලකයා <xliff:g id="NAME">%1$s</xliff:g>."</string> <string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g> වෙත මාරු කරමින්…"</string> <string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g> වරමින්…"</string> @@ -2469,8 +2459,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"ආරක්ෂාව සඳහා යෙදුම් අන්තර්ගතය තිරය බෙදා ගැනීමෙන් සඟවා ඇත"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"චන්ද්රිකාවට ස්වයංක්රීයව සම්බන්ධ වේ"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"ඔබට ජංගම හෝ Wi-Fi ජාලයක් නොමැතිව පණිවිඩ යැවීමට සහ ලැබීමට හැක"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"ඔබට චන්ද්රිකා භාවිතයෙන් පණිවිඩ යැවීමට සහ ලැබීමට සහ සීමිත දත්ත භාවිතා කිරීමට හැක"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"චන්ද්රිකා පණිවිඩ යැවීම භාවිතා කරන්න ද?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"ජංගම හෝ Wi-Fi ජාලයකින් තොරව පණිවිඩ යැවීම සහ ලැබීම"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages විවෘත කරන්න"</string> diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml index c50773d594a9..fbb57ca0cb74 100644 --- a/core/res/res/values-sk/strings.xml +++ b/core/res/res/values-sk/strings.xml @@ -2461,8 +2461,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Obsah aplikácie je z bezpečnostných dôvodov pri zdieľaní obrazovky skrytý"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Automaticky pripojené k satelitu"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"Správy môžete odosielať a prijímať bez mobilnej siete či siete Wi-Fi"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"Môžete odosielať a prijímať správy a používať obmedzené dáta cez satelit"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"Chcete používať správy cez satelit?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"Odosielajte a prijímajte správy bez mobilnej siete či siete Wi‑Fi"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"Otvoriť Správy"</string> diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml index 134ee9df3f3d..f7a25e9288b6 100644 --- a/core/res/res/values-sl/strings.xml +++ b/core/res/res/values-sl/strings.xml @@ -2461,8 +2461,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Pri deljenju zaslona je vsebina aplikacije skrita zaradi varnosti"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Samodejno vzpostavljena povezava s satelitom"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"Sporočila SMS lahko pošiljate in prejemate brez mobilnega omrežja ali omrežja Wi-Fi"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"Prek satelita lahko pošiljate in prejemate sporočila ter uporabljate omejeno količino podatkov"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"Želite uporabiti satelitsko pošiljanje sporočil?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"Pošiljanje in prejemanje sporočil brez mobilnega omrežja ali omrežja Wi-Fi"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"Odpri Sporočila"</string> diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml index 0a87e39e16df..19be60047254 100644 --- a/core/res/res/values-sq/strings.xml +++ b/core/res/res/values-sq/strings.xml @@ -302,8 +302,7 @@ <string name="notification_channel_network_alerts" msgid="6312366315654526528">"Sinjalizimet e rrjetit"</string> <string name="notification_channel_network_available" msgid="6083697929214165169">"Ka rrjet të disponueshëm"</string> <string name="notification_channel_vpn" msgid="1628529026203808999">"Statusi i VPN-së"</string> - <!-- no translation found for notification_channel_system_time (1660313368058030441) --> - <skip /> + <string name="notification_channel_system_time" msgid="1660313368058030441">"Ora dhe brezat orarë"</string> <string name="notification_channel_device_admin" msgid="6384932669406095506">"Sinjalizimet nga administratori i teknologjisë së informacionit"</string> <string name="notification_channel_alerts" msgid="5070241039583668427">"Sinjalizimet"</string> <string name="notification_channel_retail_mode" msgid="3732239154256431213">"Demonstrimi i shitjes me pakicë"</string> @@ -311,8 +310,7 @@ <string name="notification_channel_heavy_weight_app" msgid="17455756500828043">"Aplikacioni është në ekzekutim"</string> <string name="notification_channel_foreground_service" msgid="7102189948158885178">"Aplikacionet që konsumojnë baterinë"</string> <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Zmadhimi"</string> - <!-- no translation found for notification_channel_accessibility_hearing_device (7816963856388758952) --> - <skip /> + <string name="notification_channel_accessibility_hearing_device" msgid="7816963856388758952">"Pajisja ndihmëse për dëgjimin"</string> <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Përdorimi i qasshmërisë"</string> <string name="notification_channel_display" msgid="6905032605735615090">"Ekrani"</string> <string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> po përdor baterinë"</string> @@ -1410,10 +1408,8 @@ <string name="install_carrier_app_notification_button" msgid="6257740533102594290">"Shkarko aplikacionin"</string> <string name="carrier_app_notification_title" msgid="5815477368072060250">"Është futur kartë e re SIM"</string> <string name="carrier_app_notification_text" msgid="6567057546341958637">"Trokit për ta konfiguruar"</string> - <!-- no translation found for time_zone_change_notification_title (5232503069219193218) --> - <skip /> - <!-- no translation found for time_zone_change_notification_body (6135793674904665585) --> - <skip /> + <string name="time_zone_change_notification_title" msgid="5232503069219193218">"Brezi yt orar është ndryshuar"</string> + <string name="time_zone_change_notification_body" msgid="6135793674904665585">"Tani je në <xliff:g id="TIME_ZONE_DISPLAY_NAME">%1$s</xliff:g> (<xliff:g id="TIME_ZONE_OFFSET">%2$s</xliff:g>)"</string> <string name="time_picker_dialog_title" msgid="9053376764985220821">"Cakto kohën"</string> <string name="date_picker_dialog_title" msgid="5030520449243071926">"Vendos datën"</string> <string name="date_time_set" msgid="4603445265164486816">"Cakto"</string> @@ -1801,18 +1797,12 @@ <string name="accessibility_gesture_instructional_text" msgid="4133877896011098550">"Veçoria do të hapet herën tjetër kur të përdorësh këtë shkurtore. Rrëshqit shpejt lart me 2 gishta nga fundi i ekranit dhe lëshoje me shpejtësi."</string> <string name="accessibility_gesture_3finger_instructional_text" msgid="1124458279366968154">"Veçoria do të hapet herën tjetër kur të përdorësh këtë shkurtore. Rrëshqit shpejt lart me 3 gishta nga fundi i ekranit dhe lëshoje me shpejtësi."</string> <string name="accessibility_magnification_chooser_text" msgid="1502075582164931596">"Zmadhimi"</string> - <!-- no translation found for hearing_device_switch_phone_mic_notification_title (6645178038359708836) --> - <skip /> - <!-- no translation found for hearing_device_switch_hearing_mic_notification_title (4612074852145289569) --> - <skip /> - <!-- no translation found for hearing_device_switch_phone_mic_notification_text (1332426273666077412) --> - <skip /> - <!-- no translation found for hearing_device_switch_hearing_mic_notification_text (8288368365767284208) --> - <skip /> - <!-- no translation found for hearing_device_notification_switch_button (3619524619430941300) --> - <skip /> - <!-- no translation found for hearing_device_notification_settings_button (6673651052880279178) --> - <skip /> + <string name="hearing_device_switch_phone_mic_notification_title" msgid="6645178038359708836">"Dëshiron të kalosh te mikrofoni i telefonit?"</string> + <string name="hearing_device_switch_hearing_mic_notification_title" msgid="4612074852145289569">"Dëshiron të kalosh te mikrofoni i aparatit të dëgjimit?"</string> + <string name="hearing_device_switch_phone_mic_notification_text" msgid="1332426273666077412">"Për tingull më të mirë ose nëse bateria e aparatit të dëgjimit është në nivel të ulët. Kjo vetëm ndërron mikrofonin gjatë telefonatës."</string> + <string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Mund ta përdorësh mikrofonin e aparatit të dëgjimit për telefonata pa përdorur duart. Kjo vetëm ndërron mikrofonin gjatë telefonatës."</string> + <string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Ndërro"</string> + <string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Cilësimet"</string> <string name="user_switched" msgid="7249833311585228097">"Emri i përdoruesit aktual: <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="user_switching_message" msgid="1912993630661332336">"Po kalon në \"<xliff:g id="NAME">%1$s</xliff:g>\"…"</string> <string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g> po del…"</string> @@ -2469,8 +2459,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Përmbajtja e aplikacionit është fshehur nga ndarja e ekranit për arsye sigurie"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"U lidh automatikisht me satelitin"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"Mund të dërgosh dhe të marrësh mesazhe pa një rrjet celular apo rrjet Wi-Fi"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"Mund të dërgosh dhe të marrësh mesazhe, si dhe të përdorësh të dhëna pa kufi nëpërmjet satelitit"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"Të përdoret shkëmbimi i mesazheve nëpërmjet satelitit?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"Dërgo dhe merr mesazhe pa një rrjet celular ose Wi-Fi"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"Hap \"Mesazhet\""</string> diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml index 33150dc3c3c2..c286a65a78e7 100644 --- a/core/res/res/values-sr/strings.xml +++ b/core/res/res/values-sr/strings.xml @@ -2460,8 +2460,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Садржај апликације је скривен за дељење садржаја екрана због безбедности"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Аутоматски повезано са сателитом"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"Можете да шаљете и примате поруке без мобилне или WiFi мреже"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"Можете да шаљете и примате поруке, као и да користите ограничену количину података преко сателита"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"Желите да користите сателитску размену порука?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"Шаљите и примајте поруке без мобилне или WiFi мреже"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"Отвори Messages"</string> diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml index ceb0fe1d6654..a2fcd231854c 100644 --- a/core/res/res/values-sv/strings.xml +++ b/core/res/res/values-sv/strings.xml @@ -2459,8 +2459,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Av säkerhetsskäl döljs appinnehållet vid skärmdelning"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Automatiskt ansluten till satellit"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"Du kan skicka och ta emot meddelanden utan mobil- eller wifi-nätverk"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"Du kan skicka och ta emot meddelanden och använda begränsad data via satellit"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"Vill du använda satellitmeddelanden?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"Skicka och ta emot meddelanden utan ett mobil- eller wifi-nätverk"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"Öppna Messages"</string> diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml index 58d71817a078..93233d91e0df 100644 --- a/core/res/res/values-sw/strings.xml +++ b/core/res/res/values-sw/strings.xml @@ -2459,8 +2459,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Maudhui ya programu yamefichwa ili yasionekane kwenye skrini ya pamoja kwa sababu za kiusalama"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Imeunganishwa kiotomatiki na satelaiti"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"Unaweza kutuma na kupokea ujumbe bila mtandao wa simu au Wi-Fi"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"Unaweza kutuma na kupokea ujumbe na kutumia kiwango kidogo cha data kupitia setilaiti"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"Ungependa kutuma ujumbe kupitia setilaiti?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"Tuma na upokee ujumbe bila kutumia mtandao wa simu wala Wi-Fi"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"Fungua Programu ya Messages"</string> diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml index 59668bbff71d..38f9175cc919 100644 --- a/core/res/res/values-ta/strings.xml +++ b/core/res/res/values-ta/strings.xml @@ -2459,8 +2459,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"பாதுகாப்பிற்காக, திரைப் பகிர்வில் இருந்து ஆப்ஸ் உள்ளடக்கம் மறைக்கப்பட்டுள்ளது"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"சாட்டிலைட்டுடன் தானாக இணைக்கப்பட்டது"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"மொபைல்/வைஃபை நெட்வொர்க் இல்லாமல் நீங்கள் மெசேஜ்களை அனுப்பலாம் பெறலாம்"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"சாட்டிலைட் மூலம் நீங்கள் மெசேஜ்களை அனுப்பலாம் பெறலாம், வரம்பிடப்பட்ட டேட்டாவைப் பயன்படுத்தலாம்"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"சாட்டிலைட் மெசேஜிங்கைப் பயன்படுத்தவா?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"மொபைல்/வைஃபை நெட்வொர்க் இல்லாமல் மெசேஜ்களை அனுப்பலாம், பெறலாம்"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages ஆப்ஸைத் திறக்கவும்"</string> diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml index 6285d776cfab..2a99f758daf7 100644 --- a/core/res/res/values-te/strings.xml +++ b/core/res/res/values-te/strings.xml @@ -2459,8 +2459,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"సెక్యూరిటీ కోసం స్క్రీన్ షేర్ నుండి యాప్ కంటెంట్ దాచబడింది"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"శాటిలైట్కు ఆటోమేటిక్గా కనెక్ట్ చేయబడింది"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"మీరు మొబైల్ లేదా Wi-Fi నెట్వర్క్ లేకుండా మెసేజ్లను పంపవచ్చు, స్వీకరించవచ్చు"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"మీరు శాటిలైట్ సర్వీస్ ద్వారా మెసేజ్లను పంపవచ్చు, స్వీకరించవచ్చు, పరిమిత డేటాను ఉపయోగించండి"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"శాటిలైట్ మెసేజింగ్ను ఉపయోగించాలనుకుంటున్నారా?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"మొబైల్ లేదా Wi-Fi నెట్వర్క్ లేకుండా మెసేజ్లను పంపండి, స్వీకరించండి"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messagesను తెరవండి"</string> diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml index 557286128053..8f8252584757 100644 --- a/core/res/res/values-th/strings.xml +++ b/core/res/res/values-th/strings.xml @@ -2459,8 +2459,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"ซ่อนเนื้อหาแอปจากการแชร์หน้าจอแล้วเพื่อความปลอดภัย"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"เชื่อมต่อกับดาวเทียมโดยอัตโนมัติ"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"คุณรับส่งข้อความผ่านดาวเทียมได้โดยไม่ต้องใช้เครือข่ายมือถือหรือ Wi-Fi"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"คุณจะรับส่งข้อความและใช้อินเทอร์เน็ตแบบจำกัดผ่านดาวเทียมได้"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"ใช้การรับส่งข้อความผ่านดาวเทียมไหม"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"รับและส่งข้อความโดยไม่ต้องใช้เครือข่ายมือถือหรือ Wi-Fi"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"เปิด Messages"</string> diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml index 44832c87a3a7..092e15c5289f 100644 --- a/core/res/res/values-tl/strings.xml +++ b/core/res/res/values-tl/strings.xml @@ -2459,8 +2459,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Nakatago ang content ng app mula sa pagbabahagi ng screen para sa seguridad"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Awtomatikong nakakonekta sa satellite"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"Puwede kang magpadala at tumanggap ng mga mensahe nang walang mobile o Wi-Fi network"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"Puwede kang magpadala at tumanggap ng mga mensahe at gumamit ng limitadong data sa satellite"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"Gumamit ng satellite messaging?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"Magpadala at tumanggap ng mga mensahe nang walang mobile o Wi-Fi network"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"Buksan ang Messages"</string> diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml index 1a8d373a2aa2..e04ad55e395f 100644 --- a/core/res/res/values-tr/strings.xml +++ b/core/res/res/values-tr/strings.xml @@ -2459,8 +2459,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Uygulama içerikleri, güvenlik nedeniyle ekran paylaşımında gizlendi"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Uyduya otomatik olarak bağlandı"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"Mobil veya kablosuz ağa bağlı olmadan mesaj alıp gönderebilirsiniz"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"Uydu üzerinden mesaj gönderip alabilir ve sınırlı miktarda veri kullanabilirsiniz"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"Uydu üzerinden mesajlaşma kullanılsın mı?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"Mobil veya kablosuz ağ kullanmadan mesaj gönderip alın"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"Mesajlar\'ı aç"</string> diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml index 5e6f9ec90037..a78847339b15 100644 --- a/core/res/res/values-uk/strings.xml +++ b/core/res/res/values-uk/strings.xml @@ -304,8 +304,7 @@ <string name="notification_channel_network_alerts" msgid="6312366315654526528">"Сповіщення мережі"</string> <string name="notification_channel_network_available" msgid="6083697929214165169">"Мережа доступна"</string> <string name="notification_channel_vpn" msgid="1628529026203808999">"Статус мережі VPN"</string> - <!-- no translation found for notification_channel_system_time (1660313368058030441) --> - <skip /> + <string name="notification_channel_system_time" msgid="1660313368058030441">"Час і часові пояси"</string> <string name="notification_channel_device_admin" msgid="6384932669406095506">"Сповіщення від ІТ-адміністратора"</string> <string name="notification_channel_alerts" msgid="5070241039583668427">"Сповіщення"</string> <string name="notification_channel_retail_mode" msgid="3732239154256431213">"Демо-режим для роздрібної торгівлі"</string> @@ -313,8 +312,7 @@ <string name="notification_channel_heavy_weight_app" msgid="17455756500828043">"Працює додаток"</string> <string name="notification_channel_foreground_service" msgid="7102189948158885178">"Додатки, що використовують заряд акумулятора"</string> <string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Збільшення"</string> - <!-- no translation found for notification_channel_accessibility_hearing_device (7816963856388758952) --> - <skip /> + <string name="notification_channel_accessibility_hearing_device" msgid="7816963856388758952">"Слуховий апарат"</string> <string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Використання спеціальних можливостей"</string> <string name="notification_channel_display" msgid="6905032605735615090">"Дисплей"</string> <string name="foreground_service_app_in_background" msgid="1439289699671273555">"Додаток <xliff:g id="APP_NAME">%1$s</xliff:g> використовує заряд акумулятора"</string> @@ -1412,10 +1410,8 @@ <string name="install_carrier_app_notification_button" msgid="6257740533102594290">"Завантажити додаток"</string> <string name="carrier_app_notification_title" msgid="5815477368072060250">"Вставлено нову SIM-карту"</string> <string name="carrier_app_notification_text" msgid="6567057546341958637">"Торкніться, щоб налаштувати"</string> - <!-- no translation found for time_zone_change_notification_title (5232503069219193218) --> - <skip /> - <!-- no translation found for time_zone_change_notification_body (6135793674904665585) --> - <skip /> + <string name="time_zone_change_notification_title" msgid="5232503069219193218">"Часовий пояс змінено"</string> + <string name="time_zone_change_notification_body" msgid="6135793674904665585">"Ви зараз у часовому поясі \"<xliff:g id="TIME_ZONE_DISPLAY_NAME">%1$s</xliff:g>\" (<xliff:g id="TIME_ZONE_OFFSET">%2$s</xliff:g>)"</string> <string name="time_picker_dialog_title" msgid="9053376764985220821">"Установити час"</string> <string name="date_picker_dialog_title" msgid="5030520449243071926">"Установити дату"</string> <string name="date_time_set" msgid="4603445265164486816">"Застосувати"</string> @@ -1803,18 +1799,12 @@ <string name="accessibility_gesture_instructional_text" msgid="4133877896011098550">"Функція відкриється, коли ви наступного разу скористаєтеся цією швидкою командою. Проведіть двома пальцями вгору від низу екрана й швидко відпустіть."</string> <string name="accessibility_gesture_3finger_instructional_text" msgid="1124458279366968154">"Функція відкриється, коли ви наступного разу скористаєтеся цією швидкою командою. Проведіть трьома пальцями вгору від низу екрана й швидко відпустіть."</string> <string name="accessibility_magnification_chooser_text" msgid="1502075582164931596">"Збільшення"</string> - <!-- no translation found for hearing_device_switch_phone_mic_notification_title (6645178038359708836) --> - <skip /> - <!-- no translation found for hearing_device_switch_hearing_mic_notification_title (4612074852145289569) --> - <skip /> - <!-- no translation found for hearing_device_switch_phone_mic_notification_text (1332426273666077412) --> - <skip /> - <!-- no translation found for hearing_device_switch_hearing_mic_notification_text (8288368365767284208) --> - <skip /> - <!-- no translation found for hearing_device_notification_switch_button (3619524619430941300) --> - <skip /> - <!-- no translation found for hearing_device_notification_settings_button (6673651052880279178) --> - <skip /> + <string name="hearing_device_switch_phone_mic_notification_title" msgid="6645178038359708836">"Перемкнути на мікрофон телефона?"</string> + <string name="hearing_device_switch_hearing_mic_notification_title" msgid="4612074852145289569">"Перемкнути на мікрофон слухового апарата?"</string> + <string name="hearing_device_switch_phone_mic_notification_text" msgid="1332426273666077412">"Використовується, щоб покращити якість звуку або якщо акумулятор слухового апарата розряджений. Мікрофон перемикатиметься лише на час дзвінка."</string> + <string name="hearing_device_switch_hearing_mic_notification_text" msgid="8288368365767284208">"Ви можете використовувати мікрофон слухового апарата для голосового керування викликами. Мікрофон перемикатиметься лише на час дзвінка."</string> + <string name="hearing_device_notification_switch_button" msgid="3619524619430941300">"Перемкнути"</string> + <string name="hearing_device_notification_settings_button" msgid="6673651052880279178">"Налаштування"</string> <string name="user_switched" msgid="7249833311585228097">"Поточний користувач: <xliff:g id="NAME">%1$s</xliff:g>."</string> <string name="user_switching_message" msgid="1912993630661332336">"Перехід у режим \"<xliff:g id="NAME">%1$s</xliff:g>\"…"</string> <string name="user_logging_out_message" msgid="7216437629179710359">"Вихід з облікового запису користувача <xliff:g id="NAME">%1$s</xliff:g>…"</string> @@ -2471,8 +2461,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"З міркувань безпеки вміст додатка приховано під час показу екрана"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Автоматично підключено до супутника"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"Ви можете надсилати й отримувати повідомлення, не використовуючи Wi-Fi або мобільну мережу"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"Ви можете надсилати й отримувати повідомлення та використовувати обмежений обсяг даних через супутник"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"Скористатися супутниковим обміном повідомленнями?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"Надсилайте й отримуйте текстові повідомлення без мобільної мережі або Wi-Fi"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"Відкрийте Повідомлення"</string> diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml index 7ddd7cb8a02a..7f3a2ef7ed31 100644 --- a/core/res/res/values-ur/strings.xml +++ b/core/res/res/values-ur/strings.xml @@ -2459,8 +2459,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"سیکیورٹی کے مد نظر ایپ کا مواد اسکرین کے اشتراک سے چھپا ہوا ہے"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"سٹلائٹ سے خودکار طور پر منسلک ہے"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"آپ موبائل یا Wi-Fi نیٹ ورک کے بغیر پیغامات بھیج اور موصول کر سکتے ہیں"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"آپ سیٹلائٹ کے ذریعے پیغامات بھیج اور موصول کر سکتے ہیں اور محدود ڈیٹا استعمال کر سکتے ہیں"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"سیٹلائٹ پیغام رسانی کا استعمال کریں؟"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"موبائل یا Wi-Fi نیٹ ورک کے بغیر پیغامات بھیجیں اور موصول کریں"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"پیغامات ایپ کو کھولیں"</string> diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml index c1697953eb53..9f24b987033b 100644 --- a/core/res/res/values-uz/strings.xml +++ b/core/res/res/values-uz/strings.xml @@ -2459,8 +2459,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Ekran namoyishida xavfsizlik maqsadida ilova kontenti berkitildi"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Sputnikka avtomatik ulandi"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"Mobil yoki Wi-Fi tarmoqsiz xabarlarni yuborishingiz va qabul qilishingiz mumkin"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"Sputnik orqali xabarlar yuborish va qabul qilish hamda cheklangan trafikdan foydalanish mumkin"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"Sputnik orqali xabarlashuv ishlatilsinmi?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"Mobil yoki Wi-Fi tarmoq blan aloqa yoʻqligida xabar yuboring va qabul qiling"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"Xabarlar ilovasini ochish"</string> diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml index 11b57703d339..678f3e09f123 100644 --- a/core/res/res/values-vi/strings.xml +++ b/core/res/res/values-vi/strings.xml @@ -2459,8 +2459,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Nội dung ứng dụng bị ẩn khỏi tính năng chia sẻ màn hình vì lý do bảo mật"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Đã tự động kết nối với vệ tinh"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"Bạn có thể gửi và nhận tin nhắn mà không cần có mạng di động hoặc mạng Wi-Fi"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"Bạn có thể gửi và nhận tin nhắn cũng như sử dụng dữ liệu hạn chế qua vệ tinh"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"Sử dụng tính năng nhắn tin qua vệ tinh?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"Gửi và nhận tin nhắn mà không cần mạng di động hoặc Wi-Fi"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"Mở ứng dụng Tin nhắn"</string> diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml index 4a38b933b000..c5cad4bc9503 100644 --- a/core/res/res/values-zh-rCN/strings.xml +++ b/core/res/res/values-zh-rCN/strings.xml @@ -2459,8 +2459,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"为安全起见,屏幕共享画面已隐藏此应用的内容"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"自动连接到卫星"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"您无需使用移动网络或 WLAN 网络便能收发消息"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"您可以使用有限的数据通过卫星收发消息"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"使用卫星消息功能?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"即使没有移动网络或 WLAN 网络,也能收发消息"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"打开“信息”应用"</string> diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml index cd428895c4d0..0fb351d12681 100644 --- a/core/res/res/values-zh-rHK/strings.xml +++ b/core/res/res/values-zh-rHK/strings.xml @@ -2459,8 +2459,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"為安全起見,應用程式內容已從分享螢幕畫面隱藏"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"已自動連線至衛星"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"你可在沒有流動/Wi-Fi 網絡的情況下收發訊息"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"你可透過衛星傳送和接收訊息,以及使用有限數據"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"要使用衛星訊息嗎?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"在沒有流動網絡或 Wi-Fi 網絡的情況下收發短訊"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"開啟「訊息」"</string> diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml index c8ae6793a4e3..9e83ce8d44c2 100644 --- a/core/res/res/values-zh-rTW/strings.xml +++ b/core/res/res/values-zh-rTW/strings.xml @@ -2459,8 +2459,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"為安全起見,分享螢幕畫面隱藏了這個應用程式的內容"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"已自動連上衛星"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"你可以收發訊息,沒有行動/Wi-Fi 網路也無妨"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"你可以透過有限的衛星數據收發訊息"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"要使用衛星訊息功能嗎?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"即使沒有行動或 Wi-Fi 網路,還是可以收發訊息"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"開啟「訊息」應用程式"</string> diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml index 7658189f3f55..a2827799c252 100644 --- a/core/res/res/values-zu/strings.xml +++ b/core/res/res/values-zu/strings.xml @@ -2459,8 +2459,7 @@ <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Okuqukethwe kwe-app kufihliwe kusuka ekwabelaneni kwesikrini ngokuvikelwa"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Ixhumeke ngokuzenzakalelayo kusathelayithi"</string> <string name="satellite_notification_summary" msgid="5207364139430767162">"Ungathumela futhi wamukele imilayezo ngaphandle kwenethiwekhi yeselula noma ye-Wi-Fi"</string> - <!-- no translation found for satellite_notification_summary_with_data (6486843676720429049) --> - <skip /> + <string name="satellite_notification_summary_with_data" msgid="6486843676720429049">"Ungathumela futhi wamukele imiyalezo usebenzise nedatha enomkhawulo ngesathelayithi"</string> <string name="satellite_notification_manual_title" msgid="1097504441849466279">"Sebenzisa ukuthumela umyalezo ngesethelayithi?"</string> <string name="satellite_notification_manual_summary" msgid="901206289846283445">"Thumela futhi wamukele imilayezo ngaphandle kwenethiwekhi yeselula noma yeWiFi"</string> <string name="satellite_notification_open_message" msgid="4149234979688273729">"Vula Imilayezo"</string> diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml index 13dd4a35564c..864daf9d2072 100644 --- a/core/res/res/values/colors.xml +++ b/core/res/res/values/colors.xml @@ -582,6 +582,10 @@ <color name="side_fps_text_color">#191C1D</color> <color name="side_fps_button_color">#00677E</color> + <!-- Color for various surfaces related to system-wide blur --> + <color name="surface_effect_0">@color/surface_effect_0_color</color> + <color name="surface_effect_1">@color/surface_effect_1_color</color> + <!-- Color for system bars --> <color name="navigation_bar_compatible">@android:color/black</color> <!-- This uses non-regular transparent intentionally. It is used to tell if the transparent diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index ec1be83dcae6..586cafdd2b57 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2009,6 +2009,10 @@ <!-- Component name of the built in wallpaper used to display bitmap wallpapers. This must not be null. --> <string name="image_wallpaper_component" translatable="false">com.android.systemui/com.android.systemui.wallpapers.ImageWallpaper</string> + <!-- Component name of the built in wallpaper that is used when the user-selected wallpaper is + incompatible with the display's resolution or aspect ratio. --> + <string name="fallback_wallpaper_component" translatable="false">com.android.systemui/com.android.systemui.wallpapers.GradientColorWallpaper</string> + <!-- True if WallpaperService is enabled --> <bool name="config_enableWallpaperService">true</bool> @@ -2889,7 +2893,7 @@ <bool name="config_dozeAlwaysOnEnabled">true</bool> <!-- If AOD can show an ambient version of the wallpaper --> - <bool name="config_dozeSupportsAodWallpaper">true</bool> + <bool name="config_dozeSupportsAodWallpaper">false</bool> <!-- Whether the display blanks itself when transitioning from a doze to a non-doze state --> <bool name="config_displayBlanksAfterDoze">false</bool> @@ -7299,6 +7303,10 @@ <!-- Whether to enable fp unlock when screen turns off on udfps devices --> <bool name="config_screen_off_udfps_enabled">false</bool> + <!-- Default value for fp screen off unlock toggle, it only works for the devices that support + fp screen off unlock--> + <bool name="config_screen_off_udfps_default_on">false</bool> + <!-- The name of the system package that will hold the dependency installer role. --> <string name="config_systemDependencyInstaller" translatable="false" /> diff --git a/core/res/res/values/public-final.xml b/core/res/res/values/public-final.xml index d421944917ea..d8e89318a134 100644 --- a/core/res/res/values/public-final.xml +++ b/core/res/res/values/public-final.xml @@ -3922,4 +3922,86 @@ <public type="color" name="system_error_900" id="0x010600d0" /> <public type="color" name="system_error_1000" id="0x010600d1" /> + <!-- =============================================================== + Resources added in version NEXT of the platform + + NOTE: After this version of the platform is forked, changes cannot be made to the root + branch's groups for that release. Only merge changes to the forked platform branch. + =============================================================== --> + <eat-comment/> + + <staging-public-group-final type="attr" first-id="0x01b70000"> + <public name="removed_" /> + <!-- @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") --> + <public name="adServiceTypes" /> + <!-- @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") --> + <public name="languageSettingsActivity"/> + <!-- @FlaggedApi(android.service.controls.flags.Flags.FLAG_HOME_PANEL_DREAM) --> + <public name="dreamCategory"/> + <!-- @FlaggedApi("android.permission.flags.replace_body_sensor_permission_enabled") + @hide @SystemApi --> + <public name="backgroundPermission"/> + <!-- @FlaggedApi(android.view.accessibility.supplemental_description) --> + <public name="supplementalDescription"/> + <!-- @FlaggedApi("android.security.enable_intent_matching_flags") --> + <public name="intentMatchingFlags"/> + <!-- @FlaggedApi(android.view.inputmethod.Flags.FLAG_IME_SWITCHER_REVAMP_API) --> + <public name="layoutLabel"/> + <public name="removed_" /> + <public name="removed_" /> + <!-- @FlaggedApi(android.content.pm.Flags.FLAG_APP_COMPAT_OPTION_16KB) --> + <public name="pageSizeCompat" /> + <!-- @FlaggedApi(android.nfc.Flags.FLAG_NFC_ASSOCIATED_ROLE_SERVICES) --> + <public name="wantsRoleHolderPriority"/> + <!-- @FlaggedApi(android.sdk.Flags.FLAG_MAJOR_MINOR_VERSIONING_SCHEME) --> + <public name="minSdkVersionFull"/> + <public name="removed_" /> + <public name="removed_" /> + <public name="removed_" /> + <public name="removed_" /> + </staging-public-group-final> + + <!-- @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") --> + <public type="attr" name="adServiceTypes" id="0x010106a4" /> + <!-- @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") --> + <public type="attr" name="languageSettingsActivity" id="0x010106a5" /> + <!-- @FlaggedApi(android.service.controls.flags.Flags.FLAG_HOME_PANEL_DREAM) --> + <public type="attr" name="dreamCategory" id="0x010106a6" /> + <!-- @FlaggedApi("android.permission.flags.replace_body_sensor_permission_enabled") + @hide @SystemApi --> + <public type="attr" name="backgroundPermission" id="0x010106a7" /> + <!-- @FlaggedApi(android.view.accessibility.supplemental_description) --> + <public type="attr" name="supplementalDescription" id="0x010106a8" /> + <!-- @FlaggedApi("android.security.enable_intent_matching_flags") --> + <public type="attr" name="intentMatchingFlags" id="0x010106a9" /> + <!-- @FlaggedApi(android.view.inputmethod.Flags.FLAG_IME_SWITCHER_REVAMP_API) --> + <public type="attr" name="layoutLabel" id="0x010106aa" /> + <!-- @FlaggedApi(android.content.pm.Flags.FLAG_APP_COMPAT_OPTION_16KB) --> + <public type="attr" name="pageSizeCompat" id="0x010106ab" /> + <!-- @FlaggedApi(android.nfc.Flags.FLAG_NFC_ASSOCIATED_ROLE_SERVICES) --> + <public type="attr" name="wantsRoleHolderPriority" id="0x010106ac" /> + <!-- @FlaggedApi(android.sdk.Flags.FLAG_MAJOR_MINOR_VERSIONING_SCHEME) --> + <public type="attr" name="minSdkVersionFull" id="0x010106ad" /> + + <staging-public-group-final type="string" first-id="0x01b40000"> + <!-- @FlaggedApi(android.content.pm.Flags.FLAG_SDK_DEPENDENCY_INSTALLER) + @hide @SystemApi --> + <public name="config_systemDependencyInstaller" /> + <!-- @hide @SystemApi --> + <public name="removed_config_defaultReservedForTestingProfileGroupExclusivity" /> + <!-- @FlaggedApi(android.permission.flags.Flags.FLAG_SYSTEM_VENDOR_INTELLIGENCE_ROLE_ENABLED) + @hide @SystemApi --> + <public name="config_systemVendorIntelligence" /> + <public name="removed_" /> + <public name="removed_" /> + <public name="removed_" /> + </staging-public-group-final> + + <!-- @FlaggedApi(android.content.pm.Flags.FLAG_SDK_DEPENDENCY_INSTALLER) + @hide @SystemApi --> + <public type="string" name="config_systemDependencyInstaller" id="0x0104004a" /> + <!-- @FlaggedApi(android.permission.flags.Flags.FLAG_SYSTEM_VENDOR_INTELLIGENCE_ROLE_ENABLED) + @hide @SystemApi --> + <public type="string" name="config_systemVendorIntelligence" id="0x0104004b" /> + </resources> diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml index 7baaa6d590f2..2d411d0268b3 100644 --- a/core/res/res/values/public-staging.xml +++ b/core/res/res/values/public-staging.xml @@ -109,69 +109,44 @@ =============================================================== --> <eat-comment/> - <staging-public-group type="attr" first-id="0x01b70000"> + <staging-public-group type="attr" first-id="0x01b30000"> <!-- @FlaggedApi("android.content.pm.sdk_lib_independence") --> <public name="optional"/> - <!-- @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") --> - <public name="adServiceTypes" /> - <!-- @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") --> - <public name="languageSettingsActivity"/> - <!-- @FlaggedApi(android.service.controls.flags.Flags.FLAG_HOME_PANEL_DREAM) --> - <public name="dreamCategory"/> - <!-- @FlaggedApi("android.permission.flags.replace_body_sensor_permission_enabled") - @hide @SystemApi --> - <public name="backgroundPermission"/> - <!-- @FlaggedApi(android.view.accessibility.supplemental_description) --> - <public name="supplementalDescription"/> - <!-- @FlaggedApi("android.security.enable_intent_matching_flags") --> - <public name="intentMatchingFlags"/> - <!-- @FlaggedApi(android.view.inputmethod.Flags.FLAG_IME_SWITCHER_REVAMP_API) --> - <public name="layoutLabel"/> <!-- @FlaggedApi(android.content.pm.Flags.FLAG_CHANGE_LAUNCHER_BADGING) --> <public name="alternateLauncherIcons"/> <!-- @FlaggedApi(android.content.pm.Flags.FLAG_CHANGE_LAUNCHER_BADGING) --> <public name="alternateLauncherLabels"/> - <!-- @FlaggedApi(android.content.pm.Flags.FLAG_APP_COMPAT_OPTION_16KB) --> - <public name="pageSizeCompat" /> - <!-- @FlaggedApi(android.nfc.Flags.FLAG_NFC_ASSOCIATED_ROLE_SERVICES) --> - <public name="wantsRoleHolderPriority"/> - <!-- @FlaggedApi(android.sdk.Flags.FLAG_MAJOR_MINOR_VERSIONING_SCHEME) --> - <public name="minSdkVersionFull"/> + <!-- @hide Only for device overlay to use this. --> + <public name="pointerIconVectorFill"/> + <!-- @hide Only for device overlay to use this. --> + <public name="pointerIconVectorFillInverse"/> + <!-- @hide Only for device overlay to use this. --> + <public name="pointerIconVectorStroke"/> + <!-- @hide Only for device overlay to use this. --> + <public name="pointerIconVectorStrokeInverse"/> </staging-public-group> - <staging-public-group type="id" first-id="0x01b60000"> + <staging-public-group type="id" first-id="0x01b20000"> <!-- @FlaggedApi(android.appwidget.flags.Flags.FLAG_ENGAGEMENT_METRICS) --> <public name="remoteViewsMetricsId"/> </staging-public-group> - <staging-public-group type="style" first-id="0x01b50000"> + <staging-public-group type="style" first-id="0x01b10000"> </staging-public-group> - <staging-public-group type="string" first-id="0x01b40000"> - <!-- @FlaggedApi(android.content.pm.Flags.FLAG_SDK_DEPENDENCY_INSTALLER) - @hide @SystemApi --> - <public name="config_systemDependencyInstaller" /> - <!-- @hide @SystemApi --> - <public name="removed_config_defaultReservedForTestingProfileGroupExclusivity" /> - <!-- @FlaggedApi(android.permission.flags.Flags.FLAG_SYSTEM_VENDOR_INTELLIGENCE_ROLE_ENABLED) - @hide @SystemApi --> - <public name="config_systemVendorIntelligence" /> - + <staging-public-group type="string" first-id="0x01b00000"> <!-- @FlaggedApi(android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE_MODULE) @hide @SystemApi --> <public name="config_defaultOnDeviceIntelligenceService" /> - <!-- @FlaggedApi(android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE_MODULE) @hide @SystemApi --> <public name="config_defaultOnDeviceSandboxedInferenceService" /> - <!-- @FlaggedApi(android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE_MODULE) @hide @SystemApi --> <public name="config_defaultOnDeviceIntelligenceDeviceConfigNamespace" /> - </staging-public-group> - <staging-public-group type="dimen" first-id="0x01b30000"> + <staging-public-group type="dimen" first-id="0x01af0000"> <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)--> <public name="config_motionStandardFastSpatialDamping"/> <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)--> @@ -208,7 +183,7 @@ <public name="config_shapeCornerRadiusXlarge"/> </staging-public-group> - <staging-public-group type="color" first-id="0x01b20000"> + <staging-public-group type="color" first-id="0x01ae0000"> <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_COLORS_10_2024)--> <public name="system_inverse_on_surface_light"/> <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_COLORS_10_2024)--> @@ -235,28 +210,28 @@ <public name="system_surface_tint_dark"/> </staging-public-group> - <staging-public-group type="array" first-id="0x01b10000"> + <staging-public-group type="array" first-id="0x01ad0000"> </staging-public-group> - <staging-public-group type="drawable" first-id="0x01b00000"> + <staging-public-group type="drawable" first-id="0x01ac0000"> </staging-public-group> - <staging-public-group type="layout" first-id="0x01af0000"> + <staging-public-group type="layout" first-id="0x01ab0000"> </staging-public-group> - <staging-public-group type="anim" first-id="0x01ae0000"> + <staging-public-group type="anim" first-id="0x01aa0000"> </staging-public-group> - <staging-public-group type="animator" first-id="0x01ad0000"> + <staging-public-group type="animator" first-id="0x01a90000"> </staging-public-group> - <staging-public-group type="interpolator" first-id="0x01ac0000"> + <staging-public-group type="interpolator" first-id="0x01a80000"> </staging-public-group> - <staging-public-group type="mipmap" first-id="0x01ab0000"> + <staging-public-group type="mipmap" first-id="0x01a70000"> </staging-public-group> - <staging-public-group type="integer" first-id="0x01aa0000"> + <staging-public-group type="integer" first-id="0x01a60000"> <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)--> <public name="config_motionStandardFastSpatialStiffness"/> <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)--> @@ -283,16 +258,16 @@ <public name="config_motionExpressiveSlowEffectStiffness"/> </staging-public-group> - <staging-public-group type="transition" first-id="0x01a90000"> + <staging-public-group type="transition" first-id="0x01a50000"> </staging-public-group> - <staging-public-group type="raw" first-id="0x01a80000"> + <staging-public-group type="raw" first-id="0x01a40000"> </staging-public-group> - <staging-public-group type="bool" first-id="0x01a70000"> + <staging-public-group type="bool" first-id="0x01a30000"> </staging-public-group> - <staging-public-group type="fraction" first-id="0x01a60000"> + <staging-public-group type="fraction" first-id="0x01a20000"> </staging-public-group> </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 77cc6868bd58..772a7413a4a7 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -532,6 +532,8 @@ <java-symbol type="bool" name="config_enableUdcSysfsUsbStateUpdate"/> <java-symbol type="bool" name="config_enableSearchTileHideIllustrationInPrivateSpace"/> + <java-symbol type="color" name="surface_effect_0" /> + <java-symbol type="color" name="surface_effect_1" /> <java-symbol type="color" name="tab_indicator_text_v4" /> <java-symbol type="dimen" name="accessibility_touch_slop" /> @@ -2268,6 +2270,7 @@ <java-symbol type="string" name="heavy_weight_notification" /> <java-symbol type="string" name="heavy_weight_notification_detail" /> <java-symbol type="string" name="image_wallpaper_component" /> + <java-symbol type="string" name="fallback_wallpaper_component" /> <java-symbol type="string" name="input_method_binding_label" /> <java-symbol type="string" name="input_method_ime_switch_long_click_action_desc" /> <java-symbol type="string" name="launch_warning_original" /> @@ -5791,6 +5794,9 @@ <!-- Fingerprint screen off unlock config --> <java-symbol type="bool" name="config_screen_off_udfps_enabled" /> + <!-- Default toggle for fp screen of unlcok--> + <java-symbol type="bool" name="config_screen_off_udfps_default_on" /> + <!-- Style for Wear Material3 Button. Will only be used for sdk 36 or above. --> <java-symbol type="style" name="Widget.DeviceDefault.Button.WearMaterial3" /> diff --git a/core/tests/FileSystemUtilsTest/Android.bp b/core/tests/FileSystemUtilsTest/Android.bp index 962ff3c0a6e0..f4d92522bb25 100644 --- a/core/tests/FileSystemUtilsTest/Android.bp +++ b/core/tests/FileSystemUtilsTest/Android.bp @@ -36,10 +36,10 @@ cc_library { ldflags: ["-z max-page-size=0x1000"], } -android_test_helper_app { - name: "app_with_4kb_elf", +java_defaults { + name: "app_with_4kb_elf_defaults", srcs: ["app_with_4kb_elf/src/**/*.java"], - manifest: "app_with_4kb_elf/app_with_4kb_elf.xml", + resource_dirs: ["app_with_4kb_elf/res"], compile_multilib: "64", jni_libs: [ "libpunchtest_4kb", @@ -47,7 +47,36 @@ android_test_helper_app { static_libs: [ "androidx.test.rules", "platform-test-annotations", + "androidx.test.uiautomator_uiautomator", + "sysui-helper", ], +} + +android_test_helper_app { + name: "app_with_4kb_elf", + defaults: ["app_with_4kb_elf_defaults"], + manifest: "app_with_4kb_elf/app_with_4kb_elf.xml", + use_embedded_native_libs: true, +} + +android_test_helper_app { + name: "app_with_4kb_compressed_elf", + defaults: ["app_with_4kb_elf_defaults"], + manifest: "app_with_4kb_elf/app_with_4kb_elf.xml", + use_embedded_native_libs: false, +} + +android_test_helper_app { + name: "page_size_compat_disabled_app", + defaults: ["app_with_4kb_elf_defaults"], + manifest: "app_with_4kb_elf/page_size_compat_disabled.xml", + use_embedded_native_libs: true, +} + +android_test_helper_app { + name: "app_with_4kb_elf_no_override", + defaults: ["app_with_4kb_elf_defaults"], + manifest: "app_with_4kb_elf/app_with_4kb_no_override.xml", use_embedded_native_libs: true, } @@ -99,6 +128,9 @@ java_test_host { ":embedded_native_libs_test_app", ":extract_native_libs_test_app", ":app_with_4kb_elf", + ":page_size_compat_disabled_app", + ":app_with_4kb_compressed_elf", + ":app_with_4kb_elf_no_override", ], test_suites: ["general-tests"], test_config: "AndroidTest.xml", diff --git a/core/tests/FileSystemUtilsTest/AndroidTest.xml b/core/tests/FileSystemUtilsTest/AndroidTest.xml index 651a7ca15dac..27f49b2289ba 100644 --- a/core/tests/FileSystemUtilsTest/AndroidTest.xml +++ b/core/tests/FileSystemUtilsTest/AndroidTest.xml @@ -22,7 +22,6 @@ <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 index b9d6d4db2c81..d7a37336cbc3 100644 --- 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 @@ -18,7 +18,6 @@ <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" diff --git a/core/tests/FileSystemUtilsTest/app_with_4kb_elf/app_with_4kb_no_override.xml b/core/tests/FileSystemUtilsTest/app_with_4kb_elf/app_with_4kb_no_override.xml new file mode 100644 index 000000000000..b0b5204d6e80 --- /dev/null +++ b/core/tests/FileSystemUtilsTest/app_with_4kb_elf/app_with_4kb_no_override.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android.test.pagesizecompat"> + <application + android:label="PageSizeCompatTestApp"> + <uses-library android:name="android.test.runner"/> + <activity android:name=".MainActivity" + android:exported="true" + android:label="Home page" + 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/page_size_compat_disabled.xml b/core/tests/FileSystemUtilsTest/app_with_4kb_elf/page_size_compat_disabled.xml new file mode 100644 index 000000000000..641c5e741014 --- /dev/null +++ b/core/tests/FileSystemUtilsTest/app_with_4kb_elf/page_size_compat_disabled.xml @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android.test.pagesizecompat"> + <application + android:pageSizeCompat="disabled"> + <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/res/res/color-night/resolver_profile_tab_text.xml b/core/tests/FileSystemUtilsTest/app_with_4kb_elf/res/layout/hello.xml index 61a5140582d0..473f3f9f9402 100644 --- a/core/res/res/color-night/resolver_profile_tab_text.xml +++ b/core/tests/FileSystemUtilsTest/app_with_4kb_elf/res/layout/hello.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2022 The Android Open Source Project +<!-- Copyright (C) 2025 The Android Open Source Project Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -13,8 +13,16 @@ See the License for the specific language governing permissions and limitations under the License. --> -<selector xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"> - <item android:color="?androidprv:attr/textColorPrimaryInverse" android:state_selected="true"/> - <item android:color="?androidprv:attr/textColorSecondary"/> -</selector> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:keepScreenOn="true"> + <TextView + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:text="this is a test activity" + /> +</LinearLayout> + 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 index 893f9cd01497..5d8d8081b0e5 100644 --- 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 @@ -19,6 +19,7 @@ package android.test.pagesizecompat; import android.app.Activity; import android.content.Intent; import android.os.Bundle; +import android.view.View; import androidx.annotation.VisibleForTesting; @@ -43,6 +44,8 @@ public class MainActivity extends Activity { @Override public void onCreate(Bundle savedOnstanceState) { super.onCreate(savedOnstanceState); + View view = getLayoutInflater().inflate(R.layout.hello, null); + setContentView(view); Intent received = getIntent(); int op1 = received.getIntExtra(KEY_OPERAND_1, -1); 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 index 9cbe414a0993..7d05c64f7624 100644 --- 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 @@ -16,6 +16,8 @@ package android.test.pagesizecompat; +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; + import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -23,6 +25,10 @@ import android.content.IntentFilter; import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; +import androidx.test.uiautomator.By; +import androidx.test.uiautomator.UiDevice; +import androidx.test.uiautomator.UiObject2; +import androidx.test.uiautomator.Until; import org.junit.Assert; import org.junit.Test; @@ -33,9 +39,10 @@ import java.util.concurrent.TimeUnit; @RunWith(AndroidJUnit4.class) public class PageSizeCompatTest { + private static final String WARNING_TEXT = "PageSizeCompatTestApp"; + private static final long TIMEOUT = 5000; - @Test - public void testPageSizeCompat_embedded4KbLib() throws Exception { + public void testPageSizeCompat_appLaunch(boolean shouldPass) throws Exception { Context context = InstrumentationRegistry.getContext(); CountDownLatch receivedSignal = new CountDownLatch(1); @@ -62,6 +69,30 @@ public class PageSizeCompatTest { launchIntent.putExtra(MainActivity.KEY_OPERAND_2, op2); context.startActivity(launchIntent); - Assert.assertTrue("Failed to launch app", receivedSignal.await(10, TimeUnit.SECONDS)); + UiDevice device = UiDevice.getInstance(getInstrumentation()); + device.waitForWindowUpdate(null, TIMEOUT); + + Assert.assertEquals(receivedSignal.await(10, TimeUnit.SECONDS), shouldPass); + } + + @Test + public void testPageSizeCompat_compatEnabled() throws Exception { + testPageSizeCompat_appLaunch(true); + } + + @Test + public void testPageSizeCompat_compatDisabled() throws Exception { + testPageSizeCompat_appLaunch(false); + } + + @Test + public void testPageSizeCompat_compatByAlignmentChecks() throws Exception { + testPageSizeCompat_appLaunch(true); + + //verify warning dialog + UiDevice device = UiDevice.getInstance(getInstrumentation()); + device.waitForWindowUpdate(null, TIMEOUT); + UiObject2 targetObject = device.wait(Until.findObject(By.text(WARNING_TEXT)), TIMEOUT); + Assert.assertTrue(targetObject != null); } } 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 aed907a0242f..208d74e49afe 100644 --- a/core/tests/FileSystemUtilsTest/src/com/android/internal/content/FileSystemUtilsTest.java +++ b/core/tests/FileSystemUtilsTest/src/com/android/internal/content/FileSystemUtilsTest.java @@ -17,10 +17,12 @@ package com.android.internal.content; import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeTrue; import android.platform.test.annotations.AppModeFull; import com.android.tradefed.device.DeviceNotAvailableException; +import com.android.tradefed.targetprep.TargetSetupError; import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; @@ -29,6 +31,12 @@ import org.junit.runner.RunWith; @RunWith(DeviceJUnit4ClassRunner.class) public class FileSystemUtilsTest extends BaseHostJUnit4Test { + private static final String PAGE_SIZE_COMPAT_ENABLED = "app_with_4kb_elf.apk"; + private static final String PAGE_SIZE_COMPAT_DISABLED = "page_size_compat_disabled_app.apk"; + private static final String PAGE_SIZE_COMPAT_ENABLED_COMPRESSED_ELF = + "app_with_4kb_compressed_elf.apk"; + private static final String PAGE_SIZE_COMPAT_ENABLED_BY_PLATFORM = + "app_with_4kb_elf_no_override.apk"; @Test @AppModeFull @@ -48,12 +56,50 @@ public class FileSystemUtilsTest extends BaseHostJUnit4Test { runDeviceTests(appPackage, appPackage + "." + testName); } - @Test - @AppModeFull - public void runAppWith4KbLib_overrideCompatMode() throws DeviceNotAvailableException { + private void runPageSizeCompatTest(String appName, String testMethodName) + throws DeviceNotAvailableException, TargetSetupError { + getDevice().enableAdbRoot(); + String result = getDevice().executeShellCommand("getconf PAGE_SIZE"); + assumeTrue("16384".equals(result.strip())); + installPackage(appName, "-r"); String appPackage = "android.test.pagesizecompat"; String testName = "PageSizeCompatTest"; assertTrue(isPackageInstalled(appPackage)); - runDeviceTests(appPackage, appPackage + "." + testName); + assertTrue(runDeviceTests(appPackage, appPackage + "." + testName, + testMethodName)); + uninstallPackage(appPackage); + } + + @Test + @AppModeFull + public void runAppWith4KbLib_overrideCompatMode() + throws DeviceNotAvailableException, TargetSetupError { + runPageSizeCompatTest(PAGE_SIZE_COMPAT_ENABLED, "testPageSizeCompat_compatEnabled"); + } + + @Test + @AppModeFull + public void runAppWith4KbCompressedLib_overrideCompatMode() + throws DeviceNotAvailableException, TargetSetupError { + runPageSizeCompatTest(PAGE_SIZE_COMPAT_ENABLED_COMPRESSED_ELF, + "testPageSizeCompat_compatEnabled"); + } + + @Test + @AppModeFull + public void runAppWith4KbLib_disabledCompatMode() + throws DeviceNotAvailableException, TargetSetupError { + // This test is expected to fail since compat is disabled in manifest + runPageSizeCompatTest(PAGE_SIZE_COMPAT_DISABLED, + "testPageSizeCompat_compatDisabled"); + } + + @Test + @AppModeFull + public void runAppWith4KbLib_compatByAlignmentChecks() + throws DeviceNotAvailableException, TargetSetupError { + // This test is expected to fail since compat is disabled in manifest + runPageSizeCompatTest(PAGE_SIZE_COMPAT_ENABLED_BY_PLATFORM, + "testPageSizeCompat_compatByAlignmentChecks"); } } diff --git a/core/tests/coretests/src/android/hardware/display/DisplayTopologyTest.kt b/core/tests/coretests/src/android/hardware/display/DisplayTopologyTest.kt index 008db5ef114e..6d6b56754000 100644 --- a/core/tests/coretests/src/android/hardware/display/DisplayTopologyTest.kt +++ b/core/tests/coretests/src/android/hardware/display/DisplayTopologyTest.kt @@ -635,8 +635,7 @@ class DisplayTopologyTest { // 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), ) verifyDisplay(root, id = 0, width = 30f, height = 30f, noOfChildren = 1) @@ -644,7 +643,7 @@ class DisplayTopologyTest { root.children[0], id = 1, width = 30f, height = 30f, POSITION_RIGHT, offset = 10f, noOfChildren = 1) verifyDisplay( - root.children[0].children[0], id = 2, width = 29.5f, height = 30f, POSITION_BOTTOM, + root.children[0].children[0], id = 2, width = 29.5f, height = 30f, POSITION_RIGHT, offset = 30f, noOfChildren = 0) } @@ -702,6 +701,76 @@ class DisplayTopologyTest { } @Test + fun rearrange_preferLongHorizontalShiftOverAttachToCorner() { + // An earlier implementation decided vertical or horizontal clamp direction based on the abs + // value of the overlap in each dimension, rather than the raw overlap. + + // This horizontal span is twice the height of displays, making abs(xOverlap) > yOverlap, + // i.e. abs(-60) > 30 + // | + // |----| + // 000 111 + // 000 111 + // 000 111 + + // Before fix: + // 000 + // 000 + // 000 + // 111 + // 111 + // 111 + + // After fix: + // 000111 + // 000111 + // 000111 + + val root = rearrangeRects( + RectF(0f, 0f, 30f, 30f), + RectF(90f, 0f, 120f, 30f), + ) + + verifyDisplay(root, id = 0, width = 30f, height = 30f, noOfChildren = 1) + verifyDisplay( + root.children[0], id = 1, width = 30f, height = 30f, POSITION_RIGHT, + offset = 0f, noOfChildren = 0) + } + + @Test + fun rearrange_preferLongVerticalShiftOverAttachToCorner() { + // Before: + // 111 + // 111 + // 111 + // | + // |- This vertical span is 40dp + // | + // | + // 000 + // 000 + // 000 + + // After: + // 111 + // 111 + // 111 + // 000 + // 000 + // 000 + + val root = rearrangeRects( + RectF(20f, 70f, 50f, 100f), + RectF(00f, 0f, 30f, 30f), + ) + + verifyDisplay(root, id = 0, width = 30f, height = 30f, noOfChildren = 1) + verifyDisplay( + root.children[0], id = 1, width = 30f, height = 30f, POSITION_TOP, + offset = -20f, noOfChildren = 0) + } + + @Test fun copy() { val display1 = DisplayTopology.TreeNode(/* displayId= */ 1, /* width= */ 200f, /* height= */ 600f, /* position= */ 0, /* offset= */ 0f) diff --git a/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java b/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java index 1bdb006c3465..9f1580cb8b57 100644 --- a/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java +++ b/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java @@ -124,7 +124,8 @@ public class NotificationRankingUpdateTest { getRankingAdjustment(i), isBubble(i), getProposedImportance(i), - hasSensitiveContent(i) + hasSensitiveContent(i), + getSummarization(i) ); rankings[i] = ranking; } @@ -334,6 +335,17 @@ public class NotificationRankingUpdateTest { } /** + * Produces a String that can be used to represent getSummarization, based on the provided + * index. + */ + public static String getSummarization(int index) { + if ((android.app.Flags.nmSummarizationUi() || android.app.Flags.nmSummarization())) { + return "summary " + index; + } + return null; + } + + /** * Produces a boolean that can be used to represent isBubble, based on the provided index. */ public static boolean isBubble(int index) { @@ -461,7 +473,8 @@ public class NotificationRankingUpdateTest { /* rankingAdjustment= */ 0, /* isBubble= */ false, /* proposedImportance= */ 0, - /* sensitiveContent= */ false + /* sensitiveContent= */ false, + /* summarization = */ null ); return ranking; } @@ -550,7 +563,8 @@ public class NotificationRankingUpdateTest { tweak.getRankingAdjustment(), tweak.isBubble(), tweak.getProposedImportance(), - tweak.hasSensitiveContent() + tweak.hasSensitiveContent(), + tweak.getSummarization() ); assertNotEquals(nru, nru2); } diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java index c40137f1bd34..cef6970ba25a 100644 --- a/core/tests/coretests/src/android/view/ViewRootImplTest.java +++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java @@ -1054,7 +1054,8 @@ public class ViewRootImplTest { ViewRootImpl viewRootImpl = mView.getViewRootImpl(); sInstrumentation.runOnMainSync(() -> { mView.invalidate(); - viewRootImpl.notifyInsetsAnimationRunningStateChanged(true); + viewRootImpl.notifyInsetsAnimationRunningStateChanged(true, 0 /* animationType */, + 0 /* insetsTypes */ /* areOtherAnimationsRunning */); mView.invalidate(); }); sInstrumentation.waitForIdleSync(); diff --git a/core/tests/coretests/src/android/window/DesktopExperienceFlagsTest.java b/core/tests/coretests/src/android/window/DesktopExperienceFlagsTest.java new file mode 100644 index 000000000000..cc06f3d21332 --- /dev/null +++ b/core/tests/coretests/src/android/window/DesktopExperienceFlagsTest.java @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.window; + +import static com.android.window.flags.Flags.FLAG_SHOW_DESKTOP_EXPERIENCE_DEV_OPTION; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assume.assumeTrue; +import static org.junit.Assume.assumeFalse; + +import android.content.Context; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.annotations.Presubmit; +import android.platform.test.flag.junit.FlagsParameterization; +import android.platform.test.flag.junit.SetFlagsRule; +import android.support.test.uiautomator.UiDevice; +import android.window.DesktopExperienceFlags.DesktopExperienceFlag; + +import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.window.flags.Flags; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import platform.test.runner.parameterized.ParameterizedAndroidJunit4; +import platform.test.runner.parameterized.Parameters; + +import java.lang.reflect.Field; +import java.util.List; + +/** + * Test class for {@link android.window.DesktopExperienceFlags} + * + * <p>Build/Install/Run: atest FrameworksCoreTests:DesktopExperienceFlagsTest + */ +@SmallTest +@Presubmit +@RunWith(ParameterizedAndroidJunit4.class) +public class DesktopExperienceFlagsTest { + + @Parameters(name = "{0}") + public static List<FlagsParameterization> getParams() { + return FlagsParameterization.allCombinationsOf(FLAG_SHOW_DESKTOP_EXPERIENCE_DEV_OPTION); + } + + @Rule public SetFlagsRule mSetFlagsRule; + + private UiDevice mUiDevice; + private Context mContext; + private boolean mLocalFlagValue = false; + private final DesktopExperienceFlag mOverriddenLocalFlag = + new DesktopExperienceFlag(() -> mLocalFlagValue, true); + private final DesktopExperienceFlag mNotOverriddenLocalFlag = + new DesktopExperienceFlag(() -> mLocalFlagValue, false); + + private static final String OVERRIDE_OFF_SETTING = "0"; + private static final String OVERRIDE_ON_SETTING = "1"; + private static final String OVERRIDE_INVALID_SETTING = "garbage"; + + public DesktopExperienceFlagsTest(FlagsParameterization flags) { + mSetFlagsRule = new SetFlagsRule(flags); + } + + @Before + public void setUp() throws Exception { + mContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); + setSysProp(null); + } + + @After + public void tearDown() throws Exception { + resetCache(); + setSysProp(null); + } + + @Test + public void isTrue_overrideOff_featureFlagOn_returnsTrue() throws Exception { + mLocalFlagValue = true; + setSysProp(OVERRIDE_OFF_SETTING); + + assertThat(mOverriddenLocalFlag.isTrue()).isTrue(); + assertThat(mNotOverriddenLocalFlag.isTrue()).isTrue(); + } + + @Test + public void isTrue_overrideOn_featureFlagOn_returnsTrue() throws Exception { + mLocalFlagValue = true; + setSysProp(OVERRIDE_ON_SETTING); + + assertThat(mOverriddenLocalFlag.isTrue()).isTrue(); + assertThat(mNotOverriddenLocalFlag.isTrue()).isTrue(); + } + + @Test + public void isTrue_overrideOff_featureFlagOff_returnsFalse() throws Exception { + mLocalFlagValue = false; + setSysProp(OVERRIDE_OFF_SETTING); + + assertThat(mOverriddenLocalFlag.isTrue()).isFalse(); + assertThat(mNotOverriddenLocalFlag.isTrue()).isFalse(); + } + + @Test + public void isTrue_devOptionEnabled_overrideOn_featureFlagOff() throws Exception { + assumeTrue(Flags.showDesktopExperienceDevOption()); + mLocalFlagValue = false; + setSysProp(OVERRIDE_ON_SETTING); + + assertThat(mOverriddenLocalFlag.isTrue()).isTrue(); + assertThat(mNotOverriddenLocalFlag.isTrue()).isFalse(); + } + + @Test + public void isTrue_devOptionDisabled_overrideOn_featureFlagOff_returnsFalse() throws Exception { + assumeFalse(Flags.showDesktopExperienceDevOption()); + mLocalFlagValue = false; + setSysProp(OVERRIDE_ON_SETTING); + + assertThat(mOverriddenLocalFlag.isTrue()).isFalse(); + assertThat(mNotOverriddenLocalFlag.isTrue()).isFalse(); + } + + private void setSysProp(String value) throws Exception { + if (value == null) { + resetSysProp(); + } else { + mUiDevice.executeShellCommand( + "setprop " + DesktopModeFlags.SYSTEM_PROPERTY_NAME + " " + value); + } + } + + private void resetSysProp() throws Exception { + mUiDevice.executeShellCommand("setprop " + DesktopModeFlags.SYSTEM_PROPERTY_NAME + " ''"); + } + + private void resetCache() throws Exception { + Field cachedToggleOverride = + DesktopExperienceFlags.class.getDeclaredField("sCachedToggleOverride"); + cachedToggleOverride.setAccessible(true); + cachedToggleOverride.set(null, null); + } +} diff --git a/core/tests/coretests/src/android/window/DesktopModeFlagsTest.java b/core/tests/coretests/src/android/window/DesktopModeFlagsTest.java index b28e2b04b342..49927be65ae5 100644 --- a/core/tests/coretests/src/android/window/DesktopModeFlagsTest.java +++ b/core/tests/coretests/src/android/window/DesktopModeFlagsTest.java @@ -21,33 +21,42 @@ import static android.window.DesktopModeFlags.ToggleOverride.OVERRIDE_OFF; import static android.window.DesktopModeFlags.ToggleOverride.OVERRIDE_ON; import static android.window.DesktopModeFlags.ToggleOverride.OVERRIDE_UNSET; import static android.window.DesktopModeFlags.ToggleOverride.fromSetting; -import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TRANSITIONS; import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE; -import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS; +import static com.android.window.flags.Flags.FLAG_SHOW_DESKTOP_EXPERIENCE_DEV_OPTION; import static com.android.window.flags.Flags.FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assume.assumeTrue; + import android.content.ContentResolver; import android.content.Context; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; +import android.platform.test.flag.junit.FlagsParameterization; import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; +import android.support.test.uiautomator.UiDevice; +import android.window.DesktopModeFlags.DesktopModeFlag; -import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; +import com.android.window.flags.Flags; + import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import platform.test.runner.parameterized.ParameterizedAndroidJunit4; +import platform.test.runner.parameterized.Parameters; + import java.lang.reflect.Field; +import java.util.List; /** * Test class for {@link android.window.DesktopModeFlags} @@ -57,21 +66,39 @@ import java.lang.reflect.Field; */ @SmallTest @Presubmit -@RunWith(AndroidJUnit4.class) +@RunWith(ParameterizedAndroidJunit4.class) public class DesktopModeFlagsTest { + @Parameters(name = "{0}") + public static List<FlagsParameterization> getParams() { + return FlagsParameterization.allCombinationsOf(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, + FLAG_SHOW_DESKTOP_EXPERIENCE_DEV_OPTION); + } + @Rule - public SetFlagsRule setFlagsRule = new SetFlagsRule(); + public SetFlagsRule mSetFlagsRule; + private UiDevice mUiDevice; private Context mContext; + private boolean mLocalFlagValue = false; + private final DesktopModeFlag mOverriddenLocalFlag = new DesktopModeFlag( + () -> mLocalFlagValue, true); + private final DesktopModeFlag mNotOverriddenLocalFlag = new DesktopModeFlag( + () -> mLocalFlagValue, false); private static final int OVERRIDE_OFF_SETTING = 0; private static final int OVERRIDE_ON_SETTING = 1; private static final int OVERRIDE_UNSET_SETTING = -1; + public DesktopModeFlagsTest(FlagsParameterization flags) { + mSetFlagsRule = new SetFlagsRule(flags); + } + @Before - public void setUp() { + public void setUp() throws Exception { mContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); + setOverride(null); } @After @@ -80,26 +107,35 @@ public class DesktopModeFlagsTest { } @Test - @DisableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - public void isTrue_devOptionFlagDisabled_overrideOff_featureFlagOn_returnsTrue() { + public void isTrue_overrideOff_featureFlagOn() throws Exception { setOverride(OVERRIDE_OFF_SETTING); - // In absence of dev options, follow flag - assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isTrue()).isTrue(); + + if (showDesktopWindowingDevOpts()) { + // DW Dev Opts turns off flags when ON + assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isTrue()).isFalse(); + } else { + // DE Dev Opts doesn't turn flags OFF + assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isTrue()).isTrue(); + } } @Test - @DisableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE}) - public void isTrue_devOptionFlagDisabled_overrideOn_featureFlagOff_returnsFalse() { + @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + public void isTrue_overrideOn_featureFlagOff() throws Exception { setOverride(OVERRIDE_ON_SETTING); - assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isTrue()).isFalse(); + if (showAnyDevOpts()) { + assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isTrue()).isTrue(); + } else { + assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isTrue()).isFalse(); + } } @Test - @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE}) - public void isTrue_overrideUnset_featureFlagOn_returnsTrue() { + @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + public void isTrue_overrideUnset_featureFlagOn() throws Exception { setOverride(OVERRIDE_UNSET_SETTING); // For overridableFlag, for unset overrides, follow flag @@ -107,9 +143,8 @@ public class DesktopModeFlagsTest { } @Test - @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - public void isTrue_overrideUnset_featureFlagOff_returnsFalse() { + public void isTrue_overrideUnset_featureFlagOff() throws Exception { setOverride(OVERRIDE_UNSET_SETTING); // For overridableFlag, for unset overrides, follow flag @@ -117,8 +152,8 @@ public class DesktopModeFlagsTest { } @Test - @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE}) - public void isTrue_noOverride_featureFlagOn_returnsTrue() { + @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + public void isTrue_noOverride_featureFlagOn_returnsTrue() throws Exception { setOverride(null); // For overridableFlag, in absence of overrides, follow flag @@ -126,9 +161,8 @@ public class DesktopModeFlagsTest { } @Test - @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - public void isTrue_noOverride_featureFlagOff_returnsFalse() { + public void isTrue_noOverride_featureFlagOff_returnsFalse() throws Exception { setOverride(null); // For overridableFlag, in absence of overrides, follow flag @@ -136,8 +170,8 @@ public class DesktopModeFlagsTest { } @Test - @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE}) - public void isTrue_unrecognizableOverride_featureFlagOn_returnsTrue() { + @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + public void isTrue_unrecognizableOverride_featureFlagOn_returnsTrue() throws Exception { setOverride(-2); // For overridableFlag, for unrecognized overrides, follow flag @@ -145,9 +179,8 @@ public class DesktopModeFlagsTest { } @Test - @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - public void isTrue_unrecognizableOverride_featureFlagOff_returnsFalse() { + public void isTrue_unrecognizableOverride_featureFlagOff_returnsFalse() throws Exception { setOverride(-2); // For overridableFlag, for unrecognizable overrides, follow flag @@ -155,27 +188,10 @@ public class DesktopModeFlagsTest { } @Test - @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE}) - public void isTrue_overrideOff_featureFlagOn_returnsFalse() { - setOverride(OVERRIDE_OFF_SETTING); - - // For overridableFlag, follow override if they exist - assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isTrue()).isFalse(); - } - - @Test - @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) - @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - public void isTrue_overrideOn_featureFlagOff_returnsTrue() { - setOverride(OVERRIDE_ON_SETTING); - - // For overridableFlag, follow override if they exist - assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isTrue()).isTrue(); - } + @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + public void isTrue_overrideOffThenOn_featureFlagOn_returnsFalseAndFalse() throws Exception { + assumeTrue(showDesktopWindowingDevOpts()); - @Test - @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE}) - public void isTrue_overrideOffThenOn_featureFlagOn_returnsFalseAndFalse() { setOverride(OVERRIDE_OFF_SETTING); // For overridableFlag, follow override if they exist @@ -188,9 +204,9 @@ public class DesktopModeFlagsTest { } @Test - @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - public void isTrue_overrideOnThenOff_featureFlagOff_returnsTrueAndTrue() { + public void isTrue_overrideOnThenOff_featureFlagOff_returnsTrueAndTrue() throws Exception { + assumeTrue(showAnyDevOpts()); setOverride(OVERRIDE_ON_SETTING); // For overridableFlag, follow override if they exist @@ -203,146 +219,144 @@ public class DesktopModeFlagsTest { } @Test - @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE, - FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS}) - public void isTrue_dwFlagOn_overrideUnset_featureFlagOn_returnsTrue() { + @EnableFlags({FLAG_ENABLE_DESKTOP_WINDOWING_MODE}) + public void isTrue_dwFlagOn_overrideUnset_featureFlagOn() throws Exception { + mLocalFlagValue = true; setOverride(OVERRIDE_UNSET_SETTING); // For unset overrides, follow flag - assertThat(ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isTrue(); + assertThat(mOverriddenLocalFlag.isTrue()).isTrue(); + assertThat(mNotOverriddenLocalFlag.isTrue()).isTrue(); } @Test - @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE}) - @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS) - public void isTrue_dwFlagOn_overrideUnset_featureFlagOff_returnsFalse() { + @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + public void isTrue_dwFlagOn_overrideUnset_featureFlagOff() throws Exception { + mLocalFlagValue = false; setOverride(OVERRIDE_UNSET_SETTING); // For unset overrides, follow flag - assertThat(ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isFalse(); + assertThat(mOverriddenLocalFlag.isTrue()).isFalse(); + assertThat(mNotOverriddenLocalFlag.isTrue()).isFalse(); } @Test - @EnableFlags({ - FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, - FLAG_ENABLE_DESKTOP_WINDOWING_MODE, - FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS - }) - public void isTrue_dwFlagOn_overrideOn_featureFlagOn_returnsTrue() { + @EnableFlags({FLAG_ENABLE_DESKTOP_WINDOWING_MODE}) + public void isTrue_dwFlagOn_overrideOn_featureFlagOn() throws Exception { + mLocalFlagValue = true; setOverride(OVERRIDE_ON_SETTING); // When toggle override matches its default state (dw flag), don't override flags - assertThat(ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isTrue(); + assertThat(mOverriddenLocalFlag.isTrue()).isTrue(); + assertThat(mNotOverriddenLocalFlag.isTrue()).isTrue(); } @Test - @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE}) - @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS) - public void isTrue_dwFlagOn_overrideOn_featureFlagOff_returnsFalse() { + @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + public void isTrue_dwFlagOn_overrideOn_featureFlagOff() throws Exception { + mLocalFlagValue = false; setOverride(OVERRIDE_ON_SETTING); - // When toggle override matches its default state (dw flag), don't override flags - assertThat(ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isFalse(); + if (showDesktopExperienceDevOpts()) { + assertThat(mOverriddenLocalFlag.isTrue()).isTrue(); + } else { + assertThat(mOverriddenLocalFlag.isTrue()).isFalse(); + } + assertThat(mNotOverriddenLocalFlag.isTrue()).isFalse(); } @Test - @EnableFlags({ - FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, - FLAG_ENABLE_DESKTOP_WINDOWING_MODE, - FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS - }) - public void isTrue_dwFlagOn_overrideOff_featureFlagOn_returnsTrue() { + @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + public void isTrue_dwFlagOn_overrideOff_featureFlagOn() throws Exception { + mLocalFlagValue = true; setOverride(OVERRIDE_OFF_SETTING); - // Follow override if they exist, and is not equal to default toggle state (dw flag) - assertThat(ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isTrue(); + if (showDesktopWindowingDevOpts()) { + // Follow override if they exist, and is not equal to default toggle state (dw flag) + assertThat(mOverriddenLocalFlag.isTrue()).isFalse(); + } else { + assertThat(mOverriddenLocalFlag.isTrue()).isTrue(); + } + assertThat(mNotOverriddenLocalFlag.isTrue()).isTrue(); } @Test - @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE}) - @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS) - public void isTrue_dwFlagOn_overrideOff_featureFlagOff_returnsFalse() { + @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + public void isTrue_dwFlagOn_overrideOff_featureFlagOff_returnsFalse() throws Exception { + mLocalFlagValue = false; setOverride(OVERRIDE_OFF_SETTING); // Follow override if they exist, and is not equal to default toggle state (dw flag) - assertThat(ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isFalse(); + assertThat(mOverriddenLocalFlag.isTrue()).isFalse(); + assertThat(mNotOverriddenLocalFlag.isTrue()).isFalse(); } @Test - @EnableFlags({ - FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, - FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS - }) @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - public void isTrue_dwFlagOff_overrideUnset_featureFlagOn_returnsTrue() { + public void isTrue_dwFlagOff_overrideUnset_featureFlagOn_returnsTrue() throws Exception { + mLocalFlagValue = true; setOverride(OVERRIDE_UNSET_SETTING); // For unset overrides, follow flag - assertThat(ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isTrue(); + assertThat(mOverriddenLocalFlag.isTrue()).isTrue(); + assertThat(mNotOverriddenLocalFlag.isTrue()).isTrue(); } @Test - @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) - @DisableFlags({ - FLAG_ENABLE_DESKTOP_WINDOWING_MODE, - FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS - }) - public void isTrue_dwFlagOff_overrideUnset_featureFlagOff_returnsFalse() { + @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + public void isTrue_dwFlagOff_overrideUnset_featureFlagOff_returnsFalse() throws Exception { + mLocalFlagValue = false; setOverride(OVERRIDE_UNSET_SETTING); // For unset overrides, follow flag - assertThat(ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isFalse(); + assertThat(mOverriddenLocalFlag.isTrue()).isFalse(); + assertThat(mNotOverriddenLocalFlag.isTrue()).isFalse(); } @Test - @EnableFlags({ - FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, - FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS - }) @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - public void isTrue_dwFlagOff_overrideOn_featureFlagOn_returnsTrue() { + public void isTrue_dwFlagOff_overrideOn_featureFlagOn_returnsTrue() throws Exception { + mLocalFlagValue = true; setOverride(OVERRIDE_ON_SETTING); // Follow override if they exist, and is not equal to default toggle state (dw flag) - assertThat(ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isTrue(); + assertThat(mOverriddenLocalFlag.isTrue()).isTrue(); + assertThat(mNotOverriddenLocalFlag.isTrue()).isTrue(); } @Test - @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) - @DisableFlags({ - FLAG_ENABLE_DESKTOP_WINDOWING_MODE, - FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS - }) - public void isTrue_dwFlagOff_overrideOn_featureFlagOff_returnFalse() { + @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + public void isTrue_dwFlagOff_overrideOn_featureFlagOff() throws Exception { + mLocalFlagValue = false; setOverride(OVERRIDE_ON_SETTING); - // Follow override if they exist, and is not equal to default toggle state (dw flag) - assertThat(ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isFalse(); + if (showAnyDevOpts()) { + assertThat(mOverriddenLocalFlag.isTrue()).isTrue(); + } else { + // Follow override if they exist, and is not equal to default toggle state (dw flag) + assertThat(mOverriddenLocalFlag.isTrue()).isFalse(); + } + assertThat(mNotOverriddenLocalFlag.isTrue()).isFalse(); } @Test - @EnableFlags({ - FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, - FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS - }) @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - public void isTrue_dwFlagOff_overrideOff_featureFlagOn_returnsTrue() { + public void isTrue_dwFlagOff_overrideOff_featureFlagOn_returnsTrue() throws Exception { + mLocalFlagValue = true; setOverride(OVERRIDE_OFF_SETTING); // When toggle override matches its default state (dw flag), don't override flags - assertThat(ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isTrue(); + assertThat(mOverriddenLocalFlag.isTrue()).isTrue(); + assertThat(mNotOverriddenLocalFlag.isTrue()).isTrue(); } @Test - @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) - @DisableFlags({ - FLAG_ENABLE_DESKTOP_WINDOWING_MODE, - FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS - }) - public void isTrue_dwFlagOff_overrideOff_featureFlagOff_returnsFalse() { + @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + public void isTrue_dwFlagOff_overrideOff_featureFlagOff_returnsFalse() throws Exception { + mLocalFlagValue = false; setOverride(OVERRIDE_OFF_SETTING); - // When toggle override matches its default state (dw flag), don't override flags - assertThat(ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isFalse(); + assertThat(mOverriddenLocalFlag.isTrue()).isFalse(); + assertThat(mNotOverriddenLocalFlag.isTrue()).isFalse(); } @Test @@ -365,7 +379,9 @@ public class DesktopModeFlagsTest { assertThat(OVERRIDE_UNSET.getSetting()).isEqualTo(-1); } - private void setOverride(Integer setting) { + private void setOverride(Integer setting) throws Exception { + setSysProp(setting); + ContentResolver contentResolver = mContext.getContentResolver(); String key = Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES; @@ -376,11 +392,35 @@ public class DesktopModeFlagsTest { } } + private void setSysProp(Integer value) throws Exception { + if (value == null) { + resetSysProp(); + } else { + mUiDevice.executeShellCommand( + "setprop " + DesktopModeFlags.SYSTEM_PROPERTY_NAME + " " + value); + } + } + + private void resetSysProp() throws Exception { + mUiDevice.executeShellCommand("setprop " + DesktopModeFlags.SYSTEM_PROPERTY_NAME + " ''"); + } + private void resetCache() throws Exception { Field cachedToggleOverride = DesktopModeFlags.class.getDeclaredField( "sCachedToggleOverride"); cachedToggleOverride.setAccessible(true); cachedToggleOverride.set(null, null); - setOverride(OVERRIDE_UNSET_SETTING); + } + + private boolean showDesktopWindowingDevOpts() { + return Flags.showDesktopWindowingDevOption() && !Flags.showDesktopExperienceDevOption(); + } + + private boolean showDesktopExperienceDevOpts() { + return Flags.showDesktopExperienceDevOption(); + } + + private boolean showAnyDevOpts() { + return Flags.showDesktopWindowingDevOption() || Flags.showDesktopExperienceDevOption(); } } diff --git a/core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java b/core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java index d898d222b8de..36c73e2e979e 100644 --- a/core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java +++ b/core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java @@ -187,6 +187,9 @@ public class InstallOverlayTests extends BaseHostJUnit4Test { shell("cmd overlay list " + APP_OVERLAY_PACKAGE_NAME).trim()); assertEquals("STATE_ENABLED", shell("cmd overlay dump state " + APP_OVERLAY_PACKAGE_NAME).trim()); + + assertEquals("STATE_ENABLED", + shell("cmd overlay dump --user current state " + APP_OVERLAY_PACKAGE_NAME).trim()); } private void delay() { diff --git a/data/etc/com.android.systemui.xml b/data/etc/com.android.systemui.xml index 45952ea75b6f..3eadf3b94515 100644 --- a/data/etc/com.android.systemui.xml +++ b/data/etc/com.android.systemui.xml @@ -95,5 +95,6 @@ <permission name="android.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND" /> <permission name="android.permission.OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW"/> <permission name="android.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE" /> + <permission name="android.permission.SET_UNRESTRICTED_GESTURE_EXCLUSION" /> </privapp-permissions> </permissions> diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java index b332cf0d751f..3d4dccf095f5 100644 --- a/graphics/java/android/graphics/Paint.java +++ b/graphics/java/android/graphics/Paint.java @@ -2198,10 +2198,12 @@ public class Paint { * is configured as {@code 'wght' 500, 'ital' 1}, and if the override is specified as * {@code 'wght' 700, `wdth` 150}, then the effective font variation setting is * {@code `wght' 700, 'ital' 1, 'wdth' 150}. The `wght` value is updated by override, 'ital' - * value is preserved because no overrides, and `wdth` value is added by override. + * value is preserved because no overrides, and `wdth` value is added by override. If the font + * variation override is empty or null, nothing overrides and original font variation settings + * assigned to the font instance is used as it is. * - * @param fontVariationOverride font variation settings. You can pass null or empty string as - * no variation settings. + * @param fontVariationOverride font variation override. You can pass null or empty string for + * clearing font variation override. * * @return true if the provided font variation settings is valid. Otherwise returns false. * diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java index d0d1721115cb..1bcb0bb91515 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -18,6 +18,7 @@ package androidx.window.extensions.embedding; import static android.app.ActivityManager.START_SUCCESS; import static android.app.ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN; +import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.view.Display.DEFAULT_DISPLAY; @@ -3154,15 +3155,22 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen final WindowContainerTransaction wct = transactionRecord.getTransaction(); final TaskFragmentContainer launchedInTaskFragment; if (launchingActivity != null) { - final int taskId = getTaskId(launchingActivity); final String overlayTag = options.getString(KEY_OVERLAY_TAG); if (Flags.activityEmbeddingOverlayPresentationFlag() && overlayTag != null) { launchedInTaskFragment = createOrUpdateOverlayTaskFragmentIfNeeded(wct, options, intent, launchingActivity); } else { - launchedInTaskFragment = resolveStartActivityIntent(wct, taskId, intent, - launchingActivity); + final int taskId = getTaskId(launchingActivity); + if (taskId != INVALID_TASK_ID) { + launchedInTaskFragment = resolveStartActivityIntent(wct, taskId, intent, + launchingActivity); + } else { + // We cannot get a valid task id of launchingActivity so we fall back to + // treat it as a non-Activity context. + launchedInTaskFragment = + resolveStartActivityIntentFromNonActivityContext(wct, intent); + } } } else { launchedInTaskFragment = resolveStartActivityIntentFromNonActivityContext(wct, diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig index e4210261a9fc..065644627393 100644 --- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig +++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig @@ -5,12 +5,6 @@ package: "com.android.wm.shell" container: "system" -flag { - name: "enable_app_pairs" - namespace: "multitasking" - description: "Enables the ability to create and save app pairs to the Home screen" - bug: "274835596" -} flag { name: "enable_taskbar_navbar_unification" @@ -27,13 +21,6 @@ flag { } flag { - name: "enable_left_right_split_in_portrait" - namespace: "multitasking" - description: "Enables left/right split in portrait" - bug: "291018646" -} - -flag { name: "enable_new_bubble_animations" namespace: "multitasking" description: "Enables new animations for expand and collapse for bubbles" 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 dd387b382dc6..09a93d501f8e 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 @@ -296,5 +296,9 @@ class BubbleControllerBubbleBarTest { override fun onBubbleStateChange(update: BubbleBarUpdate?) {} override fun animateBubbleBarLocation(location: BubbleBarLocation?) {} + + override fun onDragItemOverBubbleBarDragZone(location: BubbleBarLocation) {} + + override fun onItemDraggedOutsideBubbleBarDropZone() {} } } diff --git a/libs/WindowManager/Shell/res/values-fa/strings.xml b/libs/WindowManager/Shell/res/values-fa/strings.xml index eb50ba7c9477..f9a3c355773c 100644 --- a/libs/WindowManager/Shell/res/values-fa/strings.xml +++ b/libs/WindowManager/Shell/res/values-fa/strings.xml @@ -79,7 +79,7 @@ <string name="bubbles_user_education_description" msgid="4215862563054175407">"مکالمههای جدید بهصورت نمادهای شناور یا حبابکها نشان داده میشوند. برای باز کردن حبابکها تکضرب بزنید. برای جابهجایی، آن را بکشید."</string> <string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"کنترل حبابکها در هرزمانی"</string> <string name="bubbles_user_education_manage" msgid="3460756219946517198">"برای خاموش کردن حبابکها از این برنامه، روی «مدیریت» تکضرب بزنید"</string> - <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"متوجهام"</string> + <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"متوجهم"</string> <string name="bubble_overflow_empty_title" msgid="2397251267073294968">"هیچ حبابک جدیدی وجود ندارد"</string> <string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"حبابکهای اخیر و حبابکهای ردشده اینجا ظاهر خواهند شد"</string> <string name="bubble_bar_education_stack_title" msgid="2486903590422497245">"گپ زدن بااستفاده از حبابک"</string> @@ -103,7 +103,7 @@ <string name="letterbox_education_dialog_title" msgid="7739895354143295358">"از چندین برنامه بهطور همزمان استفاده کنید"</string> <string name="letterbox_education_split_screen_text" msgid="449233070804658627">"برای حالت صفحهٔ دونیمه، در برنامهای دیگر بکشید"</string> <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"برای جابهجا کردن برنامه، بیرون از آن دو تکضرب بزنید"</string> - <string name="letterbox_education_got_it" msgid="4057634570866051177">"متوجهام"</string> + <string name="letterbox_education_got_it" msgid="4057634570866051177">"متوجهم"</string> <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"برای اطلاعات بیشتر، گسترده کنید."</string> <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"برای نمایش بهتر بازراهاندازی شود؟"</string> <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"میتوانید برنامه را بازراهاندازی کنید تا بهتر روی صفحهنمایش نشان داده شود، اما ممکن است پیشرفت یا تغییرات ذخیرهنشده را ازدست بدهید"</string> diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java index 2ca011bfe000..0a1e3b9495a0 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java @@ -111,7 +111,7 @@ public class GroupedTaskInfo implements Parcelable { * Create new for a pair of tasks in split screen */ public static GroupedTaskInfo forSplitTasks(@NonNull TaskInfo task1, - @NonNull TaskInfo task2, @Nullable SplitBounds splitBounds) { + @NonNull TaskInfo task2, @NonNull SplitBounds splitBounds) { return new GroupedTaskInfo(List.of(task1, task2), splitBounds, TYPE_SPLIT, null /* minimizedFreeformTasks */); } diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java index 840de2c86f92..4d00c74155a8 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java @@ -127,6 +127,46 @@ public class TransitionUtil { } /** + * Check if all changes in this transition are only ordering changes. If so, we won't animate. + */ + public static boolean isAllOrderOnly(TransitionInfo info) { + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + if (!isOrderOnly(info.getChanges().get(i))) return false; + } + return true; + } + + /** + * Look through a transition and see if all non-closing changes are no-animation. If so, no + * animation should play. + */ + public static boolean isAllNoAnimation(TransitionInfo info) { + if (isClosingType(info.getType())) { + // no-animation is only relevant for launching (open) activities. + return false; + } + boolean hasNoAnimation = false; + final int changeSize = info.getChanges().size(); + for (int i = changeSize - 1; i >= 0; --i) { + final TransitionInfo.Change change = info.getChanges().get(i); + if (isClosingType(change.getMode())) { + // ignore closing apps since they are a side-effect of the transition and don't + // animate. + continue; + } + if (change.hasFlags(TransitionInfo.FLAG_NO_ANIMATION)) { + hasNoAnimation = true; + } else if (!isOrderOnly(change) && !change.hasFlags(TransitionInfo.FLAG_IS_OCCLUDED)) { + // Ignore the order only or occluded changes since they shouldn't be visible during + // animation. For anything else, we need to animate if at-least one relevant + // participant *is* animated, + return false; + } + } + return hasNoAnimation; + } + + /** * Filter that selects leaf-tasks only. THIS IS ORDER-DEPENDENT! For it to work properly, you * MUST call `test` in the same order that the changes appear in the TransitionInfo. */ diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt new file mode 100644 index 000000000000..0ea3c2a80fb4 --- /dev/null +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt @@ -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.wm.shell.shared.desktopmode + +import android.app.TaskInfo +import android.content.Context +import android.window.DesktopModeFlags +import com.android.internal.R + +/** + * Class to decide whether to apply app compat policies in desktop mode. + */ +// TODO(b/347289970): Consider replacing with API +class DesktopModeCompatPolicy(context: Context) { + + private val systemUiPackage: String = context.resources.getString(R.string.config_systemUi) + + /** + * If the top activity should be exempt from desktop windowing and forced back to fullscreen. + * Currently includes all system ui activities and modal dialogs. However if the top activity is + * not being displayed, regardless of its configuration, we will not exempt it as to remain in + * the desktop windowing environment. + */ + fun isTopActivityExemptFromDesktopWindowing(task: TaskInfo) = + isTopActivityExemptFromDesktopWindowing(task.baseActivity?.packageName, + task.numActivities, task.isTopActivityNoDisplay, task.isActivityStackTransparent) + + fun isTopActivityExemptFromDesktopWindowing(packageName: String?, + numActivities: Int, isTopActivityNoDisplay: Boolean, isActivityStackTransparent: Boolean) = + DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue + && ((isSystemUiTask(packageName) + || isTransparentTask(isActivityStackTransparent, numActivities)) + && !isTopActivityNoDisplay) + + /** + * Returns true if all activities in a tasks stack are transparent. If there are no activities + * will return false. + */ + fun isTransparentTask(task: TaskInfo): Boolean = + isTransparentTask(task.isActivityStackTransparent, task.numActivities) + + private fun isTransparentTask(isActivityStackTransparent: Boolean, numActivities: Int) = + isActivityStackTransparent && numActivities > 0 + + private fun isSystemUiTask(packageName: String?) = packageName == systemUiPackage +} 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 2fed1380b635..1ee71ca78815 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 @@ -218,6 +218,13 @@ public class DesktopModeStatus { return isDeviceEligibleForDesktopMode(context) && Flags.showDesktopWindowingDevOption(); } + /** + * Return {@code true} if desktop mode dev option should be shown on current device + */ + public static boolean canShowDesktopExperienceDevOption(@NonNull Context context) { + return Flags.showDesktopExperienceDevOption(); + } + /** Returns if desktop mode dev option should be enabled if there is no user override. */ public static boolean shouldDevOptionBeEnabledByDefault() { return Flags.enableDesktopWindowingMode(); @@ -290,7 +297,7 @@ public class DesktopModeStatus { /** * Return {@code true} if desktop mode is unrestricted and is supported in the device. */ - private static boolean isDeviceEligibleForDesktopMode(@NonNull Context context) { + public static boolean isDeviceEligibleForDesktopMode(@NonNull Context context) { return !enforceDeviceRestrictions() || isDesktopModeSupported(context); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/EventLogTags.logtags b/libs/WindowManager/Shell/src/com/android/wm/shell/EventLogTags.logtags index db960d15c526..b716e9e574fa 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/EventLogTags.logtags +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/EventLogTags.logtags @@ -8,4 +8,4 @@ option java_package com.android.wm.shell 38500 wm_shell_enter_desktop_mode (EnterReason|1|5),(SessionId|1|5) 38501 wm_shell_exit_desktop_mode (ExitReason|1|5),(SessionId|1|5) -38502 wm_shell_desktop_mode_task_update (TaskEvent|1|5),(InstanceId|1|5),(uid|1|5),(TaskHeight|1),(TaskWidth|1),(TaskX|1),(TaskY|1),(SessionId|1|5),(MinimiseReason|1|5),(UnminimiseReason|1|5),(VisibleTaskCount|1) +38502 wm_shell_desktop_mode_task_update (TaskEvent|1|5),(InstanceId|1|5),(uid|1|5),(TaskHeight|1),(TaskWidth|1),(TaskX|1),(TaskY|1),(SessionId|1|5),(MinimiseReason|1|5),(UnminimiseReason|1|5),(VisibleTaskCount|1),(FocusReason|1|5) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStack.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStack.kt deleted file mode 100644 index caacdd355996..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStack.kt +++ /dev/null @@ -1,62 +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.wm.shell.automotive - -import android.app.ActivityManager -import android.graphics.Rect -import android.view.SurfaceControl - -/** - * Represents an auto task stack, which is always in multi-window mode. - * - * @property id The ID of the task stack. - * @property displayId The ID of the display the task stack is on. - * @property leash The surface control leash of the task stack. - */ -interface AutoTaskStack { - val id: Int - val displayId: Int - var leash: SurfaceControl -} - -/** - * Data class representing the state of an auto task stack. - * - * @property bounds The bounds of the task stack. - * @property childrenTasksVisible Whether the child tasks of the stack are visible. - * @property layer The layer of the task stack. - */ -data class AutoTaskStackState( - val bounds: Rect = Rect(), - val childrenTasksVisible: Boolean, - val layer: Int -) - -/** - * Data class representing a root task stack. - * - * @property id The ID of the root task stack - * @property displayId The ID of the display the root task stack is on. - * @property leash The surface control leash of the root task stack. - * @property rootTaskInfo The running task info of the root task. - */ -data class RootTaskStack( - override val id: Int, - override val displayId: Int, - override var leash: SurfaceControl, - var rootTaskInfo: ActivityManager.RunningTaskInfo -) : AutoTaskStack diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStackController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStackController.kt deleted file mode 100644 index 15fedac62af3..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStackController.kt +++ /dev/null @@ -1,229 +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.wm.shell.automotive - -import android.app.PendingIntent -import android.content.Intent -import android.os.Bundle -import android.os.IBinder -import android.view.SurfaceControl -import android.window.TransitionInfo -import android.window.TransitionRequestInfo -import com.android.wm.shell.shared.annotations.ShellMainThread -import com.android.wm.shell.transition.Transitions -import com.android.wm.shell.transition.Transitions.TransitionFinishCallback - -/** - * Delegate interface for handling auto task stack transitions. - */ -interface AutoTaskStackTransitionHandlerDelegate { - /** - * Handles a transition request. - * - * @param transition The transition identifier. - * @param request The transition request information. - * @return An [AutoTaskStackTransaction] to be applied for the transition, or null if the - * animation is not handled by this delegate. - */ - fun handleRequest( - transition: IBinder, request: TransitionRequestInfo - ): AutoTaskStackTransaction? - - /** - * See [Transitions.TransitionHandler.startAnimation] for more details. - * - * @param changedTaskStacks Contains the states of the task stacks that were changed as a - * result of this transition. The key is the [AutoTaskStack.id] and the value is the - * corresponding [AutoTaskStackState]. - */ - fun startAnimation( - transition: IBinder, - changedTaskStacks: Map<Int, AutoTaskStackState>, - info: TransitionInfo, - startTransaction: SurfaceControl.Transaction, - finishTransaction: SurfaceControl.Transaction, - finishCallback: TransitionFinishCallback - ): Boolean - - /** - * See [Transitions.TransitionHandler.onTransitionConsumed] for more details. - * - * @param requestedTaskStacks contains the states of the task stacks that were requested in - * the transition. The key is the [AutoTaskStack.id] and the value is the corresponding - * [AutoTaskStackState]. - */ - fun onTransitionConsumed( - transition: IBinder, - requestedTaskStacks: Map<Int, AutoTaskStackState>, - aborted: Boolean, finishTransaction: SurfaceControl.Transaction? - ) - - /** - * See [Transitions.TransitionHandler.mergeAnimation] for more details. - * - * @param changedTaskStacks Contains the states of the task stacks that were changed as a - * result of this transition. The key is the [AutoTaskStack.id] and the value is the - * corresponding [AutoTaskStackState]. - */ - fun mergeAnimation( - transition: IBinder, - changedTaskStacks: Map<Int, AutoTaskStackState>, - info: TransitionInfo, - surfaceTransaction: SurfaceControl.Transaction, - mergeTarget: IBinder, - finishCallback: TransitionFinishCallback - ) -} - - -/** - * Controller for managing auto task stacks. - */ -interface AutoTaskStackController { - - var autoTransitionHandlerDelegate: AutoTaskStackTransitionHandlerDelegate? - set - - /** - * Map of task stack IDs to their states. - * - * This gets updated right before [AutoTaskStackTransitionHandlerDelegate.startAnimation] or - * [AutoTaskStackTransitionHandlerDelegate.onTransitionConsumed] is called. - */ - val taskStackStateMap: Map<Int, AutoTaskStackState> - get - - /** - * Creates a new multi-window root task. - * - * A root task stack is placed in the default TDA of the specified display by default. - * Once the root task is removed, the [AutoTaskStackController] no longer holds a reference to - * it. - * - * @param displayId The ID of the display to create the root task stack on. - * @param listener The listener for root task stack events. - */ - @ShellMainThread - fun createRootTaskStack(displayId: Int, listener: RootTaskStackListener) - - - /** - * Sets the default root task stack (launch root) on a display. Calling it again with a - * different [rootTaskStackId] will simply replace the default root task stack on the display. - * - * Note: This is helpful for passively routing tasks to a specified container. If a display - * doesn't have a default root task stack set, all tasks will open in fullscreen and cover - * the entire default TDA by default. - * - * @param displayId The ID of the display. - * @param rootTaskStackId The ID of the root task stack, or null to clear the default. - */ - @ShellMainThread - fun setDefaultRootTaskStackOnDisplay(displayId: Int, rootTaskStackId: Int?) - - /** - * Starts a transaction with the specified [transaction]. - * Returns the transition identifier. - */ - @ShellMainThread - fun startTransition(transaction: AutoTaskStackTransaction): IBinder? -} - -internal sealed class TaskStackOperation { - data class ReparentTask( - val taskId: Int, - val parentTaskStackId: Int, - val onTop: Boolean - ) : TaskStackOperation() - - data class SendPendingIntent( - val sender: PendingIntent, - val intent: Intent, - val options: Bundle? - ) : TaskStackOperation() - - data class SetTaskStackState( - val taskStackId: Int, - val state: AutoTaskStackState - ) : TaskStackOperation() -} - -data class AutoTaskStackTransaction internal constructor( - internal val operations: MutableList<TaskStackOperation> = mutableListOf() -) { - constructor() : this( - mutableListOf() - ) - - /** See [WindowContainerTransaction.reparent] for more details. */ - fun reparentTask( - taskId: Int, - parentTaskStackId: Int, - onTop: Boolean - ): AutoTaskStackTransaction { - operations.add(TaskStackOperation.ReparentTask(taskId, parentTaskStackId, onTop)) - return this - } - - /** See [WindowContainerTransaction.sendPendingIntent] for more details. */ - fun sendPendingIntent( - sender: PendingIntent, - intent: Intent, - options: Bundle? - ): AutoTaskStackTransaction { - operations.add(TaskStackOperation.SendPendingIntent(sender, intent, options)) - return this - } - - /** - * Adds a set task stack state operation to the transaction. - * - * If an operation with the same task stack ID already exists, it is replaced with the new one. - * - * @param taskStackId The ID of the task stack. - * @param state The new state of the task stack. - * @return The transaction with the added operation. - */ - fun setTaskStackState(taskStackId: Int, state: AutoTaskStackState): AutoTaskStackTransaction { - val existingOperation = operations.find { - it is TaskStackOperation.SetTaskStackState && it.taskStackId == taskStackId - } - if (existingOperation != null) { - val index = operations.indexOf(existingOperation) - operations[index] = TaskStackOperation.SetTaskStackState(taskStackId, state) - } else { - operations.add(TaskStackOperation.SetTaskStackState(taskStackId, state)) - } - return this - } - - /** - * Returns a map of task stack IDs to their states from the set task stack state operations. - * - * @return The map of task stack IDs to states. - */ - fun getTaskStackStates(): Map<Int, AutoTaskStackState> { - val states = mutableMapOf<Int, AutoTaskStackState>() - operations.forEach { operation -> - if (operation is TaskStackOperation.SetTaskStackState) { - states[operation.taskStackId] = operation.state - } - } - return states - } -} - diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStackControllerImpl.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStackControllerImpl.kt deleted file mode 100644 index 8171312762ef..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStackControllerImpl.kt +++ /dev/null @@ -1,534 +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.wm.shell.automotive - -import android.app.ActivityManager -import android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT -import android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS -import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD -import android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED -import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW -import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED -import android.os.IBinder -import android.util.Log -import android.util.Slog -import android.view.SurfaceControl -import android.view.SurfaceControl.Transaction -import android.view.WindowManager -import android.view.WindowManager.TRANSIT_CHANGE -import android.window.TransitionInfo -import android.window.TransitionRequestInfo -import android.window.WindowContainerTransaction -import com.android.wm.shell.Flags.enableAutoTaskStackController -import com.android.wm.shell.RootTaskDisplayAreaOrganizer -import com.android.wm.shell.ShellTaskOrganizer -import com.android.wm.shell.common.ShellExecutor -import com.android.wm.shell.dagger.WMSingleton -import com.android.wm.shell.shared.TransitionUtil -import com.android.wm.shell.shared.annotations.ShellMainThread -import com.android.wm.shell.sysui.ShellInit -import com.android.wm.shell.transition.Transitions -import com.android.wm.shell.transition.Transitions.TransitionFinishCallback -import javax.inject.Inject - -const val TAG = "AutoTaskStackController" - -@WMSingleton -class AutoTaskStackControllerImpl @Inject constructor( - val taskOrganizer: ShellTaskOrganizer, - @ShellMainThread private val shellMainThread: ShellExecutor, - val transitions: Transitions, - val shellInit: ShellInit, - val rootTdaOrganizer: RootTaskDisplayAreaOrganizer -) : AutoTaskStackController, Transitions.TransitionHandler { - override var autoTransitionHandlerDelegate: AutoTaskStackTransitionHandlerDelegate? = null - override val taskStackStateMap = mutableMapOf<Int, AutoTaskStackState>() - - private val DBG = Log.isLoggable(TAG, Log.DEBUG) - private val taskStackMap = mutableMapOf<Int, AutoTaskStack>() - private val pendingTransitions = ArrayList<PendingTransition>() - private val mTaskStackStateTranslator = TaskStackStateTranslator() - private val appTasksMap = mutableMapOf<Int, ActivityManager.RunningTaskInfo>() - private val defaultRootTaskPerDisplay = mutableMapOf<Int, Int>() - - init { - if (!enableAutoTaskStackController()) { - throw IllegalStateException("Failed to initialize" + - "AutoTaskStackController as the auto_task_stack_windowing TS flag is disabled.") - } else { - shellInit.addInitCallback(this::onInit, this); - } - } - - fun onInit() { - transitions.addHandler(this) - } - - /** Translates the [AutoTaskStackState] to relevant WM and surface transactions. */ - inner class TaskStackStateTranslator { - // TODO(b/384946072): Move to an interface with 2 implementations, one for root task and - // other for TDA - fun applyVisibilityAndBounds( - wct: WindowContainerTransaction, - taskStack: AutoTaskStack, - state: AutoTaskStackState - ) { - if (taskStack !is RootTaskStack) { - Slog.e(TAG, "Unsupported task stack, unable to convertToWct") - return - } - wct.setBounds(taskStack.rootTaskInfo.token, state.bounds) - wct.reorder(taskStack.rootTaskInfo.token, /* onTop= */ state.childrenTasksVisible) - } - - fun reorderLeash( - taskStack: AutoTaskStack, - state: AutoTaskStackState, - transaction: Transaction - ) { - if (taskStack !is RootTaskStack) { - Slog.e(TAG, "Unsupported task stack, unable to reorder leash") - return - } - Slog.d(TAG, "Setting the layer ${state.layer}") - transaction.setLayer(taskStack.leash, state.layer) - } - - fun restoreLeash(taskStack: AutoTaskStack, transaction: Transaction) { - if (taskStack !is RootTaskStack) { - Slog.e(TAG, "Unsupported task stack, unable to restore leash") - return - } - - val rootTdaInfo = rootTdaOrganizer.getDisplayAreaInfo(taskStack.displayId) - if (rootTdaInfo == null || - rootTdaInfo.featureId != taskStack.rootTaskInfo.displayAreaFeatureId - ) { - Slog.e(TAG, "Cannot find the rootTDA for the root task stack ${taskStack.id}") - return - } - if (DBG) { - Slog.d(TAG, "Reparenting ${taskStack.id} leash to DA ${rootTdaInfo.featureId}") - } - transaction.reparent( - taskStack.leash, - rootTdaOrganizer.getDisplayAreaLeash(taskStack.displayId) - ) - } - } - - inner class RootTaskStackListenerAdapter( - val rootTaskStackListener: RootTaskStackListener, - ) : ShellTaskOrganizer.TaskListener { - private var rootTaskStack: RootTaskStack? = null - - // TODO(b/384948029): Notify car service for all the children tasks' events - override fun onTaskAppeared( - taskInfo: ActivityManager.RunningTaskInfo?, - leash: SurfaceControl? - ) { - if (taskInfo == null) { - throw IllegalArgumentException("taskInfo can't be null in onTaskAppeared") - } - if (leash == null) { - throw IllegalArgumentException("leash can't be null in onTaskAppeared") - } - if (DBG) Slog.d(TAG, "onTaskAppeared = ${taskInfo.taskId}") - - if (rootTaskStack == null) { - val rootTask = - RootTaskStack(taskInfo.taskId, taskInfo.displayId, leash, taskInfo) - taskStackMap[rootTask.id] = rootTask - - rootTaskStack = rootTask; - rootTaskStackListener.onRootTaskStackCreated(rootTask); - return - } - appTasksMap[taskInfo.taskId] = taskInfo - rootTaskStackListener.onTaskAppeared(taskInfo, leash) - } - - override fun onTaskInfoChanged(taskInfo: ActivityManager.RunningTaskInfo?) { - if (taskInfo == null) { - throw IllegalArgumentException("taskInfo can't be null in onTaskInfoChanged") - } - if (DBG) Slog.d(TAG, "onTaskInfoChanged = ${taskInfo.taskId}") - var previousRootTaskStackInfo = rootTaskStack ?: run { - Slog.e(TAG, "Received onTaskInfoChanged, when root task stack is null") - return@onTaskInfoChanged - } - rootTaskStack?.let { - if (taskInfo.taskId == previousRootTaskStackInfo.id) { - previousRootTaskStackInfo = previousRootTaskStackInfo.copy(rootTaskInfo = taskInfo) - taskStackMap[previousRootTaskStackInfo.id] = previousRootTaskStackInfo - rootTaskStack = previousRootTaskStackInfo; - rootTaskStackListener.onRootTaskStackInfoChanged(it) - return - } - } - - appTasksMap[taskInfo.taskId] = taskInfo - rootTaskStackListener.onTaskInfoChanged(taskInfo) - } - - override fun onTaskVanished(taskInfo: ActivityManager.RunningTaskInfo?) { - if (taskInfo == null) { - throw IllegalArgumentException("taskInfo can't be null in onTaskVanished") - } - if (DBG) Slog.d(TAG, "onTaskVanished = ${taskInfo.taskId}") - var rootTask = rootTaskStack ?: run { - Slog.e(TAG, "Received onTaskVanished, when root task stack is null") - return@onTaskVanished - } - if (taskInfo.taskId == rootTask.id) { - rootTask = rootTask.copy(rootTaskInfo = taskInfo) - rootTaskStack = rootTask - rootTaskStackListener.onRootTaskStackDestroyed(rootTask) - taskStackMap.remove(rootTask.id) - taskStackStateMap.remove(rootTask.id) - rootTaskStack = null - return - } - appTasksMap.remove(taskInfo.taskId) - rootTaskStackListener.onTaskVanished(taskInfo) - } - - override fun onBackPressedOnTaskRoot(taskInfo: ActivityManager.RunningTaskInfo?) { - if (taskInfo == null) { - throw IllegalArgumentException("taskInfo can't be null in onBackPressedOnTaskRoot") - } - super.onBackPressedOnTaskRoot(taskInfo) - rootTaskStackListener.onBackPressedOnTaskRoot(taskInfo) - } - } - - override fun createRootTaskStack( - displayId: Int, - listener: RootTaskStackListener - ) { - if (!enableAutoTaskStackController()) { - Slog.e( - TAG, "Failed to create root task stack as the " + - "auto_task_stack_windowing TS flag is disabled." - ) - return - } - taskOrganizer.createRootTask( - displayId, - WINDOWING_MODE_MULTI_WINDOW, - RootTaskStackListenerAdapter(listener), - /* removeWithTaskOrganizer= */ true - ) - } - - override fun setDefaultRootTaskStackOnDisplay(displayId: Int, rootTaskStackId: Int?) { - if (!enableAutoTaskStackController()) { - Slog.e( - TAG, "Failed to set default root task stack as the " + - "auto_task_stack_windowing TS flag is disabled." - ) - return - } - var wct = WindowContainerTransaction() - - // Clear the default root task stack if already set - defaultRootTaskPerDisplay[displayId]?.let { existingDefaultRootTaskStackId -> - (taskStackMap[existingDefaultRootTaskStackId] as? RootTaskStack)?.let { rootTaskStack -> - wct.setLaunchRoot(rootTaskStack.rootTaskInfo.token, null, null) - } - } - - if (rootTaskStackId != null) { - var taskStack = - taskStackMap[rootTaskStackId] ?: run { return@setDefaultRootTaskStackOnDisplay } - if (DBG) Slog.d(TAG, "setting launch root for = ${taskStack.id}") - if (taskStack !is RootTaskStack) { - throw IllegalArgumentException( - "Cannot set a non root task stack as default root task " + - "stack" - ) - } - wct.setLaunchRoot( - taskStack.rootTaskInfo.token, - intArrayOf(WINDOWING_MODE_UNDEFINED), - intArrayOf( - ACTIVITY_TYPE_STANDARD, ACTIVITY_TYPE_UNDEFINED, ACTIVITY_TYPE_RECENTS, - - // TODO(b/386242708): Figure out if this flag will ever be used for automotive - // assistant. Based on output, remove it from here and fix the - // AssistantStackTests accordingly. - ACTIVITY_TYPE_ASSISTANT - ) - ) - } - - taskOrganizer.applyTransaction(wct) - } - - override fun startTransition(transaction: AutoTaskStackTransaction): IBinder? { - if (!enableAutoTaskStackController()) { - Slog.e( - TAG, "Failed to start transaction as the " + - "auto_task_stack_windowing TS flag is disabled." - ) - return null - } - if (transaction.operations.isEmpty()) { - Slog.e(TAG, "Operations empty, no transaction started") - return null - } - if (DBG) Slog.d(TAG, "startTransaction ${transaction.operations}") - - var wct = WindowContainerTransaction() - convertToWct(transaction, wct) - var pending = PendingTransition( - TRANSIT_CHANGE, - wct, - transaction, - ) - return startTransitionNow(pending) - } - - override fun handleRequest( - transition: IBinder, - request: TransitionRequestInfo - ): WindowContainerTransaction? { - if (DBG) { - Slog.d(TAG, "handle request, id=${request.debugId}, type=${request.type}, " + - "triggertask = ${request.triggerTask ?: "null"}") - } - val ast = autoTransitionHandlerDelegate?.handleRequest(transition, request) - ?: run { return@handleRequest null } - - if (ast.operations.isEmpty()) { - return null - } - var wct = WindowContainerTransaction() - convertToWct(ast, wct) - - pendingTransitions.add( - PendingTransition(request.type, wct, ast).apply { isClaimed = transition } - ) - return wct - } - - fun updateTaskStackStates(taskStatStates: Map<Int, AutoTaskStackState>) { - taskStackStateMap.putAll(taskStatStates) - } - - override fun startAnimation( - transition: IBinder, - info: TransitionInfo, - startTransaction: Transaction, - finishTransaction: Transaction, - finishCallback: TransitionFinishCallback - ): Boolean { - if (DBG) Slog.d(TAG, " startAnimation, id=${info.debugId} = changes=" + info.changes) - val pending: PendingTransition? = findPending(transition) - if (pending != null) { - pendingTransitions.remove(pending) - updateTaskStackStates(pending.transaction.getTaskStackStates()) - } - - reorderLeashes(startTransaction) - reorderLeashes(finishTransaction) - - for (chg in info.changes) { - // TODO(b/384946072): handle the da stack similarly. The below implementation only - // handles the root task stack - - val taskInfo = chg.taskInfo ?: continue - val taskStack = taskStackMap[taskInfo.taskId] ?: continue - - // Restore the leashes for the task stacks to ensure correct z-order competition - if (taskStackMap.containsKey(taskInfo.taskId)) { - mTaskStackStateTranslator.restoreLeash( - taskStack, - startTransaction - ) - if (TransitionUtil.isOpeningMode(chg.mode)) { - // Clients can still manipulate the alpha, but this ensures that the default - // behavior is natural - startTransaction.setAlpha(chg.leash, 1f) - } - continue - } - } - - val isPlayedByDelegate = autoTransitionHandlerDelegate?.startAnimation( - transition, - pending?.transaction?.getTaskStackStates() ?: mapOf(), - info, - startTransaction, - finishTransaction, - { - shellMainThread.execute { - finishCallback.onTransitionFinished(it) - startNextTransition() - } - } - ) ?: false - - if (isPlayedByDelegate) { - if (DBG) Slog.d(TAG, "${info.debugId} played"); - return true; - } - - // If for an animation which is not played by the delegate, contains a change in a known - // task stack, it should be leveraged to correct the leashes. So, handle the animation in - // this case. - if (info.changes.any { taskStackMap.containsKey(it.taskInfo?.taskId) }) { - startTransaction.apply() - finishCallback.onTransitionFinished(null) - startNextTransition() - if (DBG) Slog.d(TAG, "${info.debugId} played"); - return true - } - return false; - } - - fun convertToWct(ast: AutoTaskStackTransaction, wct: WindowContainerTransaction) { - ast.operations.forEach { operation -> - when (operation) { - is TaskStackOperation.ReparentTask -> { - val appTask = appTasksMap[operation.taskId] - - if (appTask == null) { - Slog.e( - TAG, "task with id=$operation.taskId not found, failed to " + - "reparent." - ) - return@forEach - } - if (!taskStackMap.containsKey(operation.parentTaskStackId)) { - Slog.e( - TAG, "task stack with id=${operation.parentTaskStackId} not " + - "found, failed to reparent" - ) - return@forEach - } - // TODO(b/384946072): Handle a display area stack as well - wct.reparent( - appTask.token, - (taskStackMap[operation.parentTaskStackId] as RootTaskStack) - .rootTaskInfo.token, - operation.onTop - ) - } - - is TaskStackOperation.SendPendingIntent -> wct.sendPendingIntent( - operation.sender, - operation.intent, - operation.options - ) - - is TaskStackOperation.SetTaskStackState -> { - taskStackMap[operation.taskStackId]?.let { taskStack -> - mTaskStackStateTranslator.applyVisibilityAndBounds( - wct, - taskStack, - operation.state - ) - } - ?: Slog.w(TAG, "AutoTaskStack with id ${operation.taskStackId} " + - "not found.") - } - } - } - } - - override fun mergeAnimation( - transition: IBinder, - info: TransitionInfo, - surfaceTransaction: Transaction, - mergeTarget: IBinder, - finishCallback: TransitionFinishCallback - ) { - val pending: PendingTransition? = findPending(transition) - - autoTransitionHandlerDelegate?.mergeAnimation( - transition, - pending?.transaction?.getTaskStackStates() ?: mapOf(), - info, - surfaceTransaction, - mergeTarget, - /* finishCallback = */ { - shellMainThread.execute { - finishCallback.onTransitionFinished(it) - } - } - ) - } - - override fun onTransitionConsumed( - transition: IBinder, - aborted: Boolean, - finishTransaction: Transaction? - ) { - val pending: PendingTransition? = findPending(transition) - if (pending != null) { - pendingTransitions.remove(pending) - updateTaskStackStates(pending.transaction.getTaskStackStates()) - // Still update the surface order because this means wm didn't lead to any change - if (finishTransaction != null) { - reorderLeashes(finishTransaction) - } - } - autoTransitionHandlerDelegate?.onTransitionConsumed( - transition, - pending?.transaction?.getTaskStackStates() ?: mapOf(), - aborted, - finishTransaction - ) - } - - private fun reorderLeashes(transaction: SurfaceControl.Transaction) { - taskStackStateMap.forEach { (taskId, taskStackState) -> - taskStackMap[taskId]?.let { taskStack -> - mTaskStackStateTranslator.reorderLeash(taskStack, taskStackState, transaction) - } ?: Slog.w(TAG, "Warning: AutoTaskStack with id $taskId not found.") - } - } - - private fun findPending(claimed: IBinder) = pendingTransitions.find { it.isClaimed == claimed } - - private fun startTransitionNow(pending: PendingTransition): IBinder { - val claimedTransition = transitions.startTransition(pending.mType, pending.wct, this) - pending.isClaimed = claimedTransition - pendingTransitions.add(pending) - return claimedTransition - } - - fun startNextTransition() { - if (pendingTransitions.isEmpty()) return - val pending: PendingTransition = pendingTransitions[0] - if (pending.isClaimed != null) { - // Wait for this to start animating. - return - } - pending.isClaimed = transitions.startTransition(pending.mType, pending.wct, this) - } - - internal class PendingTransition( - @field:WindowManager.TransitionType @param:WindowManager.TransitionType val mType: Int, - val wct: WindowContainerTransaction, - val transaction: AutoTaskStackTransaction, - ) { - var isClaimed: IBinder? = null - } - -}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/OWNERS deleted file mode 100644 index 84596b015209..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/OWNERS +++ /dev/null @@ -1,6 +0,0 @@ -# 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/automotive/RootTaskStackListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/RootTaskStackListener.kt deleted file mode 100644 index 9d121b144492..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/RootTaskStackListener.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.automotive - -import com.android.wm.shell.ShellTaskOrganizer - -/** - * A [TaskListener] which simplifies the interface when used for - * [ShellTaskOrganizer.createRootTask]. - * - * [onRootTaskStackCreated], [onRootTaskStackInfoChanged], [onRootTaskStackDestroyed] will be called - * for the underlying root task. - * The [onTaskAppeared], [onTaskInfoChanged], [onTaskVanished] are called for the children tasks. - */ -interface RootTaskStackListener : ShellTaskOrganizer.TaskListener { - fun onRootTaskStackCreated(rootTaskStack: RootTaskStack) - fun onRootTaskStackInfoChanged(rootTaskStack: RootTaskStack) - fun onRootTaskStackDestroyed(rootTaskStack: RootTaskStack) -}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java index e8e25e20d8d8..a65e69eee5fe 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java @@ -28,6 +28,7 @@ import android.annotation.Nullable; import android.app.Notification; import android.app.PendingIntent; import android.app.Person; +import android.app.TaskInfo; import android.content.Context; import android.content.Intent; import android.content.LocusId; @@ -57,6 +58,7 @@ import com.android.wm.shell.shared.annotations.ShellBackgroundThread; import com.android.wm.shell.shared.annotations.ShellMainThread; import com.android.wm.shell.shared.bubbles.BubbleInfo; import com.android.wm.shell.shared.bubbles.ParcelableFlyoutMessage; +import com.android.wm.shell.taskview.TaskView; import java.io.PrintWriter; import java.util.List; @@ -204,6 +206,13 @@ public class Bubble implements BubbleViewProvider { private Intent mAppIntent; /** + * Set while preparing a transition for animation. Several steps are needed before animation + * starts, so this is used to detect and route associated events to the coordinating transition. + */ + @Nullable + private BubbleTransitions.BubbleTransition mPreparingTransition; + + /** * Create a bubble with limited information based on given {@link ShortcutInfo}. * Note: Currently this is only being used when the bubble is persisted to disk. */ @@ -280,6 +289,30 @@ public class Bubble implements BubbleViewProvider { mShortcutInfo = info; } + private Bubble( + TaskInfo task, + UserHandle user, + @Nullable Icon icon, + String key, + @ShellMainThread Executor mainExecutor, + @ShellBackgroundThread Executor bgExecutor) { + mGroupKey = null; + mLocusId = null; + mFlags = 0; + mUser = user; + mIcon = icon; + mIsAppBubble = true; + mKey = key; + mShowBubbleUpdateDot = false; + mMainExecutor = mainExecutor; + mBgExecutor = bgExecutor; + mTaskId = task.taskId; + mAppIntent = null; + mDesiredHeight = Integer.MAX_VALUE; + mPackageName = task.baseActivity.getPackageName(); + } + + /** Creates an app bubble. */ public static Bubble createAppBubble(Intent intent, UserHandle user, @Nullable Icon icon, @ShellMainThread Executor mainExecutor, @ShellBackgroundThread Executor bgExecutor) { @@ -291,6 +324,16 @@ public class Bubble implements BubbleViewProvider { mainExecutor, bgExecutor); } + /** Creates a task bubble. */ + public static Bubble createTaskBubble(TaskInfo info, UserHandle user, @Nullable Icon icon, + @ShellMainThread Executor mainExecutor, @ShellBackgroundThread Executor bgExecutor) { + return new Bubble(info, + user, + icon, + getAppBubbleKeyForTask(info), + mainExecutor, bgExecutor); + } + /** Creates a shortcut bubble. */ public static Bubble createShortcutBubble( ShortcutInfo info, @@ -316,6 +359,15 @@ public class Bubble implements BubbleViewProvider { return info.getPackage() + ":" + info.getUserId() + ":" + info.getId(); } + /** + * Returns the key for an app bubble from an app with package name, {@code packageName} on an + * Android user, {@code user}. + */ + public static String getAppBubbleKeyForTask(TaskInfo taskInfo) { + Objects.requireNonNull(taskInfo); + return KEY_APP_BUBBLE + ":" + taskInfo.taskId; + } + @VisibleForTesting(visibility = PRIVATE) public Bubble(@NonNull final BubbleEntry entry, final Bubbles.BubbleMetadataFlagListener listener, @@ -469,6 +521,10 @@ public class Bubble implements BubbleViewProvider { return mBubbleTaskView; } + public TaskView getTaskView() { + return mBubbleTaskView.getTaskView(); + } + /** * @return the ShortcutInfo id if it exists, or the metadata shortcut id otherwise. */ @@ -486,6 +542,10 @@ public class Bubble implements BubbleViewProvider { return (mMetadataShortcutId != null && !mMetadataShortcutId.isEmpty()); } + public BubbleTransitions.BubbleTransition getPreparingTransition() { + return mPreparingTransition; + } + /** * Call this to clean up the task for the bubble. Ensure this is always called when done with * the bubble. @@ -512,7 +572,8 @@ public class Bubble implements BubbleViewProvider { mIntentActive = false; } - private void cleanupTaskView() { + /** Cleans-up the taskview associated with this bubble (possibly removing the task from wm) */ + public void cleanupTaskView() { if (mBubbleTaskView != null) { mBubbleTaskView.cleanup(); mBubbleTaskView = null; @@ -533,7 +594,7 @@ public class Bubble implements BubbleViewProvider { * <p>If we're switching between bar and floating modes, pass {@code false} on * {@code cleanupTaskView} to avoid recreating it in the new mode. */ - void cleanupViews(boolean cleanupTaskView) { + public void cleanupViews(boolean cleanupTaskView) { cleanupExpandedView(cleanupTaskView); mIconView = null; } @@ -556,6 +617,13 @@ public class Bubble implements BubbleViewProvider { } /** + * Sets the current bubble-transition that is coordinating a change in this bubble. + */ + void setPreparingTransition(BubbleTransitions.BubbleTransition transit) { + mPreparingTransition = transit; + } + + /** * Sets whether this bubble is considered text changed. This method is purely for * testing. */ 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 30d679006c98..5f2b95f7b137 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 @@ -40,9 +40,11 @@ import android.annotation.BinderThread; import android.annotation.NonNull; import android.annotation.UserIdInt; import android.app.ActivityManager; +import android.app.ActivityOptions; import android.app.Notification; import android.app.NotificationChannel; import android.app.PendingIntent; +import android.app.TaskInfo; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -78,6 +80,8 @@ import android.view.WindowInsets; import android.view.WindowManager; import android.window.ScreenCapture; import android.window.ScreenCapture.SynchronousScreenCaptureListener; +import android.window.WindowContainerToken; +import android.window.WindowContainerTransaction; import androidx.annotation.MainThread; import androidx.annotation.Nullable; @@ -110,6 +114,7 @@ import com.android.wm.shell.onehanded.OneHandedController; import com.android.wm.shell.onehanded.OneHandedTransitionCallback; import com.android.wm.shell.shared.annotations.ShellBackgroundThread; import com.android.wm.shell.shared.annotations.ShellMainThread; +import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper; import com.android.wm.shell.shared.bubbles.BubbleBarLocation; import com.android.wm.shell.shared.bubbles.BubbleBarUpdate; import com.android.wm.shell.sysui.ConfigurationChangeListener; @@ -287,6 +292,8 @@ public class BubbleController implements ConfigurationChangeListener, /** Used to send updates to the views from {@link #mBubbleDataListener}. */ private BubbleViewCallback mBubbleViewCallback; + private final BubbleTransitions mBubbleTransitions; + public BubbleController(Context context, ShellInit shellInit, ShellCommandHandler shellCommandHandler, @@ -350,12 +357,16 @@ public class BubbleController implements ConfigurationChangeListener, context.getResources().getDimensionPixelSize( com.android.internal.R.dimen.importance_ring_stroke_width)); mDisplayController = displayController; + final TaskViewTransitions tvTransitions; if (TaskViewTransitions.useRepo()) { - mTaskViewController = new TaskViewTransitions(transitions, taskViewRepository, - organizer, syncQueue); + tvTransitions = new TaskViewTransitions(transitions, taskViewRepository, organizer, + syncQueue); } else { - mTaskViewController = taskViewTransitions; + tvTransitions = taskViewTransitions; } + mTaskViewController = new BubbleTaskViewController(tvTransitions); + mBubbleTransitions = new BubbleTransitions(transitions, organizer, taskViewRepository, data, + tvTransitions, context); mTransitions = transitions; mOneHandedOptional = oneHandedOptional; mDragAndDropController = dragAndDropController; @@ -477,24 +488,23 @@ public class BubbleController implements ConfigurationChangeListener, @Override public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task, boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) { - for (Bubble b : mBubbleData.getBubbles()) { - if (task.taskId == b.getTaskId()) { - ProtoLog.d(WM_SHELL_BUBBLES, - "onActivityRestartAttempt - taskId=%d selecting matching bubble=%s", - task.taskId, b.getKey()); - mBubbleData.setSelectedBubbleAndExpandStack(b); - return; - } + final int taskId = task.taskId; + Bubble bubble = mBubbleData.getBubbleInStackWithTaskId(taskId); + if (bubble != null) { + ProtoLog.d(WM_SHELL_BUBBLES, + "onActivityRestartAttempt - taskId=%d selecting matching bubble=%s", + taskId, bubble.getKey()); + mBubbleData.setSelectedBubbleAndExpandStack(bubble); + return; } - for (Bubble b : mBubbleData.getOverflowBubbles()) { - if (task.taskId == b.getTaskId()) { - ProtoLog.d(WM_SHELL_BUBBLES, "onActivityRestartAttempt - taskId=%d " - + "selecting matching overflow bubble=%s", - task.taskId, b.getKey()); - promoteBubbleFromOverflow(b); - mBubbleData.setExpanded(true); - return; - } + + bubble = mBubbleData.getOverflowBubbleWithTaskId(taskId); + if (bubble != null) { + ProtoLog.d(WM_SHELL_BUBBLES, "onActivityRestartAttempt - taskId=%d " + + "selecting matching overflow bubble=%s", + taskId, bubble.getKey()); + promoteBubbleFromOverflow(bubble); + mBubbleData.setExpanded(true); } } }); @@ -1296,8 +1306,8 @@ public class BubbleController implements ConfigurationChangeListener, * @param timestamp the timestamp of the removal */ public void dragBubbleToDismiss(String bubbleKey, long timestamp) { - String selectedBubbleKey = mBubbleData.getSelectedBubbleKey(); - Bubble bubbleToDismiss = mBubbleData.getAnyBubbleWithkey(bubbleKey); + final String selectedBubbleKey = mBubbleData.getSelectedBubbleKey(); + final Bubble bubbleToDismiss = mBubbleData.getAnyBubbleWithKey(bubbleKey); if (bubbleToDismiss != null) { mBubbleData.dismissBubbleWithKey( bubbleKey, Bubbles.DISMISS_USER_GESTURE_FROM_LAUNCHER, timestamp); @@ -1330,11 +1340,11 @@ public class BubbleController implements ConfigurationChangeListener, @VisibleForTesting public boolean isBubbleNotificationSuppressedFromShade(String key, String groupKey) { - boolean isSuppressedBubble = (mBubbleData.hasAnyBubbleWithKey(key) - && !mBubbleData.getAnyBubbleWithkey(key).showInShade()); + final boolean isSuppressedBubble = (mBubbleData.hasAnyBubbleWithKey(key) + && !mBubbleData.getAnyBubbleWithKey(key).showInShade()); - boolean isSuppressedSummary = mBubbleData.isSummarySuppressed(groupKey); - boolean isSummary = key.equals(mBubbleData.getSummaryKey(groupKey)); + final boolean isSuppressedSummary = mBubbleData.isSummarySuppressed(groupKey); + final boolean isSummary = key.equals(mBubbleData.getSummaryKey(groupKey)); return (isSummary && isSuppressedSummary) || isSuppressedBubble; } @@ -1362,8 +1372,6 @@ public class BubbleController implements ConfigurationChangeListener, public void expandStackAndSelectBubbleFromLauncher(String key, int topOnScreen) { mBubblePositioner.setBubbleBarTopOnScreen(topOnScreen); - boolean wasExpanded = (mLayerView != null && mLayerView.isExpanded()); - if (BubbleOverflow.KEY.equals(key)) { mBubbleData.setSelectedBubbleFromLauncher(mBubbleData.getOverflow()); mLayerView.showExpandedView(mBubbleData.getOverflow()); @@ -1371,10 +1379,11 @@ public class BubbleController implements ConfigurationChangeListener, return; } - Bubble b = mBubbleData.getAnyBubbleWithkey(key); + final Bubble b = mBubbleData.getAnyBubbleWithKey(key); if (b == null) { return; } + final boolean wasExpanded = (mLayerView != null && mLayerView.isExpanded()); if (mBubbleData.hasBubbleInStackWithKey(b.getKey())) { // already in the stack mBubbleData.setSelectedBubbleFromLauncher(b); @@ -1458,7 +1467,19 @@ public class BubbleController implements ConfigurationChangeListener, * @param taskInfo the task. */ public void expandStackAndSelectBubble(ActivityManager.RunningTaskInfo taskInfo) { - // TODO(384976265): Not implemented yet + if (!BubbleAnythingFlagHelper.enableBubbleToFullscreen()) return; + Bubble b = mBubbleData.getOrCreateBubble(taskInfo); // Removes from overflow + ProtoLog.v(WM_SHELL_BUBBLES, "expandStackAndSelectBubble - intent=%s", taskInfo.taskId); + if (b.isInflated()) { + mBubbleData.setSelectedBubbleAndExpandStack(b); + } else { + b.enable(Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE); + // Lazy init stack view when a bubble is created + ensureBubbleViewsAndWindowCreated(); + mBubbleTransitions.startConvertToBubble(b, taskInfo, mExpandedViewManager, + mBubbleTaskViewFactory, mBubblePositioner, mLogger, mStackView, mLayerView, + mBubbleIconFactory, mInflateSynchronously); + } } /** @@ -1748,31 +1769,32 @@ public class BubbleController implements ConfigurationChangeListener, public void updateBubble(BubbleEntry notif, boolean suppressFlyout, boolean showInShade) { // If this is an interruptive notif, mark that it's interrupted mSysuiProxy.setNotificationInterruption(notif.getKey()); - boolean isNonInterruptiveNotExpanding = !notif.getRanking().isTextChanged() + final boolean isNonInterruptiveNotExpanding = !notif.getRanking().isTextChanged() && (notif.getBubbleMetadata() != null && !notif.getBubbleMetadata().getAutoExpandBubble()); + final Bubble bubble; if (isNonInterruptiveNotExpanding && mBubbleData.hasOverflowBubbleWithKey(notif.getKey())) { // Update the bubble but don't promote it out of overflow - Bubble b = mBubbleData.getOverflowBubbleWithKey(notif.getKey()); + bubble = mBubbleData.getOverflowBubbleWithKey(notif.getKey()); if (notif.isBubble()) { notif.setFlagBubble(false); } - updateNotNotifyingEntry(b, notif, showInShade); + updateNotNotifyingEntry(bubble, notif, showInShade); } else if (mBubbleData.hasAnyBubbleWithKey(notif.getKey()) && isNonInterruptiveNotExpanding) { - Bubble b = mBubbleData.getAnyBubbleWithkey(notif.getKey()); - if (b != null) { - updateNotNotifyingEntry(b, notif, showInShade); + bubble = mBubbleData.getAnyBubbleWithKey(notif.getKey()); + if (bubble != null) { + updateNotNotifyingEntry(bubble, notif, showInShade); } } else if (mBubbleData.isSuppressedWithLocusId(notif.getLocusId())) { // Update the bubble but don't promote it out of overflow - Bubble b = mBubbleData.getSuppressedBubbleWithKey(notif.getKey()); - if (b != null) { - updateNotNotifyingEntry(b, notif, showInShade); + bubble = mBubbleData.getSuppressedBubbleWithKey(notif.getKey()); + if (bubble != null) { + updateNotNotifyingEntry(bubble, notif, showInShade); } } else { - Bubble bubble = mBubbleData.getOrCreateBubble(notif, null /* persistedBubble */); + bubble = mBubbleData.getOrCreateBubble(notif, null /* persistedBubble */); if (notif.shouldSuppressNotificationList()) { // If we're suppressing notifs for DND, we don't want the bubbles to randomly // expand when DND turns off so flip the flag. @@ -2058,7 +2080,12 @@ public class BubbleController implements ConfigurationChangeListener, @Override public void removeBubble(Bubble removedBubble) { if (mLayerView != null) { + final BubbleTransitions.BubbleTransition bubbleTransit = + removedBubble.getPreparingTransition(); mLayerView.removeBubble(removedBubble, () -> { + if (bubbleTransit != null) { + bubbleTransit.continueCollapse(); + } if (!mBubbleData.hasBubbles() && !isStackExpanded()) { mLayerView.setVisibility(INVISIBLE); removeFromWindowManagerMaybe(); @@ -2262,9 +2289,16 @@ public class BubbleController implements ConfigurationChangeListener, private void showExpandedViewForBubbleBar() { BubbleViewProvider selectedBubble = mBubbleData.getSelectedBubble(); - if (selectedBubble != null && mLayerView != null) { - mLayerView.showExpandedView(selectedBubble); + if (selectedBubble == null) return; + if (selectedBubble instanceof Bubble) { + final Bubble bubble = (Bubble) selectedBubble; + if (bubble.getPreparingTransition() != null) { + bubble.getPreparingTransition().continueExpand(); + return; + } } + if (mLayerView == null) return; + mLayerView.showExpandedView(selectedBubble); } private void collapseExpandedViewForBubbleBar() { @@ -2323,12 +2357,12 @@ public class BubbleController implements ConfigurationChangeListener, BubbleEntry summary, @Nullable List<BubbleEntry> children, IntConsumer removeCallback) { if (children != null) { for (int i = 0; i < children.size(); i++) { - BubbleEntry child = children.get(i); + final BubbleEntry child = children.get(i); if (mBubbleData.hasAnyBubbleWithKey(child.getKey())) { // Suppress the bubbled child // As far as group manager is concerned, once a child is no longer shown // in the shade, it is essentially removed. - Bubble bubbleChild = mBubbleData.getAnyBubbleWithkey(child.getKey()); + final Bubble bubbleChild = mBubbleData.getAnyBubbleWithKey(child.getKey()); if (bubbleChild != null) { bubbleChild.setSuppressNotification(true); bubbleChild.setShowDot(false /* show */); @@ -2614,6 +2648,17 @@ public class BubbleController implements ConfigurationChangeListener, public void animateBubbleBarLocation(BubbleBarLocation location) { mListener.call(l -> l.animateBubbleBarLocation(location)); } + + @Override + public void onDragItemOverBubbleBarDragZone( + @NonNull BubbleBarLocation location) { + mListener.call(l -> l.onDragItemOverBubbleBarDragZone(location)); + } + + @Override + public void onItemDraggedOutsideBubbleBarDropZone() { + mListener.call(IBubblesListener::onItemDraggedOutsideBubbleBarDropZone); + } }; IBubblesImpl(BubbleController controller) { @@ -2666,7 +2711,18 @@ public class BubbleController implements ConfigurationChangeListener, @Override public void collapseBubbles() { - mMainExecutor.execute(() -> mController.collapseStack()); + mMainExecutor.execute(() -> { + if (mBubbleData.getSelectedBubble() instanceof Bubble) { + if (((Bubble) mBubbleData.getSelectedBubble()).getPreparingTransition() + != null) { + // Currently preparing a transition which will, itself, collapse the bubble. + // For transition preparation, the timing of bubble-collapse must be in + // sync with the rest of the set-up. + return; + } + } + mController.collapseStack(); + }); } @Override @@ -3058,4 +3114,84 @@ public class BubbleController implements ConfigurationChangeListener, return mKeyToShownInShadeMap.get(key); } } + + private class BubbleTaskViewController implements TaskViewController { + private final TaskViewTransitions mBaseTransitions; + + BubbleTaskViewController(TaskViewTransitions baseTransitions) { + mBaseTransitions = baseTransitions; + } + + @Override + public void registerTaskView(TaskViewTaskController tv) { + mBaseTransitions.registerTaskView(tv); + } + + @Override + public void unregisterTaskView(TaskViewTaskController tv) { + mBaseTransitions.unregisterTaskView(tv); + } + + @Override + public void startShortcutActivity(@NonNull TaskViewTaskController destination, + @NonNull ShortcutInfo shortcut, @NonNull ActivityOptions options, + @Nullable Rect launchBounds) { + mBaseTransitions.startShortcutActivity(destination, shortcut, options, launchBounds); + } + + @Override + public void startActivity(@NonNull TaskViewTaskController destination, + @NonNull PendingIntent pendingIntent, @Nullable Intent fillInIntent, + @NonNull ActivityOptions options, @Nullable Rect launchBounds) { + mBaseTransitions.startActivity(destination, pendingIntent, fillInIntent, + options, launchBounds); + } + + @Override + public void startRootTask(@NonNull TaskViewTaskController destination, + ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash, + @Nullable WindowContainerTransaction wct) { + mBaseTransitions.startRootTask(destination, taskInfo, leash, wct); + } + + @Override + public void removeTaskView(@NonNull TaskViewTaskController taskView, + @Nullable WindowContainerToken taskToken) { + mBaseTransitions.removeTaskView(taskView, taskToken); + } + + @Override + public void moveTaskViewToFullscreen(@NonNull TaskViewTaskController taskView) { + final TaskInfo tinfo = taskView.getTaskInfo(); + if (tinfo == null) { + return; + } + Bubble bub = null; + for (Bubble b : mBubbleData.getBubbles()) { + if (b.getTaskId() == tinfo.taskId) { + bub = b; + break; + } + } + if (bub == null) { + return; + } + mBubbleTransitions.startConvertFromBubble(bub, tinfo); + } + + @Override + public void setTaskViewVisible(TaskViewTaskController taskView, boolean visible) { + mBaseTransitions.setTaskViewVisible(taskView, visible); + } + + @Override + public void setTaskBounds(TaskViewTaskController taskView, Rect boundsOnScreen) { + mBaseTransitions.setTaskBounds(taskView, boundsOnScreen); + } + + @Override + public boolean isUsingShellTransitions() { + return mBaseTransitions.isUsingShellTransitions(); + } + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java index 76d91ede7aa3..96d0f6d5654e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java @@ -22,12 +22,12 @@ import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES; import android.annotation.NonNull; import android.app.PendingIntent; +import android.app.TaskInfo; import android.content.Context; import android.content.Intent; import android.content.LocusId; import android.content.pm.ShortcutInfo; import android.os.UserHandle; -import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; @@ -48,6 +48,7 @@ import com.android.wm.shell.shared.bubbles.RemovedBubble; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; @@ -470,6 +471,17 @@ public class BubbleData { return bubbleToReturn; } + Bubble getOrCreateBubble(TaskInfo taskInfo) { + UserHandle user = UserHandle.of(mCurrentUserId); + String bubbleKey = Bubble.getAppBubbleKeyForTask(taskInfo); + Bubble bubbleToReturn = findAndRemoveBubbleFromOverflow(bubbleKey); + if (bubbleToReturn == null) { + bubbleToReturn = Bubble.createTaskBubble(taskInfo, user, null, mMainExecutor, + mBgExecutor); + } + return bubbleToReturn; + } + @Nullable private Bubble findAndRemoveBubbleFromOverflow(String key) { Bubble bubbleToReturn = getBubbleInStackWithKey(key); @@ -667,7 +679,7 @@ public class BubbleData { /** Removes all bubbles for the given user. */ public void removeBubblesForUser(int userId) { - List<Bubble> removedBubbles = filterAllBubbles(bubble -> + final List<Bubble> removedBubbles = filterAllBubbles(bubble -> userId == bubble.getUser().getIdentifier()); for (Bubble b : removedBubbles) { doRemove(b.getKey(), Bubbles.DISMISS_USER_ACCOUNT_REMOVED); @@ -1162,7 +1174,7 @@ public class BubbleData { @VisibleForTesting(visibility = PRIVATE) @Nullable - Bubble getAnyBubbleWithkey(String key) { + Bubble getAnyBubbleWithKey(String key) { Bubble b = getBubbleInStackWithKey(key); if (b == null) { b = getOverflowBubbleWithKey(key); @@ -1173,77 +1185,45 @@ public class BubbleData { return b; } - /** @return any bubble (in the stack or the overflow) that matches the provided shortcutId. */ + /** @return the bubble in the stack that matches the provided taskId. */ @Nullable - Bubble getAnyBubbleWithShortcutId(String shortcutId) { - if (TextUtils.isEmpty(shortcutId)) { - return null; - } - for (int i = 0; i < mBubbles.size(); i++) { - Bubble bubble = mBubbles.get(i); - String bubbleShortcutId = bubble.getShortcutInfo() != null - ? bubble.getShortcutInfo().getId() - : bubble.getMetadataShortcutId(); - if (shortcutId.equals(bubbleShortcutId)) { - return bubble; - } - } - - for (int i = 0; i < mOverflowBubbles.size(); i++) { - Bubble bubble = mOverflowBubbles.get(i); - String bubbleShortcutId = bubble.getShortcutInfo() != null - ? bubble.getShortcutInfo().getId() - : bubble.getMetadataShortcutId(); - if (shortcutId.equals(bubbleShortcutId)) { - return bubble; - } - } - return null; + Bubble getBubbleInStackWithTaskId(int taskId) { + return getBubbleWithPredicate(mBubbles, b -> b.getTaskId() == taskId); } - @VisibleForTesting(visibility = PRIVATE) + /** @return the bubble in the stack that matches the provided key. */ @Nullable + @VisibleForTesting(visibility = PRIVATE) public Bubble getBubbleInStackWithKey(String key) { - for (int i = 0; i < mBubbles.size(); i++) { - Bubble bubble = mBubbles.get(i); - if (bubble.getKey().equals(key)) { - return bubble; - } - } - return null; + return getBubbleWithPredicate(mBubbles, b -> b.getKey().equals(key)); } + /** @return the bubble in the stack that matches the provided locusId. */ @Nullable - private Bubble getBubbleInStackWithLocusId(LocusId locusId) { - if (locusId == null) return null; - for (int i = 0; i < mBubbles.size(); i++) { - Bubble bubble = mBubbles.get(i); - if (locusId.equals(bubble.getLocusId())) { - return bubble; - } + private Bubble getBubbleInStackWithLocusId(@Nullable LocusId locusId) { + if (locusId == null) { + return null; } - return null; + return getBubbleWithPredicate(mBubbles, b -> locusId.equals(b.getLocusId())); } + /** @return the bubble in the stack that matches the provided icon view. */ @Nullable - Bubble getBubbleWithView(View view) { - for (int i = 0; i < mBubbles.size(); i++) { - Bubble bubble = mBubbles.get(i); - if (bubble.getIconView() != null && bubble.getIconView().equals(view)) { - return bubble; - } - } - return null; + Bubble getBubbleInStackWithView(View view) { + return getBubbleWithPredicate(mBubbles, b -> + b.getIconView() != null && b.getIconView().equals(view)); } + /** @return the overflow bubble that matches the provided taskId. */ + @Nullable + Bubble getOverflowBubbleWithTaskId(int taskId) { + return getBubbleWithPredicate(mOverflowBubbles, b -> b.getTaskId() == taskId); + } + + /** @return the overflow bubble that matches the provided key. */ + @Nullable public Bubble getOverflowBubbleWithKey(String key) { - for (int i = 0; i < mOverflowBubbles.size(); i++) { - Bubble bubble = mOverflowBubbles.get(i); - if (bubble.getKey().equals(key)) { - return bubble; - } - } - return null; + return getBubbleWithPredicate(mOverflowBubbles, b -> b.getKey().equals(key)); } /** @@ -1255,12 +1235,7 @@ public class BubbleData { @Nullable @VisibleForTesting(visibility = PRIVATE) public Bubble getSuppressedBubbleWithKey(String key) { - for (Bubble b : mSuppressedBubbles.values()) { - if (b.getKey().equals(key)) { - return b; - } - } - return null; + return getBubbleWithPredicate(mSuppressedBubbles.values(), b -> b.getKey().equals(key)); } /** @@ -1269,11 +1244,32 @@ public class BubbleData { * @param key notification key * @return bubble that matches or null */ + @Nullable @VisibleForTesting(visibility = PRIVATE) public Bubble getPendingBubbleWithKey(String key) { - for (Bubble b : mPendingBubbles.values()) { - if (b.getKey().equals(key)) { - return b; + return getBubbleWithPredicate(mPendingBubbles.values(), b -> b.getKey().equals(key)); + } + + @Nullable + private static Bubble getBubbleWithPredicate(@NonNull final List<Bubble> bubbles, + @NonNull final Predicate<Bubble> predicate) { + // Uses an indexed for loop for optimized performance when iterating over ArrayLists. + for (int i = 0; i < bubbles.size(); i++) { + final Bubble bubble = bubbles.get(i); + if (predicate.test(bubble)) { + return bubble; + } + } + return null; + } + + @Nullable + private static Bubble getBubbleWithPredicate(@NonNull final Collection<Bubble> bubbles, + @NonNull final Predicate<Bubble> predicate) { + // Uses an enhanced for loop for general collections, which may not support indexed access. + for (final Bubble bubble : bubbles) { + if (predicate.test(bubble)) { + return bubble; } } return null; @@ -1284,7 +1280,7 @@ public class BubbleData { * bubbles (i.e. pending, suppressed, active, and overflowed). */ private List<Bubble> filterAllBubbles(Predicate<Bubble> predicate) { - ArrayList<Bubble> matchingBubbles = new ArrayList<>(); + final ArrayList<Bubble> matchingBubbles = new ArrayList<>(); for (Bubble b : mPendingBubbles.values()) { if (predicate.test(b)) { matchingBubbles.add(b); 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 1a61793eab87..a725e04d3f8a 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 @@ -87,6 +87,7 @@ public class BubblePositioner { private int mExpandedViewLargeScreenWidth; private int mExpandedViewLargeScreenInsetClosestEdge; private int mExpandedViewLargeScreenInsetFurthestEdge; + private int mExpandedViewBubbleBarWidth; private int mOverflowWidth; private int mExpandedViewPadding; @@ -158,12 +159,13 @@ public class BubblePositioner { mBubbleOffscreenAmount = res.getDimensionPixelSize(R.dimen.bubble_stack_offscreen); mStackOffset = res.getDimensionPixelSize(R.dimen.bubble_stack_offset); mBubbleElevation = res.getDimensionPixelSize(R.dimen.bubble_elevation); + mExpandedViewBubbleBarWidth = Math.min( + res.getDimensionPixelSize(R.dimen.bubble_bar_expanded_view_width), + mPositionRect.width() - 2 * mExpandedViewPadding + ); if (mShowingInBubbleBar) { - mExpandedViewLargeScreenWidth = Math.min( - res.getDimensionPixelSize(R.dimen.bubble_bar_expanded_view_width), - mPositionRect.width() - 2 * mExpandedViewPadding - ); + mExpandedViewLargeScreenWidth = mExpandedViewBubbleBarWidth; } else if (mDeviceConfig.isSmallTablet()) { mExpandedViewLargeScreenWidth = (int) (bounds.width() * EXPANDED_VIEW_SMALL_TABLET_WIDTH_PERCENT); @@ -888,7 +890,7 @@ public class BubblePositioner { * How wide the expanded view should be when showing from the bubble bar. */ public int getExpandedViewWidthForBubbleBar(boolean isOverflow) { - return isOverflow ? mOverflowWidth : mExpandedViewLargeScreenWidth; + return isOverflow ? mOverflowWidth : mExpandedViewBubbleBarWidth; } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index 1094c290df06..f1f49eda75b6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -497,7 +497,7 @@ public class BubbleStackView extends FrameLayout view /* bubble */, mDismissView.getHeight() /* translationYBy */, () -> dismissBubbleIfExists( - mBubbleData.getBubbleWithView(view)) /* after */); + mBubbleData.getBubbleInStackWithView(view)) /* after */); } mDismissView.hide(); @@ -558,7 +558,7 @@ public class BubbleStackView extends FrameLayout return; } - final Bubble clickedBubble = mBubbleData.getBubbleWithView(view); + final Bubble clickedBubble = mBubbleData.getBubbleInStackWithView(view); // If the bubble has since left us, ignore the click. if (clickedBubble == null) { @@ -633,8 +633,6 @@ public class BubbleStackView extends FrameLayout mMagneticTarget, mIndividualBubbleMagnetListener); - hideCurrentInputMethod(); - // Save the magnetized individual bubble so we can dispatch touch events to it. mMagnetizedObject = mExpandedAnimationController.getMagnetizedBubbleDraggingOut(); } else { @@ -671,6 +669,10 @@ public class BubbleStackView extends FrameLayout return; } + if (mPositioner.isImeVisible()) { + hideCurrentInputMethod(); + } + // Show the dismiss target, if we haven't already. mDismissView.show(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java index 89c038b4a26b..ae84f449c0e4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java @@ -109,7 +109,9 @@ public class BubbleTaskViewHelper { MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS); final boolean isShortcutBubble = (mBubble.hasMetadataShortcutId() || (mBubble.getShortcutInfo() != null && Flags.enableBubbleAnything())); - if (mBubble.isAppBubble()) { + if (mBubble.getPreparingTransition() != null) { + mBubble.getPreparingTransition().surfaceCreated(); + } else if (mBubble.isAppBubble()) { Context context = mContext.createContextAsUser( mBubble.getUser(), Context.CONTEXT_RESTRICTED); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java new file mode 100644 index 000000000000..29fb1a23017c --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java @@ -0,0 +1,518 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.bubbles; + +import static android.app.ActivityTaskManager.INVALID_TASK_ID; +import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; +import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static android.view.View.INVISIBLE; +import static android.view.WindowManager.TRANSIT_CHANGE; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.ActivityManager; +import android.app.TaskInfo; +import android.content.Context; +import android.graphics.Rect; +import android.os.IBinder; +import android.util.Slog; +import android.view.SurfaceControl; +import android.view.SurfaceView; +import android.view.View; +import android.window.TransitionInfo; +import android.window.TransitionRequestInfo; +import android.window.WindowContainerToken; +import android.window.WindowContainerTransaction; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.launcher3.icons.BubbleIconFactory; +import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView; +import com.android.wm.shell.bubbles.bar.BubbleBarLayerView; +import com.android.wm.shell.taskview.TaskView; +import com.android.wm.shell.taskview.TaskViewRepository; +import com.android.wm.shell.taskview.TaskViewTaskController; +import com.android.wm.shell.taskview.TaskViewTransitions; +import com.android.wm.shell.transition.Transitions; + +import java.util.concurrent.Executor; + +/** + * Implements transition coordination for bubble operations. + */ +public class BubbleTransitions { + private static final String TAG = "BubbleTransitions"; + + /** + * Multiplier used to convert a view elevation to an "equivalent" shadow-radius. This is the + * same multiple used by skia and surface-outsets in WMS. + */ + private static final float ELEVATION_TO_RADIUS = 2; + + @NonNull final Transitions mTransitions; + @NonNull final ShellTaskOrganizer mTaskOrganizer; + @NonNull final TaskViewRepository mRepository; + @NonNull final Executor mMainExecutor; + @NonNull final BubbleData mBubbleData; + @NonNull final TaskViewTransitions mTaskViewTransitions; + @NonNull final Context mContext; + + BubbleTransitions(@NonNull Transitions transitions, @NonNull ShellTaskOrganizer organizer, + @NonNull TaskViewRepository repository, @NonNull BubbleData bubbleData, + @NonNull TaskViewTransitions taskViewTransitions, Context context) { + mTransitions = transitions; + mTaskOrganizer = organizer; + mRepository = repository; + mMainExecutor = transitions.getMainExecutor(); + mBubbleData = bubbleData; + mTaskViewTransitions = taskViewTransitions; + mContext = context; + } + + /** + * Starts a convert-to-bubble transition. + * + * @see ConvertToBubble + */ + public BubbleTransition startConvertToBubble(Bubble bubble, TaskInfo taskInfo, + BubbleExpandedViewManager expandedViewManager, BubbleTaskViewFactory factory, + BubblePositioner positioner, BubbleLogger logger, BubbleStackView stackView, + BubbleBarLayerView layerView, BubbleIconFactory iconFactory, + boolean inflateSync) { + ConvertToBubble convert = new ConvertToBubble(bubble, taskInfo, mContext, + expandedViewManager, factory, positioner, logger, stackView, layerView, iconFactory, + inflateSync); + return convert; + } + + /** + * Starts a convert-from-bubble transition. + * + * @see ConvertFromBubble + */ + public BubbleTransition startConvertFromBubble(Bubble bubble, + TaskInfo taskInfo) { + ConvertFromBubble convert = new ConvertFromBubble(bubble, taskInfo); + return convert; + } + + /** + * Plucks the task-surface out of an ancestor view while making the view invisible. This helper + * attempts to do this seamlessly (ie. view becomes invisible in sync with task reparent). + */ + private void pluck(SurfaceControl taskLeash, View fromView, SurfaceControl dest, + float destX, float destY, float cornerRadius, SurfaceControl.Transaction t, + Runnable onPlucked) { + SurfaceControl.Transaction pluckT = new SurfaceControl.Transaction(); + pluckT.reparent(taskLeash, dest); + t.reparent(taskLeash, dest); + pluckT.setPosition(taskLeash, destX, destY); + t.setPosition(taskLeash, destX, destY); + pluckT.show(taskLeash); + pluckT.setAlpha(taskLeash, 1.f); + float shadowRadius = fromView.getElevation() * ELEVATION_TO_RADIUS; + pluckT.setShadowRadius(taskLeash, shadowRadius); + pluckT.setCornerRadius(taskLeash, cornerRadius); + t.setShadowRadius(taskLeash, shadowRadius); + t.setCornerRadius(taskLeash, cornerRadius); + + // Need to remove the taskview AFTER applying the startTransaction because it isn't + // synchronized. + pluckT.addTransactionCommittedListener(mMainExecutor, onPlucked::run); + fromView.getViewRootImpl().applyTransactionOnDraw(pluckT); + fromView.setVisibility(INVISIBLE); + } + + /** + * Interface to a bubble-specific transition. Bubble transitions have a multi-step lifecycle + * in order to coordinate with the bubble view logic. These steps are communicated on this + * interface. + */ + interface BubbleTransition { + default void surfaceCreated() {} + default void continueExpand() {} + void skip(); + default void continueCollapse() {} + } + + /** + * BubbleTransition that coordinates the process of a non-bubble task becoming a bubble. The + * steps are as follows: + * + * 1. Start inflating the bubble view + * 2. Once inflated (but not-yet visible), tell WM to do the shell-transition. + * 3. Transition becomes ready, so notify Launcher + * 4. Launcher responds with showExpandedView which calls continueExpand() to make view visible + * 5. Surface is created which kicks off actual animation + * + * So, constructor -> onInflated -> startAnimation -> continueExpand -> surfaceCreated. + * + * continueExpand and surfaceCreated are set-up to happen in either order, though, to support + * UX/timing adjustments. + */ + @VisibleForTesting + class ConvertToBubble implements Transitions.TransitionHandler, BubbleTransition { + final BubbleBarLayerView mLayerView; + Bubble mBubble; + IBinder mTransition; + Transitions.TransitionFinishCallback mFinishCb; + WindowContainerTransaction mFinishWct = null; + final Rect mStartBounds = new Rect(); + SurfaceControl mSnapshot = null; + TaskInfo mTaskInfo; + boolean mFinishedExpand = false; + BubbleViewProvider mPriorBubble = null; + + private SurfaceControl.Transaction mFinishT; + private SurfaceControl mTaskLeash; + + ConvertToBubble(Bubble bubble, TaskInfo taskInfo, Context context, + BubbleExpandedViewManager expandedViewManager, BubbleTaskViewFactory factory, + BubblePositioner positioner, BubbleLogger logger, BubbleStackView stackView, + BubbleBarLayerView layerView, BubbleIconFactory iconFactory, boolean inflateSync) { + mBubble = bubble; + mTaskInfo = taskInfo; + mLayerView = layerView; + mBubble.setInflateSynchronously(inflateSync); + mBubble.setPreparingTransition(this); + mBubble.inflate( + this::onInflated, + context, + expandedViewManager, + factory, + positioner, + logger, + stackView, + layerView, + iconFactory, + false /* skipInflation */); + } + + @VisibleForTesting + void onInflated(Bubble b) { + if (b != mBubble) { + throw new IllegalArgumentException("inflate callback doesn't match bubble"); + } + final Rect launchBounds = new Rect(); + mLayerView.getExpandedViewRestBounds(launchBounds); + WindowContainerTransaction wct = new WindowContainerTransaction(); + if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW) { + if (mTaskInfo.getParentTaskId() != INVALID_TASK_ID) { + wct.reparent(mTaskInfo.token, null, true); + } + } + + wct.setAlwaysOnTop(mTaskInfo.token, true); + wct.setWindowingMode(mTaskInfo.token, WINDOWING_MODE_MULTI_WINDOW); + wct.setBounds(mTaskInfo.token, launchBounds); + + final TaskView tv = b.getTaskView(); + tv.setSurfaceLifecycle(SurfaceView.SURFACE_LIFECYCLE_FOLLOWS_ATTACHMENT); + final TaskViewRepository.TaskViewState state = mRepository.byTaskView( + tv.getController()); + if (state != null) { + state.mVisible = true; + } + mTaskViewTransitions.enqueueExternal(tv.getController(), () -> { + mTransition = mTransitions.startTransition(TRANSIT_CHANGE, wct, this); + return mTransition; + }); + } + + @Override + public void skip() { + mBubble.setPreparingTransition(null); + mFinishCb.onTransitionFinished(mFinishWct); + mFinishCb = null; + } + + @Override + public WindowContainerTransaction handleRequest(@NonNull IBinder transition, + @Nullable TransitionRequestInfo request) { + return null; + } + + @Override + public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + } + + @Override + public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted, + @NonNull SurfaceControl.Transaction finishTransaction) { + if (!aborted) return; + mTransition = null; + mTaskViewTransitions.onExternalDone(transition); + } + + @Override + public boolean startAnimation(@NonNull IBinder transition, + @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + if (mTransition != transition) return false; + boolean found = false; + for (int i = 0; i < info.getChanges().size(); ++i) { + final TransitionInfo.Change chg = info.getChanges().get(i); + if (chg.getTaskInfo() == null) continue; + if (chg.getMode() != TRANSIT_CHANGE) continue; + if (!mTaskInfo.token.equals(chg.getTaskInfo().token)) continue; + mStartBounds.set(chg.getStartAbsBounds()); + // Converting a task into taskview, so treat as "new" + mFinishWct = new WindowContainerTransaction(); + mTaskInfo = chg.getTaskInfo(); + mFinishT = finishTransaction; + mTaskLeash = chg.getLeash(); + found = true; + mSnapshot = chg.getSnapshot(); + break; + } + if (!found) { + Slog.w(TAG, "Expected a TaskView conversion in this transition but didn't get " + + "one, cleaning up the task view"); + mBubble.getTaskView().getController().setTaskNotFound(); + mTaskViewTransitions.onExternalDone(transition); + return false; + } + mFinishCb = finishCallback; + + // Now update state (and talk to launcher) in parallel with snapshot stuff + mBubbleData.notificationEntryUpdated(mBubble, /* suppressFlyout= */ true, + /* showInShade= */ false); + + startTransaction.show(mSnapshot); + // Move snapshot to root so that it remains visible while task is moved to taskview + startTransaction.reparent(mSnapshot, info.getRoot(0).getLeash()); + startTransaction.setPosition(mSnapshot, + mStartBounds.left - info.getRoot(0).getOffset().x, + mStartBounds.top - info.getRoot(0).getOffset().y); + startTransaction.setLayer(mSnapshot, Integer.MAX_VALUE); + startTransaction.apply(); + + mTaskViewTransitions.onExternalDone(transition); + return true; + } + + @Override + public void continueExpand() { + mFinishedExpand = true; + final boolean animate = mLayerView.canExpandView(mBubble); + if (animate) { + mPriorBubble = mLayerView.prepareConvertedView(mBubble); + } + if (mPriorBubble != null) { + // TODO: an animation. For now though, just remove it. + final BubbleBarExpandedView priorView = mPriorBubble.getBubbleBarExpandedView(); + mLayerView.removeView(priorView); + mPriorBubble = null; + } + if (!animate || mBubble.getTaskView().getSurfaceControl() != null) { + playAnimation(animate); + } + } + + @Override + public void surfaceCreated() { + mMainExecutor.execute(() -> { + final TaskViewTaskController tvc = mBubble.getTaskView().getController(); + final TaskViewRepository.TaskViewState state = mRepository.byTaskView(tvc); + if (state == null) return; + state.mVisible = true; + if (mFinishedExpand) { + playAnimation(true /* animate */); + } + }); + } + + private void playAnimation(boolean animate) { + final TaskViewTaskController tv = mBubble.getTaskView().getController(); + final SurfaceControl.Transaction startT = new SurfaceControl.Transaction(); + mTaskViewTransitions.prepareOpenAnimation(tv, true /* new */, startT, mFinishT, + (ActivityManager.RunningTaskInfo) mTaskInfo, mTaskLeash, mFinishWct); + + if (mFinishWct.isEmpty()) { + mFinishWct = null; + } + + // Preparation is complete. + mBubble.setPreparingTransition(null); + + if (animate) { + mLayerView.animateConvert(startT, mStartBounds, mSnapshot, mTaskLeash, () -> { + mFinishCb.onTransitionFinished(mFinishWct); + mFinishCb = null; + }); + } else { + startT.apply(); + mFinishCb.onTransitionFinished(mFinishWct); + mFinishCb = null; + } + } + } + + /** + * BubbleTransition that coordinates the setup for moving a task out of a bubble. The actual + * animation is owned by the "receiver" of the task; however, because Bubbles uses TaskView, + * we need to do some extra coordination work to get the task surface out of the view + * "seamlessly". + * + * The process here looks like: + * 1. Send transition to WM for leaving bubbles mode + * 2. in startAnimation, set-up a "pluck" operation to pull the task surface out of taskview + * 3. Once "plucked", remove the view (calls continueCollapse when surfaces can be cleaned-up) + * 4. Then re-dispatch the transition animation so that the "receiver" can animate it. + * + * So, constructor -> startAnimation -> continueCollapse -> re-dispatch. + */ + @VisibleForTesting + class ConvertFromBubble implements Transitions.TransitionHandler, BubbleTransition { + @NonNull final Bubble mBubble; + IBinder mTransition; + TaskInfo mTaskInfo; + SurfaceControl mTaskLeash; + SurfaceControl mRootLeash; + + ConvertFromBubble(@NonNull Bubble bubble, TaskInfo taskInfo) { + mBubble = bubble; + mTaskInfo = taskInfo; + + mBubble.setPreparingTransition(this); + WindowContainerTransaction wct = new WindowContainerTransaction(); + WindowContainerToken token = mTaskInfo.getToken(); + wct.setWindowingMode(token, WINDOWING_MODE_UNDEFINED); + wct.setAlwaysOnTop(token, false); + mTaskOrganizer.setInterceptBackPressedOnTaskRoot(token, false); + mTaskViewTransitions.enqueueExternal( + mBubble.getTaskView().getController(), + () -> { + mTransition = mTransitions.startTransition(TRANSIT_CHANGE, wct, this); + return mTransition; + }); + } + + @Override + public void skip() { + mBubble.setPreparingTransition(null); + final TaskViewTaskController tv = + mBubble.getTaskView().getController(); + tv.notifyTaskRemovalStarted(tv.getTaskInfo()); + mTaskLeash = null; + } + + @Override + public WindowContainerTransaction handleRequest(@NonNull IBinder transition, + @android.annotation.Nullable TransitionRequestInfo request) { + return null; + } + + @Override + public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + } + + @Override + public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted, + @NonNull SurfaceControl.Transaction finishTransaction) { + if (!aborted) return; + mTransition = null; + skip(); + mTaskViewTransitions.onExternalDone(transition); + } + + @Override + public boolean startAnimation(@NonNull IBinder transition, + @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + if (mTransition != transition) return false; + + final TaskViewTaskController tv = + mBubble.getTaskView().getController(); + if (tv == null) { + mTaskViewTransitions.onExternalDone(transition); + return false; + } + + TransitionInfo.Change taskChg = null; + + boolean found = false; + for (int i = 0; i < info.getChanges().size(); ++i) { + final TransitionInfo.Change chg = info.getChanges().get(i); + if (chg.getTaskInfo() == null) continue; + if (chg.getMode() != TRANSIT_CHANGE) continue; + if (!mTaskInfo.token.equals(chg.getTaskInfo().token)) continue; + found = true; + mRepository.remove(tv); + taskChg = chg; + break; + } + + if (!found) { + Slog.w(TAG, "Expected a TaskView conversion in this transition but didn't get " + + "one, cleaning up the task view"); + tv.setTaskNotFound(); + skip(); + mTaskViewTransitions.onExternalDone(transition); + return false; + } + + mTaskLeash = taskChg.getLeash(); + mRootLeash = info.getRoot(0).getLeash(); + + SurfaceControl dest = + mBubble.getBubbleBarExpandedView().getViewRootImpl().getSurfaceControl(); + final Runnable onPlucked = () -> { + // Need to remove the taskview AFTER applying the startTransaction because + // it isn't synchronized. + tv.notifyTaskRemovalStarted(tv.getTaskInfo()); + // Unset after removeView so it can be used to pick a different animation. + mBubble.setPreparingTransition(null); + mBubbleData.setExpanded(false /* expanded */); + }; + if (dest != null) { + pluck(mTaskLeash, mBubble.getBubbleBarExpandedView(), dest, + taskChg.getStartAbsBounds().left - info.getRoot(0).getOffset().x, + taskChg.getStartAbsBounds().top - info.getRoot(0).getOffset().y, + mBubble.getBubbleBarExpandedView().getCornerRadius(), startTransaction, + onPlucked); + mBubble.getBubbleBarExpandedView().post(() -> mTransitions.dispatchTransition( + mTransition, info, startTransaction, finishTransaction, finishCallback, + null)); + } else { + onPlucked.run(); + mTransitions.dispatchTransition(mTransition, info, startTransaction, + finishTransaction, finishCallback, null); + } + + mTaskViewTransitions.onExternalDone(transition); + return true; + } + + @Override + public void continueCollapse() { + mBubble.cleanupTaskView(); + if (mTaskLeash == null) return; + SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + t.reparent(mTaskLeash, mRootLeash); + t.apply(); + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java index 62895fe7c7cc..4297fac0f6a8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java @@ -36,6 +36,7 @@ import android.window.ScreenCapture.ScreenshotHardwareBuffer; import android.window.ScreenCapture.SynchronousScreenCaptureListener; import androidx.annotation.IntDef; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.wm.shell.shared.annotations.ExternalThread; @@ -330,6 +331,18 @@ public interface Bubbles { * Does not result in a state change. */ void animateBubbleBarLocation(BubbleBarLocation location); + + /** + * Called when an application icon is being dragged over the Bubble Bar drop zone. + * The location of the Bubble Bar is provided as an argument. + */ + void onDragItemOverBubbleBarDragZone(@NonNull BubbleBarLocation location); + + /** + * Called when an application icon is being dragged outside the Bubble Bar drop zone. + * Always called after {@link #onDragItemOverBubbleBarDragZone(BubbleBarLocation)} + */ + void onItemDraggedOutsideBubbleBarDropZone(); } /** Listener to find out about stack expansion / collapse events. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesTransitionObserver.java index c1f704ab455d..1f3b1b6718f7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesTransitionObserver.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesTransitionObserver.java @@ -33,11 +33,13 @@ import com.android.wm.shell.transition.Transitions; */ public class BubblesTransitionObserver implements Transitions.TransitionObserver { - private BubbleController mBubbleController; - private BubbleData mBubbleData; + @NonNull + private final BubbleController mBubbleController; + @NonNull + private final BubbleData mBubbleData; - public BubblesTransitionObserver(BubbleController controller, - BubbleData bubbleData) { + public BubblesTransitionObserver(@NonNull BubbleController controller, + @NonNull BubbleData bubbleData) { mBubbleController = controller; mBubbleData = bubbleData; } @@ -57,7 +59,7 @@ public class BubblesTransitionObserver implements Transitions.TransitionObserver || mBubbleData.getSelectedBubble() == null) { continue; } - int expandedId = mBubbleData.getSelectedBubble().getTaskId(); + final int expandedId = mBubbleData.getSelectedBubble().getTaskId(); // If the task id that's opening is the same as the expanded bubble, skip collapsing // because it is our bubble that is opening. if (expandedId != INVALID_TASK_ID && expandedId != taskInfo.taskId) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubblesListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubblesListener.aidl index eb907dbb6597..9fc769f562a9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubblesListener.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubblesListener.aidl @@ -33,4 +33,16 @@ oneway interface IBubblesListener { * Does not result in a state change. */ void animateBubbleBarLocation(in BubbleBarLocation location); + + /** + * Called when an application icon is being dragged over the Bubble Bar drop zone. + * The location of the Bubble Bar is provided as an argument. + */ + void onDragItemOverBubbleBarDragZone(in BubbleBarLocation location); + + /** + * Called when an application icon is being dragged outside the Bubble Bar drop zone. + * Always called after {@link #onDragItemOverBubbleBarDragZone(BubbleBarLocation)} + */ + void onItemDraggedOutsideBubbleBarDropZone(); }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java index de6d1f6c8852..52f20646fb4a 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 @@ -36,17 +36,21 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; +import android.annotation.NonNull; import android.content.Context; import android.graphics.Point; import android.graphics.Rect; import android.util.Log; import android.util.Size; +import android.view.SurfaceControl; 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.animation.SizeChangeAnimation; +import com.android.wm.shell.bubbles.Bubble; import com.android.wm.shell.bubbles.BubbleOverflow; import com.android.wm.shell.bubbles.BubblePositioner; import com.android.wm.shell.bubbles.BubbleViewProvider; @@ -571,6 +575,49 @@ public class BubbleBarAnimationHelper { } /** + * Animates converting of a non-bubble task into an expanded bubble view. + */ + public void animateConvert(BubbleViewProvider expandedBubble, + @NonNull SurfaceControl.Transaction startT, + @NonNull Rect origBounds, + @NonNull SurfaceControl snapshot, + @NonNull SurfaceControl taskLeash, + @Nullable Runnable afterAnimation) { + mExpandedBubble = expandedBubble; + final BubbleBarExpandedView bbev = getExpandedView(); + if (bbev == null) { + return; + } + + bbev.setTaskViewAlpha(1f); + SurfaceControl tvSf = ((Bubble) mExpandedBubble).getTaskView().getSurfaceControl(); + + final Size size = getExpandedViewSize(); + Point position = getExpandedViewRestPosition(size); + + final SizeChangeAnimation sca = + new SizeChangeAnimation( + new Rect(origBounds.left - position.x, origBounds.top - position.y, + origBounds.right - position.x, origBounds.bottom - position.y), + new Rect(0, 0, size.getWidth(), size.getHeight())); + sca.initialize(bbev, taskLeash, snapshot, startT); + + Animator a = sca.buildViewAnimator(bbev, tvSf, snapshot, /* onFinish */ (va) -> { + updateExpandedView(bbev); + snapshot.release(); + bbev.setSurfaceZOrderedOnTop(false); + bbev.setAnimating(false); + if (afterAnimation != null) { + afterAnimation.run(); + } + }); + + bbev.setSurfaceZOrderedOnTop(true); + a.setDuration(EXPANDED_VIEW_ANIMATE_TO_REST_DURATION); + a.start(); + } + + /** * Cancel current animations */ public void cancelAnimations() { @@ -627,6 +674,13 @@ public class BubbleBarAnimationHelper { bbev.maybeShowOverflow(); } + void getExpandedViewRestBounds(Rect out) { + final int width = mPositioner.getExpandedViewWidthForBubbleBar(false /* overflow */); + final int height = mPositioner.getExpandedViewHeightForBubbleBar(false /* overflow */); + Point position = getExpandedViewRestPosition(new Size(width, height)); + out.set(position.x, position.y, position.x + width, position.y + height); + } + private Point getExpandedViewRestPosition(Size size) { final int padding = mPositioner.getBubbleBarExpandedViewPadding(); Point point = new Point(); 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 eaa0bd250fc4..f3f8d6f96a42 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 @@ -28,6 +28,7 @@ import android.graphics.Rect; import android.graphics.Region; import android.graphics.drawable.ColorDrawable; import android.view.Gravity; +import android.view.SurfaceControl; import android.view.TouchDelegate; import android.view.View; import android.view.ViewTreeObserver; @@ -174,14 +175,34 @@ public class BubbleBarLayerView extends FrameLayout /** Shows the expanded view of the provided bubble. */ public void showExpandedView(BubbleViewProvider b) { - BubbleBarExpandedView expandedView = b.getBubbleBarExpandedView(); - if (expandedView == null) { - return; - } + if (!canExpandView(b)) return; + animateExpand(prepareExpandedView(b)); + } + + /** + * @return whether it's possible to expand {@param b} right now. This is {@code false} if + * the bubble has no view or if the bubble is already showing. + */ + public boolean canExpandView(BubbleViewProvider b) { + if (b.getBubbleBarExpandedView() == null) return false; if (mExpandedBubble != null && mIsExpanded && b.getKey().equals(mExpandedBubble.getKey())) { - // Already showing this bubble, skip animating - return; + // Already showing this bubble so can't expand it. + return false; + } + return true; + } + + /** + * Prepares the expanded view of the provided bubble to be shown. This includes removing any + * stale content and cancelling any related animations. + * + * @return previous open bubble if there was one. + */ + private BubbleViewProvider prepareExpandedView(BubbleViewProvider b) { + if (!canExpandView(b)) { + throw new IllegalStateException("Can't prepare expand. Check canExpandView(b) first."); } + BubbleBarExpandedView expandedView = b.getBubbleBarExpandedView(); BubbleViewProvider previousBubble = null; if (mExpandedBubble != null && !b.getKey().equals(mExpandedBubble.getKey())) { if (mIsExpanded && mExpandedBubble.getBubbleBarExpandedView() != null) { @@ -251,7 +272,20 @@ public class BubbleBarLayerView extends FrameLayout mIsExpanded = true; mBubbleController.getSysuiProxy().onStackExpandChanged(true); + showScrim(true); + return previousBubble; + } + /** + * Performs an animation to open a bubble with content that is not already visible. + * + * @param previousBubble If non-null, this is a bubble that is already showing before the new + * bubble is expanded. + */ + public void animateExpand(BubbleViewProvider previousBubble) { + if (!mIsExpanded || mExpandedBubble == null) { + throw new IllegalStateException("Can't animateExpand without expnaded state"); + } final Runnable afterAnimation = () -> { if (mExpandedView == null) return; // Touch delegate for the menu @@ -274,14 +308,57 @@ public class BubbleBarLayerView extends FrameLayout } else { mAnimationHelper.animateExpansion(mExpandedBubble, afterAnimation); } + } - showScrim(true); + /** + * Like {@link #prepareExpandedView} but also makes the current expanded bubble visible + * immediately so it gets a surface that can be animated. Since the surface may not be ready + * yet, this keeps the TaskView alpha=0. + */ + public BubbleViewProvider prepareConvertedView(BubbleViewProvider b) { + final BubbleViewProvider prior = prepareExpandedView(b); + + final BubbleBarExpandedView bbev = mExpandedBubble.getBubbleBarExpandedView(); + if (bbev != null) { + updateExpandedView(); + bbev.setAnimating(true); + bbev.setContentVisibility(true); + bbev.setSurfaceZOrderedOnTop(true); + bbev.setTaskViewAlpha(0.f); + bbev.setVisibility(VISIBLE); + } + + return prior; + } + + /** + * Starts and animates a conversion-from transition. + * + * @param startT A transaction with first-frame work. this *will* be applied here! + */ + public void animateConvert(@NonNull SurfaceControl.Transaction startT, + @NonNull Rect startBounds, @NonNull SurfaceControl snapshot, SurfaceControl taskLeash, + Runnable animFinish) { + if (!mIsExpanded || mExpandedBubble == null) { + throw new IllegalStateException("Can't animateExpand without expanded state"); + } + mAnimationHelper.animateConvert(mExpandedBubble, startT, startBounds, snapshot, taskLeash, + animFinish); + } + + /** + * Populates {@param out} with the rest bounds of an expanded bubble. + */ + public void getExpandedViewRestBounds(Rect out) { + mAnimationHelper.getExpandedViewRestBounds(out); } /** Removes the given {@code bubble}. */ public void removeBubble(Bubble bubble, Runnable endAction) { + final boolean inTransition = bubble.getPreparingTransition() != null; Runnable cleanUp = () -> { - bubble.cleanupViews(); + // The transition is already managing the task/wm state. + bubble.cleanupViews(!inTransition); endAction.run(); }; if (mBubbleData.getBubbles().isEmpty()) { 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 c9890a5b4963..94e629a6887f 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 @@ -661,7 +661,9 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged if (android.view.inputmethod.Flags.refactorInsetsController()) { setVisibleDirectly(false /* visible */, statsToken); } - ImeTracker.forLogging().onHidden(mStatsToken); + if (!android.view.inputmethod.Flags.refactorInsetsController()) { + ImeTracker.forLogging().onHidden(mStatsToken); + } } else if (mAnimationDirection == DIRECTION_SHOW && !mCancelled) { ImeTracker.forLogging().onShown(mStatsToken); } else if (mCancelled) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiInstanceHelper.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiInstanceHelper.kt index 4cd2fd04d3cf..ff3e65a247ae 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiInstanceHelper.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiInstanceHelper.kt @@ -15,16 +15,21 @@ */ package com.android.wm.shell.common +import android.annotation.UserIdInt import android.app.PendingIntent import android.content.ComponentName import android.content.Context import android.content.pm.LauncherApps import android.content.pm.PackageManager +import android.content.pm.PackageManager.Property import android.os.UserHandle import android.view.WindowManager.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI import com.android.internal.protolog.ProtoLog import com.android.wm.shell.R import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL +import com.android.wm.shell.sysui.ShellCommandHandler +import com.android.wm.shell.sysui.ShellInit +import java.io.PrintWriter import java.util.Arrays /** @@ -35,12 +40,23 @@ class MultiInstanceHelper @JvmOverloads constructor( private val packageManager: PackageManager, private val staticAppsSupportingMultiInstance: Array<String> = context.resources .getStringArray(R.array.config_appsSupportMultiInstancesSplit), - private val supportsMultiInstanceProperty: Boolean) { + shellInit: ShellInit, + private val shellCommandHandler: ShellCommandHandler, + private val supportsMultiInstanceProperty: Boolean +) : ShellCommandHandler.ShellCommandActionHandler { + + init { + shellInit.addInitCallback(this::onInit, this) + } + + private fun onInit() { + shellCommandHandler.addCommandCallback("multi-instance", this, this) + } /** * Returns whether a specific component desires to be launched in multiple instances. */ - fun supportsMultiInstanceSplit(componentName: ComponentName?): Boolean { + fun supportsMultiInstanceSplit(componentName: ComponentName?, @UserIdInt userId: Int): Boolean { if (componentName == null || componentName.packageName == null) { // TODO(b/262864589): Handle empty component case return false @@ -63,8 +79,9 @@ class MultiInstanceHelper @JvmOverloads constructor( // Check the activity property first try { - val activityProp = packageManager.getProperty( - PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, componentName) + val activityProp = packageManager.getPropertyAsUser( + PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, componentName.packageName, + componentName.className, userId) // If the above call doesn't throw a NameNotFoundException, then the activity property // should override the application property value if (activityProp.isBoolean) { @@ -80,8 +97,9 @@ class MultiInstanceHelper @JvmOverloads constructor( // Check the application property otherwise try { - val appProp = packageManager.getProperty( - PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, packageName) + val appProp = packageManager.getPropertyAsUser( + PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, packageName, null /* className */, + userId) if (appProp.isBoolean) { ProtoLog.v(WM_SHELL, "application=%s supports multi-instance", packageName) return appProp.boolean @@ -96,6 +114,66 @@ class MultiInstanceHelper @JvmOverloads constructor( return false } + override fun onShellCommand(args: Array<out String>?, pw: PrintWriter?): Boolean { + if (pw == null || args == null || args.isEmpty()) { + return false + } + when (args[0]) { + "list" -> return dumpSupportedApps(pw) + } + return false + } + + override fun printShellCommandHelp(pw: PrintWriter, prefix: String) { + pw.println("${prefix}list") + pw.println("$prefix Lists all the packages that support the multiinstance property") + } + + /** + * Dumps the static allowlist and list of apps that have the declared property in the manifest. + */ + private fun dumpSupportedApps(pw: PrintWriter): Boolean { + pw.println("Static allow list (for all users):") + staticAppsSupportingMultiInstance.forEach { pkg -> + pw.println(" $pkg") + } + + // TODO(b/391693747): Dump this per-user once PM allows us to query properties + // for non-calling users + val apps = packageManager.queryApplicationProperty( + PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI) + val activities = packageManager.queryActivityProperty( + PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI) + val appsWithProperty = (apps + activities) + .sortedWith(object : Comparator<Property?> { + override fun compare(o1: Property?, o2: Property?): Int { + if (o1?.packageName != o2?.packageName) { + return o1?.packageName!!.compareTo(o2?.packageName!!) + } else { + if (o1?.className != null) { + return o1.className!!.compareTo(o2?.className!!) + } else if (o2?.className != null) { + return -o2.className!!.compareTo(o1?.className!!) + } + return 0 + } + } + }) + if (appsWithProperty.isNotEmpty()) { + pw.println("Apps (User ${context.userId}):") + appsWithProperty.forEach { prop -> + if (prop.isBoolean && prop.boolean) { + if (prop.className != null) { + pw.println(" ${prop.packageName}/${prop.className}") + } else { + pw.println(" ${prop.packageName}") + } + } + } + } + return true + } + companion object { /** Returns the component from a PendingIntent */ @JvmStatic diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/UserProfileContexts.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/UserProfileContexts.kt new file mode 100644 index 000000000000..0577f9e625ca --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/UserProfileContexts.kt @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.common + +import android.app.ActivityManager +import android.content.Context +import android.content.pm.UserInfo +import android.os.UserHandle +import android.os.UserManager +import android.util.SparseArray +import com.android.wm.shell.sysui.ShellController +import com.android.wm.shell.sysui.ShellInit +import com.android.wm.shell.sysui.UserChangeListener + +/** Creates and manages contexts for all the profiles of the current user. */ +class UserProfileContexts( + private val baseContext: Context, + private val shellController: ShellController, + shellInit: ShellInit, +) { + // Contexts for all the profiles of the current user. + private val currentProfilesContext = SparseArray<Context>() + + lateinit var userContext: Context + private set + + init { + shellInit.addInitCallback(this::onInit, this) + } + + private fun onInit() { + shellController.addUserChangeListener( + object : UserChangeListener { + override fun onUserChanged(newUserId: Int, userContext: Context) { + currentProfilesContext.clear() + this@UserProfileContexts.userContext = userContext + currentProfilesContext.put(newUserId, userContext) + } + + override fun onUserProfilesChanged(profiles: List<UserInfo>) { + updateProfilesContexts(profiles) + } + } + ) + val defaultUserId = ActivityManager.getCurrentUser() + val userManager = baseContext.getSystemService(UserManager::class.java) + userContext = baseContext.createContextAsUser(UserHandle.of(defaultUserId), /* flags= */ 0) + updateProfilesContexts(userManager.getProfiles(defaultUserId)) + } + + private fun updateProfilesContexts(profiles: List<UserInfo>) { + for (profile in profiles) { + if (profile.id in currentProfilesContext) continue + val profileContext = baseContext.createContextAsUser(profile.userHandle, /* flags= */ 0) + currentProfilesContext.put(profile.id, profileContext) + } + val profilesToRemove = buildList<Int> { + for (i in 0..<currentProfilesContext.size()) { + val userId = currentProfilesContext.keyAt(i) + if (profiles.none { it.id == userId }) { + add(userId) + } + } + } + profilesToRemove.forEach { currentProfilesContext.remove(it) } + } + + operator fun get(userId: Int): Context? = currentProfilesContext.get(userId) +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/IPip.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/IPip.aidl index 37779077f9b6..1444a626fb11 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/IPip.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/IPip.aidl @@ -16,7 +16,7 @@ package com.android.wm.shell.common.pip; -import android.app.ActivityManager; +import android.app.ActivityManager.RunningTaskInfo; import android.app.PictureInPictureParams; import android.view.SurfaceControl; import android.content.ComponentName; @@ -42,7 +42,7 @@ interface IPip { bounds * @return destination bounds the PiP window should land into */ - Rect startSwipePipToHome(in ActivityManager.RunningTaskInfo taskInfo, int launcherRotation, + Rect startSwipePipToHome(in RunningTaskInfo taskInfo, int launcherRotation, in Rect hotseatKeepClearArea) = 1; /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java index 83e5e31bd125..84c30a52e8a6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java @@ -76,8 +76,7 @@ public class SplitScreenUtils { * Returns whether left/right split is allowed in portrait. */ public static boolean allowLeftRightSplitInPortrait(Resources res) { - return Flags.enableLeftRightSplitInPortrait() && res.getBoolean( - com.android.internal.R.bool.config_leftRightSplitInPortrait); + return res.getBoolean(com.android.internal.R.bool.config_leftRightSplitInPortrait); } /** 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 deleted file mode 100644 index d1dcc9b1d591..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt +++ /dev/null @@ -1,47 +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. - */ - -@file:JvmName("AppCompatUtils") - -package com.android.wm.shell.compatui - -import android.app.ActivityManager.RunningTaskInfo -import android.content.Context -import com.android.internal.R - -// TODO(b/347289970): Consider replacing with API -/** - * If the top activity should be exempt from desktop windowing and forced back to fullscreen. - * Currently includes all system ui activities and modal dialogs. However if the top activity is not - * being displayed, regardless of its configuration, we will not exempt it as to remain in the - * desktop windowing environment. - */ -fun isTopActivityExemptFromDesktopWindowing(context: Context, task: RunningTaskInfo) = - (isSystemUiTask(context, task) || isTransparentTask(task)) - && !task.isTopActivityNoDisplay - -/** - * Returns true if all activities in a tasks stack are transparent. If there are no activities will - * return false. - */ -fun isTransparentTask(task: RunningTaskInfo): Boolean = task.isActivityStackTransparent - && task.numActivities > 0 - -private fun isSystemUiTask(context: Context, task: RunningTaskInfo): Boolean { - val sysUiPackageName: String = - context.resources.getString(R.string.config_systemUi) - return task.baseActivity?.packageName == sysUiPackageName -} 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 84042591ad1b..e0a829df79ad 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 @@ -43,6 +43,8 @@ import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.WindowManagerShellWrapper; import com.android.wm.shell.activityembedding.ActivityEmbeddingController; +import com.android.wm.shell.appzoomout.AppZoomOut; +import com.android.wm.shell.appzoomout.AppZoomOutController; import com.android.wm.shell.back.BackAnimation; import com.android.wm.shell.back.BackAnimationBackground; import com.android.wm.shell.back.BackAnimationController; @@ -112,9 +114,8 @@ import com.android.wm.shell.shared.TransactionPool; import com.android.wm.shell.shared.annotations.ShellAnimationThread; import com.android.wm.shell.shared.annotations.ShellMainThread; import com.android.wm.shell.shared.annotations.ShellSplashscreenThread; +import com.android.wm.shell.shared.desktopmode.DesktopModeCompatPolicy; import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; -import com.android.wm.shell.appzoomout.AppZoomOut; -import com.android.wm.shell.appzoomout.AppZoomOutController; import com.android.wm.shell.splitscreen.SplitScreen; import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.startingsurface.StartingSurface; @@ -258,6 +259,12 @@ public abstract class WMShellBaseModule { @WMSingleton @Provides + static DesktopModeCompatPolicy provideDesktopModeCompatPolicy(Context context) { + return new DesktopModeCompatPolicy(context); + } + + @WMSingleton + @Provides static Optional<CompatUIHandler> provideCompatUIController( Context context, ShellInit shellInit, @@ -410,9 +417,13 @@ public abstract class WMShellBaseModule { @WMSingleton @Provides - static MultiInstanceHelper provideMultiInstanceHelper(Context context) { + static MultiInstanceHelper provideMultiInstanceHelper( + Context context, + ShellInit shellInit, + ShellCommandHandler shellCommandHandler + ) { return new MultiInstanceHelper(context, context.getPackageManager(), - Flags.supportsMultiInstanceSystemUi()); + shellInit, shellCommandHandler, Flags.supportsMultiInstanceSystemUi()); } // 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 67e345365d26..4bbe22a59315 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 @@ -37,6 +37,7 @@ import android.os.UserManager; import android.view.Choreographer; import android.view.IWindowManager; import android.view.WindowManager; +import android.window.DesktopModeFlags; import androidx.annotation.OptIn; @@ -70,6 +71,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.TaskStackListenerImpl; +import com.android.wm.shell.common.UserProfileContexts; import com.android.wm.shell.common.split.SplitState; import com.android.wm.shell.compatui.letterbox.LetterboxCommandHandler; import com.android.wm.shell.compatui.letterbox.LetterboxTransitionObserver; @@ -95,6 +97,7 @@ 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; +import com.android.wm.shell.desktopmode.OverviewToDesktopTransitionObserver; import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator; import com.android.wm.shell.desktopmode.SpringDragToDesktopTransitionHandler; import com.android.wm.shell.desktopmode.ToggleResizeDesktopTaskTransitionHandler; @@ -107,6 +110,8 @@ import com.android.wm.shell.desktopmode.education.AppToWebEducationController; import com.android.wm.shell.desktopmode.education.AppToWebEducationFilter; import com.android.wm.shell.desktopmode.education.data.AppHandleEducationDatastoreRepository; import com.android.wm.shell.desktopmode.education.data.AppToWebEducationDatastoreRepository; +import com.android.wm.shell.desktopmode.multidesks.DesksOrganizer; +import com.android.wm.shell.desktopmode.multidesks.RootTaskDesksOrganizer; import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository; import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer; import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializerImpl; @@ -128,6 +133,7 @@ import com.android.wm.shell.shared.TransactionPool; import com.android.wm.shell.shared.annotations.ShellAnimationThread; import com.android.wm.shell.shared.annotations.ShellBackgroundThread; import com.android.wm.shell.shared.annotations.ShellMainThread; +import com.android.wm.shell.shared.desktopmode.DesktopModeCompatPolicy; import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.sysui.ShellCommandHandler; @@ -394,6 +400,7 @@ public abstract class WMShellModule { ShellTaskOrganizer shellTaskOrganizer, Optional<DesktopUserRepositories> desktopUserRepositories, Optional<DesktopTasksController> desktopTasksController, + DesktopModeLoggerTransitionObserver desktopModeLoggerTransitionObserver, LaunchAdjacentController launchAdjacentController, WindowDecorViewModel windowDecorViewModel, Optional<TaskChangeListener> taskChangeListener) { @@ -406,6 +413,7 @@ public abstract class WMShellModule { shellTaskOrganizer, desktopUserRepositories, desktopTasksController, + desktopModeLoggerTransitionObserver, launchAdjacentController, windowDecorViewModel, taskChangeListener); @@ -702,6 +710,16 @@ public abstract class WMShellModule { @WMSingleton @Provides + static DesksOrganizer provideDesksOrganizer( + @NonNull ShellInit shellInit, + @NonNull ShellCommandHandler shellCommandHandler, + @NonNull ShellTaskOrganizer shellTaskOrganizer + ) { + return new RootTaskDesksOrganizer(shellInit, shellCommandHandler, shellTaskOrganizer); + } + + @WMSingleton + @Provides @DynamicOverride static DesktopTasksController provideDesktopTasksController( Context context, @@ -739,7 +757,11 @@ public abstract class WMShellModule { DesktopModeUiEventLogger desktopModeUiEventLogger, DesktopTilingDecorViewModel desktopTilingDecorViewModel, DesktopWallpaperActivityTokenProvider desktopWallpaperActivityTokenProvider, - Optional<BubbleController> bubbleController) { + Optional<BubbleController> bubbleController, + OverviewToDesktopTransitionObserver overviewToDesktopTransitionObserver, + DesksOrganizer desksOrganizer, + UserProfileContexts userProfileContexts, + DesktopModeCompatPolicy desktopModeCompatPolicy) { return new DesktopTasksController( context, shellInit, @@ -772,7 +794,11 @@ public abstract class WMShellModule { desktopModeUiEventLogger, desktopTilingDecorViewModel, desktopWallpaperActivityTokenProvider, - bubbleController); + bubbleController, + overviewToDesktopTransitionObserver, + desksOrganizer, + userProfileContexts, + desktopModeCompatPolicy); } @WMSingleton @@ -789,7 +815,9 @@ public abstract class WMShellModule { ReturnToDragStartAnimator returnToDragStartAnimator, @DynamicOverride DesktopUserRepositories desktopUserRepositories, DesktopModeEventLogger desktopModeEventLogger, - WindowDecorTaskResourceLoader windowDecorTaskResourceLoader) { + WindowDecorTaskResourceLoader windowDecorTaskResourceLoader, + FocusTransitionObserver focusTransitionObserver, + @ShellMainThread ShellExecutor mainExecutor) { return new DesktopTilingDecorViewModel( context, mainDispatcher, @@ -803,7 +831,9 @@ public abstract class WMShellModule { returnToDragStartAnimator, desktopUserRepositories, desktopModeEventLogger, - windowDecorTaskResourceLoader + windowDecorTaskResourceLoader, + focusTransitionObserver, + mainExecutor ); } @@ -904,7 +934,7 @@ public abstract class WMShellModule { if (DesktopModeStatus.canEnterDesktopMode(context) && useKeyGestureEventHandler() && manageKeyGestures() && (Flags.enableMoveToNextDisplayShortcut() - || Flags.enableTaskResizingKeyboardShortcuts())) { + || DesktopModeFlags.ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS.isTrue())) { return Optional.of(new DesktopModeKeyGestureHandler(context, desktopModeWindowDecorViewModel, desktopTasksController, inputManager, shellTaskOrganizer, focusTransitionObserver, @@ -950,7 +980,8 @@ public abstract class WMShellModule { DesktopModeEventLogger desktopModeEventLogger, DesktopModeUiEventLogger desktopModeUiEventLogger, WindowDecorTaskResourceLoader taskResourceLoader, - RecentsTransitionHandler recentsTransitionHandler + RecentsTransitionHandler recentsTransitionHandler, + DesktopModeCompatPolicy desktopModeCompatPolicy ) { if (!DesktopModeStatus.canEnterDesktopModeOrShowAppHandle(context)) { return Optional.empty(); @@ -966,7 +997,7 @@ public abstract class WMShellModule { desktopTasksLimiter, appHandleEducationController, appToWebEducationController, windowDecorCaptionHandleRepository, activityOrientationChangeHandler, focusTransitionObserver, desktopModeEventLogger, desktopModeUiEventLogger, - taskResourceLoader, recentsTransitionHandler)); + taskResourceLoader, recentsTransitionHandler, desktopModeCompatPolicy)); } @WMSingleton @@ -974,9 +1005,10 @@ public abstract class WMShellModule { static WindowDecorTaskResourceLoader provideWindowDecorTaskResourceLoader( @NonNull Context context, @NonNull ShellInit shellInit, @NonNull ShellController shellController, - @NonNull ShellCommandHandler shellCommandHandler) { + @NonNull ShellCommandHandler shellCommandHandler, + @NonNull UserProfileContexts userProfileContexts) { return new WindowDecorTaskResourceLoader(context, shellInit, shellController, - shellCommandHandler); + shellCommandHandler, userProfileContexts); } @WMSingleton @@ -987,7 +1019,8 @@ public abstract class WMShellModule { @ShellAnimationThread ShellExecutor animExecutor, ShellInit shellInit, Transitions transitions, - @DynamicOverride DesktopUserRepositories desktopUserRepositories) { + @DynamicOverride DesktopUserRepositories desktopUserRepositories, + DesktopModeCompatPolicy desktopModeCompatPolicy) { if (!DesktopModeStatus.canEnterDesktopMode(context) || !ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue() || !Flags.enableDesktopSystemDialogsTransitions()) { @@ -996,7 +1029,7 @@ public abstract class WMShellModule { return Optional.of( new SystemModalsTransitionHandler( context, mainExecutor, animExecutor, shellInit, transitions, - desktopUserRepositories)); + desktopUserRepositories, desktopModeCompatPolicy)); } @WMSingleton @@ -1180,10 +1213,12 @@ public abstract class WMShellModule { Transitions transitions, DisplayController displayController, RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, - IWindowManager windowManager + IWindowManager windowManager, + Optional<DesktopUserRepositories> desktopUserRepositories, + Optional<DesktopTasksController> desktopTasksController, + ShellTaskOrganizer shellTaskOrganizer ) { - if (!DesktopModeStatus.canEnterDesktopMode(context) - || !Flags.enableDisplayWindowingModeSwitching()) { + if (!DesktopModeStatus.canEnterDesktopMode(context)) { return Optional.empty(); } return Optional.of( @@ -1193,7 +1228,10 @@ public abstract class WMShellModule { transitions, displayController, rootTaskDisplayAreaOrganizer, - windowManager)); + windowManager, + desktopUserRepositories.get(), + desktopTasksController.get(), + shellTaskOrganizer)); } @WMSingleton @@ -1397,4 +1435,20 @@ public abstract class WMShellModule { return new Object(); } + @WMSingleton + @Provides + static OverviewToDesktopTransitionObserver provideOverviewToDesktopTransitionObserver( + Transitions transitions, ShellInit shellInit) { + return new OverviewToDesktopTransitionObserver(transitions, shellInit); + } + + @WMSingleton + @Provides + static UserProfileContexts provideUserProfilesContexts( + Context context, + ShellController shellController, + ShellInit shellInit) { + return new UserProfileContexts(context, shellController, shellInit); + } + } 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 793bdf0b5614..413300612f7d 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 @@ -159,11 +159,12 @@ public abstract class Pip2Module { PipUiEventLogger pipUiEventLogger, PipTaskListener pipTaskListener, @NonNull PipTransitionState pipTransitionState, + @NonNull PipDisplayLayoutState pipDisplayLayoutState, @ShellMainThread ShellExecutor mainExecutor, @ShellMainThread Handler mainHandler) { return new PhonePipMenuController(context, pipBoundsState, pipMediaController, - systemWindows, pipUiEventLogger, pipTaskListener, pipTransitionState, mainExecutor, - mainHandler); + systemWindows, pipUiEventLogger, pipTaskListener, pipTransitionState, + pipDisplayLayoutState, mainExecutor, mainHandler); } @@ -178,6 +179,8 @@ public abstract class Pip2Module { @NonNull PipTransitionState pipTransitionState, @NonNull PipScheduler pipScheduler, @NonNull SizeSpecSource sizeSpecSource, + @NonNull PipDisplayLayoutState pipDisplayLayoutState, + DisplayController displayController, PipMotionHelper pipMotionHelper, FloatingContentCoordinator floatingContentCoordinator, PipUiEventLogger pipUiEventLogger, @@ -185,8 +188,9 @@ public abstract class Pip2Module { Optional<PipPerfHintController> pipPerfHintControllerOptional) { return new PipTouchHandler(context, shellInit, shellCommandHandler, menuPhoneController, pipBoundsAlgorithm, pipBoundsState, pipTransitionState, pipScheduler, - sizeSpecSource, pipMotionHelper, floatingContentCoordinator, pipUiEventLogger, - mainExecutor, pipPerfHintControllerOptional); + sizeSpecSource, pipDisplayLayoutState, displayController, pipMotionHelper, + floatingContentCoordinator, pipUiEventLogger, mainExecutor, + pipPerfHintControllerOptional); } @WMSingleton 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 43e8d2a30930..6f455df6cfec 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 @@ -16,7 +16,10 @@ package com.android.wm.shell.desktopmode +import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM +import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED +import android.app.WindowConfiguration.windowingModeToString import android.content.Context import android.provider.Settings import android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS @@ -24,9 +27,15 @@ import android.view.Display.DEFAULT_DISPLAY import android.view.IWindowManager import android.view.WindowManager.TRANSIT_CHANGE import android.window.WindowContainerTransaction +import com.android.internal.protolog.ProtoLog +import com.android.window.flags.Flags 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.DisplayController.OnDisplaysChangedListener +import com.android.wm.shell.desktopmode.multidesks.OnDeskRemovedListener +import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE +import com.android.wm.shell.shared.desktopmode.DesktopModeStatus import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.Transitions @@ -38,7 +47,13 @@ class DesktopDisplayEventHandler( private val displayController: DisplayController, private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer, private val windowManager: IWindowManager, -) : OnDisplaysChangedListener { + private val desktopUserRepositories: DesktopUserRepositories, + private val desktopTasksController: DesktopTasksController, + private val shellTaskOrganizer: ShellTaskOrganizer, +) : OnDisplaysChangedListener, OnDeskRemovedListener { + + private val desktopRepository: DesktopRepository + get() = desktopUserRepositories.current init { shellInit.addInitCallback({ onInit() }, this) @@ -46,23 +61,48 @@ class DesktopDisplayEventHandler( private fun onInit() { displayController.addDisplayWindowListener(this) + + if (Flags.enableMultipleDesktopsBackend()) { + desktopTasksController.onDeskRemovedListener = this + } } override fun onDisplayAdded(displayId: Int) { - if (displayId == DEFAULT_DISPLAY) { + if (displayId != DEFAULT_DISPLAY) { + refreshDisplayWindowingMode() + } + + if (!supportsDesks(displayId)) { + logV("Display #$displayId does not support desks") return } - refreshDisplayWindowingMode() + logV("Creating new desk in new display#$displayId") + // TODO: b/362720497 - when SystemUI crashes with a freeform task open for any reason, the + // task is recreated and received in [FreeformTaskListener] before this display callback + // is invoked, which results in the repository trying to add the task to a desk before the + // desk has been recreated here, which may result in a crash-loop if the repository is + // checking that the desk exists before adding a task to it. See b/391984373. + desktopTasksController.createDesk(displayId) } override fun onDisplayRemoved(displayId: Int) { - if (displayId == DEFAULT_DISPLAY) { - return + if (displayId != DEFAULT_DISPLAY) { + refreshDisplayWindowingMode() + } + + // TODO: b/362720497 - move desks in closing display to the remaining desk. + } + + override fun onDeskRemoved(lastDisplayId: Int, deskId: Int) { + val remainingDesks = desktopRepository.getNumberOfDesks(lastDisplayId) + if (remainingDesks == 0) { + logV("All desks removed from display#$lastDisplayId, creating empty desk") + desktopTasksController.createDesk(lastDisplayId) } - refreshDisplayWindowingMode() } private fun refreshDisplayWindowingMode() { + if (!Flags.enableDisplayWindowingModeSwitching()) return // TODO: b/375319538 - Replace the check with a DisplayManager API once it's available. val isExtendedDisplayEnabled = 0 != @@ -89,13 +129,46 @@ class DesktopDisplayEventHandler( } val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY) requireNotNull(tdaInfo) { "DisplayAreaInfo of DEFAULT_DISPLAY must be non-null." } - if (tdaInfo.configuration.windowConfiguration.windowingMode == targetDisplayWindowingMode) { + val currentDisplayWindowingMode = tdaInfo.configuration.windowConfiguration.windowingMode + if (currentDisplayWindowingMode == targetDisplayWindowingMode) { // Already in the target mode. return } + logV( + "As an external display is connected, changing default display's windowing mode from" + + " ${windowingModeToString(currentDisplayWindowingMode)}" + + " to ${windowingModeToString(targetDisplayWindowingMode)}" + ) + val wct = WindowContainerTransaction() wct.setWindowingMode(tdaInfo.token, targetDisplayWindowingMode) + shellTaskOrganizer + .getRunningTasks(DEFAULT_DISPLAY) + .filter { it.activityType == ACTIVITY_TYPE_STANDARD } + .forEach { + // TODO: b/391965153 - Reconsider the logic under multi-desk window hierarchy + when (it.windowingMode) { + currentDisplayWindowingMode -> { + wct.setWindowingMode(it.token, currentDisplayWindowingMode) + } + targetDisplayWindowingMode -> { + wct.setWindowingMode(it.token, WINDOWING_MODE_UNDEFINED) + } + } + } transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ null) } + + // TODO: b/362720497 - connected/projected display considerations. + private fun supportsDesks(displayId: Int): Boolean = + DesktopModeStatus.canEnterDesktopMode(context) + + private fun logV(msg: String, vararg arguments: Any?) { + ProtoLog.v(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments) + } + + companion object { + private const val TAG = "DesktopDisplayEventHandler" + } } 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 702c67473db2..f5a95a670036 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 @@ -24,10 +24,10 @@ import android.view.MotionEvent import android.view.MotionEvent.TOOL_TYPE_FINGER import android.view.MotionEvent.TOOL_TYPE_MOUSE import android.view.MotionEvent.TOOL_TYPE_STYLUS +import android.window.DesktopModeFlags import com.android.internal.annotations.VisibleForTesting import com.android.internal.protolog.ProtoLog import com.android.internal.util.FrameworkStatsLog -import com.android.window.flags.Flags import com.android.wm.shell.EventLogTags import com.android.wm.shell.common.DisplayController import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE @@ -185,7 +185,7 @@ class DesktopModeEventLogger { displayController: DisplayController? = null, displayLayoutSize: Size? = null, ) { - if (!Flags.enableResizingMetrics()) return + if (!DesktopModeFlags.ENABLE_RESIZING_METRICS.isTrue) return val sessionId = currentSessionId.get() if (sessionId == NO_SESSION_ID) { @@ -232,7 +232,7 @@ class DesktopModeEventLogger { displayController: DisplayController? = null, displayLayoutSize: Size? = null, ) { - if (!Flags.enableResizingMetrics()) return + if (!DesktopModeFlags.ENABLE_RESIZING_METRICS.isTrue) return val sessionId = currentSessionId.get() if (sessionId == NO_SESSION_ID) { @@ -342,6 +342,7 @@ class DesktopModeEventLogger { taskUpdate.unminimizeReason?.reason ?: UNSET_UNMINIMIZE_REASON, /* visible_task_count */ taskUpdate.visibleTaskCount, + taskUpdate.focusReason?.reason ?: UNSET_FOCUS_REASON, ) EventLogTags.writeWmShellDesktopModeTaskUpdate( /* task_event */ @@ -364,6 +365,7 @@ class DesktopModeEventLogger { taskUpdate.unminimizeReason?.reason ?: UNSET_UNMINIMIZE_REASON, /* visible_task_count */ taskUpdate.visibleTaskCount, + taskUpdate.focusReason?.reason ?: UNSET_FOCUS_REASON, ) } @@ -408,6 +410,8 @@ class DesktopModeEventLogger { * @property taskY y-coordinate of the top-left corner * @property minimizeReason the reason the task was minimized * @property unminimizeReason the reason the task was unminimized + * @property visibleTaskCount the number of visible tasks after this update + * @property focusReason the reason the task was focused */ data class TaskUpdate( val instanceId: Int, @@ -419,6 +423,7 @@ class DesktopModeEventLogger { val minimizeReason: MinimizeReason? = null, val unminimizeReason: UnminimizeReason? = null, val visibleTaskCount: Int, + val focusReason: FocusReason? = null, ) /** @@ -509,6 +514,16 @@ class DesktopModeEventLogger { ), } + // Default value used when the task was not unminimized. + @VisibleForTesting + const val UNSET_FOCUS_REASON = + FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__FOCUS_REASON__UNSET_FOCUS + + /** The reason a task was unminimized. */ + enum class FocusReason(val reason: Int) { + UNKNOWN(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__FOCUS_REASON__FOCUS_UNKNOWN) + } + /** * Enum EnterReason mapped to the EnterReason definition in * stats/atoms/desktopmode/desktopmode_extensions_atoms.proto 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 9334898fdb93..5269318943d9 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 @@ -23,10 +23,10 @@ import android.hardware.input.InputManager import android.hardware.input.InputManager.KeyGestureEventHandler import android.hardware.input.KeyGestureEvent import android.os.IBinder +import android.window.DesktopModeFlags 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 @@ -144,7 +144,8 @@ class DesktopModeKeyGestureHandler( 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() + DesktopModeFlags.ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS.isTrue && + manageKeyGestures() else -> false } 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 df4b1c4a66ec..3b051694ae81 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 @@ -27,7 +27,9 @@ import android.os.Trace import android.util.SparseArray import android.view.SurfaceControl import android.view.WindowManager +import android.view.WindowManager.TRANSIT_OPEN import android.window.TransitionInfo +import android.window.TransitionInfo.FLAG_MOVED_TO_TOP import androidx.annotation.VisibleForTesting import androidx.core.util.containsKey import androidx.core.util.forEach @@ -39,6 +41,7 @@ import com.android.internal.protolog.ProtoLog import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.EnterReason import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ExitReason +import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.FocusReason import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.TaskUpdate import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UnminimizeReason @@ -91,6 +94,8 @@ class DesktopModeLoggerTransitionObserver( // following enter reason could be Screen On private var wasPreviousTransitionExitByScreenOff: Boolean = false + private var focusedFreeformTask: TaskInfo? = null + @VisibleForTesting var isSessionActive: Boolean = false fun onInit() { @@ -151,6 +156,7 @@ class DesktopModeLoggerTransitionObserver( transitionInfo = info, preTransitionVisibleFreeformTasks = visibleFreeformTaskInfos, postTransitionVisibleFreeformTasks = postTransitionVisibleFreeformTasks, + newFocusedFreeformTask = getNewFocusedFreeformTask(info), ) wasPreviousTransitionExitToOverview = info.isExitToRecentsTransition() } @@ -161,6 +167,42 @@ class DesktopModeLoggerTransitionObserver( override fun onTransitionFinished(transition: IBinder, aborted: Boolean) {} + fun onTaskVanished(taskInfo: RunningTaskInfo) { + // At this point the task should have been cleared up due to transition. If it's not yet + // cleared up, it might be one of the edge cases where transitions don't give the correct + // signal. + if (visibleFreeformTaskInfos.containsKey(taskInfo.taskId)) { + val postTransitionFreeformTasks: SparseArray<TaskInfo> = SparseArray() + postTransitionFreeformTasks.putAll(visibleFreeformTaskInfos) + postTransitionFreeformTasks.remove(taskInfo.taskId) + ProtoLog.v( + WM_SHELL_DESKTOP_MODE, + "DesktopModeLogger: processing tasks after task vanished %s", + postTransitionFreeformTasks.size(), + ) + identifyLogEventAndUpdateState( + transition = null, + transitionInfo = null, + preTransitionVisibleFreeformTasks = visibleFreeformTaskInfos, + postTransitionVisibleFreeformTasks = postTransitionFreeformTasks, + newFocusedFreeformTask = null, + ) + } + } + + // Returns null if there was no change in focused task + private fun getNewFocusedFreeformTask(info: TransitionInfo): TaskInfo? { + val freeformWindowChanges = + info.changes + .filter { it.taskInfo != null && it.requireTaskInfo().taskId != INVALID_TASK_ID } + .filter { it.requireTaskInfo().isFreeformWindow() } + return freeformWindowChanges + .findLast { change -> + change.hasFlags(FLAG_MOVED_TO_TOP) || change.mode == TRANSIT_OPEN + } + ?.taskInfo + } + private fun getPostTransitionVisibleFreeformTaskInfos( info: TransitionInfo ): SparseArray<TaskInfo> { @@ -234,10 +276,11 @@ class DesktopModeLoggerTransitionObserver( * state and update it */ private fun identifyLogEventAndUpdateState( - transition: IBinder, - transitionInfo: TransitionInfo, + transition: IBinder?, + transitionInfo: TransitionInfo?, preTransitionVisibleFreeformTasks: SparseArray<TaskInfo>, postTransitionVisibleFreeformTasks: SparseArray<TaskInfo>, + newFocusedFreeformTask: TaskInfo?, ) { if ( postTransitionVisibleFreeformTasks.isEmpty() && @@ -250,6 +293,7 @@ class DesktopModeLoggerTransitionObserver( transitionInfo, preTransitionVisibleFreeformTasks, postTransitionVisibleFreeformTasks, + newFocusedFreeformTask, ) desktopModeEventLogger.logSessionExit(getExitReason(transitionInfo)) @@ -268,6 +312,7 @@ class DesktopModeLoggerTransitionObserver( transitionInfo, preTransitionVisibleFreeformTasks, postTransitionVisibleFreeformTasks, + newFocusedFreeformTask, ) } else if (isSessionActive) { // Session is neither starting, nor finishing, log task updates if there are any @@ -276,24 +321,32 @@ class DesktopModeLoggerTransitionObserver( transitionInfo, preTransitionVisibleFreeformTasks, postTransitionVisibleFreeformTasks, + newFocusedFreeformTask, ) } // update the state to the new version visibleFreeformTaskInfos.clear() visibleFreeformTaskInfos.putAll(postTransitionVisibleFreeformTasks) + focusedFreeformTask = newFocusedFreeformTask } /** Compare the old and new state of taskInfos and identify and log the changes */ private fun identifyAndLogTaskUpdates( - transition: IBinder, - transitionInfo: TransitionInfo, + transition: IBinder?, + transitionInfo: TransitionInfo?, preTransitionVisibleFreeformTasks: SparseArray<TaskInfo>, postTransitionVisibleFreeformTasks: SparseArray<TaskInfo>, + newFocusedFreeformTask: TaskInfo?, ) { postTransitionVisibleFreeformTasks.forEach { taskId, taskInfo -> + val focusChangedReason = getFocusChangedReason(taskId, newFocusedFreeformTask) val currentTaskUpdate = - buildTaskUpdateForTask(taskInfo, postTransitionVisibleFreeformTasks.size()) + buildTaskUpdateForTask( + taskInfo, + postTransitionVisibleFreeformTasks.size(), + focusChangedReason = focusChangedReason, + ) val previousTaskInfo = preTransitionVisibleFreeformTasks[taskId] when { // new tasks added @@ -314,11 +367,14 @@ class DesktopModeLoggerTransitionObserver( postTransitionVisibleFreeformTasks.size().toString(), ) } + focusChangedReason != null -> + desktopModeEventLogger.logTaskInfoChanged(currentTaskUpdate) // old tasks that were resized or repositioned // TODO(b/347935387): Log changes only once they are stable. buildTaskUpdateForTask( previousTaskInfo, postTransitionVisibleFreeformTasks.size(), + focusChangedReason = focusChangedReason, ) != currentTaskUpdate -> desktopModeEventLogger.logTaskInfoChanged(currentTaskUpdate) } @@ -351,33 +407,45 @@ class DesktopModeLoggerTransitionObserver( } private fun getMinimizeReason( - transition: IBinder, - transitionInfo: TransitionInfo, + transition: IBinder?, + transitionInfo: TransitionInfo?, taskInfo: TaskInfo, ): MinimizeReason? { - if (transitionInfo.type == Transitions.TRANSIT_MINIMIZE) { + if (transitionInfo?.type == Transitions.TRANSIT_MINIMIZE) { return MinimizeReason.MINIMIZE_BUTTON } - val minimizingTask = desktopTasksLimiter.getOrNull()?.getMinimizingTask(transition) + val minimizingTask = + transition?.let { desktopTasksLimiter.getOrNull()?.getMinimizingTask(transition) } if (minimizingTask?.taskId == taskInfo.taskId) { return minimizingTask.minimizeReason } return null } - private fun getUnminimizeReason(transition: IBinder, taskInfo: TaskInfo): UnminimizeReason? { - val unminimizingTask = desktopTasksLimiter.getOrNull()?.getUnminimizingTask(transition) + private fun getUnminimizeReason(transition: IBinder?, taskInfo: TaskInfo): UnminimizeReason? { + val unminimizingTask = + transition?.let { desktopTasksLimiter.getOrNull()?.getUnminimizingTask(transition) } if (unminimizingTask?.taskId == taskInfo.taskId) { return unminimizingTask.unminimizeReason } return null } + private fun getFocusChangedReason( + taskId: Int, + newFocusedFreeformTask: TaskInfo?, + ): FocusReason? { + val newFocusedTask = newFocusedFreeformTask ?: return null + if (taskId != newFocusedTask.taskId) return null + return if (newFocusedTask != focusedFreeformTask) FocusReason.UNKNOWN else null + } + private fun buildTaskUpdateForTask( taskInfo: TaskInfo, visibleTasks: Int, minimizeReason: MinimizeReason? = null, unminimizeReason: UnminimizeReason? = null, + focusChangedReason: FocusReason? = null, ): TaskUpdate { val screenBounds = taskInfo.configuration.windowConfiguration.bounds val positionInParent = taskInfo.positionInParent @@ -393,28 +461,29 @@ class DesktopModeLoggerTransitionObserver( visibleTaskCount = visibleTasks, minimizeReason = minimizeReason, unminimizeReason = unminimizeReason, + focusReason = focusChangedReason, ) } /** Get [EnterReason] for this session enter */ - private fun getEnterReason(transitionInfo: TransitionInfo): EnterReason { + private fun getEnterReason(transitionInfo: TransitionInfo?): EnterReason { val enterReason = when { - transitionInfo.type == WindowManager.TRANSIT_WAKE + transitionInfo?.type == WindowManager.TRANSIT_WAKE // If there is a screen lock, desktop window entry is after dismissing keyguard || - (transitionInfo.type == WindowManager.TRANSIT_TO_BACK && + (transitionInfo?.type == WindowManager.TRANSIT_TO_BACK && wasPreviousTransitionExitByScreenOff) -> EnterReason.SCREEN_ON - transitionInfo.type == TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP -> + transitionInfo?.type == TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP -> EnterReason.APP_HANDLE_DRAG - transitionInfo.type == TRANSIT_ENTER_DESKTOP_FROM_APP_HANDLE_MENU_BUTTON -> + transitionInfo?.type == TRANSIT_ENTER_DESKTOP_FROM_APP_HANDLE_MENU_BUTTON -> EnterReason.APP_HANDLE_MENU_BUTTON - transitionInfo.type == TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW -> + transitionInfo?.type == TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW -> EnterReason.APP_FROM_OVERVIEW - transitionInfo.type == TRANSIT_ENTER_DESKTOP_FROM_KEYBOARD_SHORTCUT -> + 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 + 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. @@ -425,12 +494,13 @@ class DesktopModeLoggerTransitionObserver( // after // a cancelled recents. wasPreviousTransitionExitToOverview -> EnterReason.OVERVIEW - transitionInfo.type == WindowManager.TRANSIT_OPEN -> EnterReason.APP_FREEFORM_INTENT + 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, + transitionInfo?.type, ) EnterReason.UNKNOWN_ENTER } @@ -440,30 +510,31 @@ class DesktopModeLoggerTransitionObserver( } /** Get [ExitReason] for this session exit */ - private fun getExitReason(transitionInfo: TransitionInfo): ExitReason = + private fun getExitReason(transitionInfo: TransitionInfo?): ExitReason = when { - transitionInfo.type == WindowManager.TRANSIT_SLEEP -> { + transitionInfo?.type == WindowManager.TRANSIT_SLEEP -> { wasPreviousTransitionExitByScreenOff = true ExitReason.SCREEN_OFF } // TODO(b/384490301): differentiate back gesture / button exit from clicking the close // button located in the window top corner. - transitionInfo.type == WindowManager.TRANSIT_TO_BACK -> ExitReason.TASK_MOVED_TO_BACK - transitionInfo.type == WindowManager.TRANSIT_CLOSE -> ExitReason.TASK_FINISHED - transitionInfo.type == TRANSIT_EXIT_DESKTOP_MODE_TASK_DRAG -> ExitReason.DRAG_TO_EXIT - transitionInfo.type == TRANSIT_EXIT_DESKTOP_MODE_HANDLE_MENU_BUTTON -> + transitionInfo?.type == WindowManager.TRANSIT_TO_BACK -> ExitReason.TASK_MOVED_TO_BACK + transitionInfo?.type == WindowManager.TRANSIT_CLOSE -> ExitReason.TASK_FINISHED + transitionInfo?.type == TRANSIT_EXIT_DESKTOP_MODE_TASK_DRAG -> ExitReason.DRAG_TO_EXIT + transitionInfo?.type == TRANSIT_EXIT_DESKTOP_MODE_HANDLE_MENU_BUTTON -> ExitReason.APP_HANDLE_MENU_BUTTON_EXIT - transitionInfo.type == TRANSIT_EXIT_DESKTOP_MODE_KEYBOARD_SHORTCUT -> + transitionInfo?.type == TRANSIT_EXIT_DESKTOP_MODE_KEYBOARD_SHORTCUT -> ExitReason.KEYBOARD_SHORTCUT_EXIT - transitionInfo.isExitToRecentsTransition() -> ExitReason.RETURN_HOME_OR_OVERVIEW - transitionInfo.type == Transitions.TRANSIT_MINIMIZE -> ExitReason.TASK_MINIMIZED + transitionInfo?.isExitToRecentsTransition() == true -> + ExitReason.RETURN_HOME_OR_OVERVIEW + transitionInfo?.type == Transitions.TRANSIT_MINIMIZE -> ExitReason.TASK_MINIMIZED else -> { ProtoLog.w( WM_SHELL_DESKTOP_MODE, "Unknown exit reason for transition type: %s", - transitionInfo.type, + transitionInfo?.type, ) ExitReason.UNKNOWN_EXIT } @@ -475,6 +546,12 @@ class DesktopModeLoggerTransitionObserver( visibleFreeformTaskInfos.set(taskInfo.taskId, taskInfo) } + /** Sets the focused task - only used for testing. */ + @VisibleForTesting + fun setFocusedTaskForTesting(taskInfo: TaskInfo) { + focusedFreeformTask = taskInfo + } + private fun TransitionInfo.Change.requireTaskInfo(): RunningTaskInfo = this.taskInfo ?: throw IllegalStateException("Expected TaskInfo in the Change") diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt index 9b9988457808..164d04bbde65 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt @@ -110,8 +110,8 @@ class DesktopModeShellCommandHandler(private val controller: DesktopTasksControl pw.println("Error: display id should be an integer") return false } - pw.println("Not implemented.") - return false + controller.createDesk(displayId) + return true } private fun runActivateDesk(args: Array<String>, pw: PrintWriter): Boolean { 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 fa696682de28..4ff1a5f1be31 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 @@ -171,6 +171,9 @@ class DesktopRepository( /** Returns a list of all [Desk]s in the repository. */ private fun desksSequence(): Sequence<Desk> = desktopData.desksSequence() + /** Returns the number of desks in the given display. */ + fun getNumberOfDesks(displayId: Int) = desktopData.getNumberOfDesks(displayId) + /** Adds [regionListener] to inform about changes to exclusion regions for all Desktop tasks. */ fun setExclusionRegionListener(regionListener: Consumer<Region>, executor: Executor) { desktopGestureExclusionListener = regionListener @@ -201,11 +204,11 @@ class DesktopRepository( /** Adds the given desk under the given display. */ fun addDesk(displayId: Int, deskId: Int) { - desktopData.getOrCreateDesk(displayId, deskId) + desktopData.createDesk(displayId, deskId) } /** Returns the default desk in the given display. */ - fun getDefaultDesk(displayId: Int): Int? = desktopData.getDefaultDesk(displayId)?.deskId + private fun getDefaultDesk(displayId: Int): Desk? = desktopData.getDefaultDesk(displayId) /** Sets the given desk as the active one in the given display. */ fun setActiveDesk(displayId: Int, deskId: Int) { @@ -229,15 +232,14 @@ class DesktopRepository( * TODO: b/389960283 - add explicit [deskId] argument. */ private fun addActiveTask(displayId: Int, taskId: Int) { - val activeDeskId = - desktopData.getActiveDesk(displayId)?.deskId - ?: error("Expected active desk in display: $displayId") + val activeDesk = desktopData.getDefaultDesk(displayId) + checkNotNull(activeDesk) { "Expected desk in display: $displayId" } // Removes task if it is active on another desk excluding [activeDesk]. - removeActiveTask(taskId, excludedDeskId = activeDeskId) + removeActiveTask(taskId, excludedDeskId = activeDesk.deskId) - if (desktopData.getOrCreateDesk(displayId, activeDeskId).activeTasks.add(taskId)) { - logD("Adds active task=%d displayId=%d deskId=%d", taskId, displayId, activeDeskId) + if (activeDesk.activeTasks.add(taskId)) { + logD("Adds active task=%d displayId=%d deskId=%d", taskId, displayId, activeDesk.deskId) updateActiveTasksListeners(displayId) } } @@ -266,18 +268,23 @@ class DesktopRepository( * TODO: b/389960283 - add explicit [deskId] argument. */ fun addClosingTask(displayId: Int, taskId: Int) { - val activeDeskId = - desktopData.getActiveDesk(displayId)?.deskId + val activeDesk = + desktopData.getActiveDesk(displayId) ?: error("Expected active desk in display: $displayId") - if (desktopData.getOrCreateDesk(displayId, activeDeskId).closingTasks.add(taskId)) { - logD("Added closing task=%d displayId=%d deskId=%d", taskId, displayId, activeDeskId) + if (activeDesk.closingTasks.add(taskId)) { + logD( + "Added closing task=%d displayId=%d deskId=%d", + taskId, + displayId, + activeDesk.deskId, + ) } else { // If the task hasn't been removed from closing list after it disappeared. logW( "Task with taskId=%d displayId=%d deskId=%d is already closing", taskId, displayId, - activeDeskId, + activeDesk.deskId, ) } } @@ -323,7 +330,7 @@ class DesktopRepository( /** * Returns the active tasks in the given display's active desk. * - * TODO: b/389960283 - add explicit [deskId] argument. + * TODO: b/389960283 - migrate callers to [getActiveTaskIdsInDesk]. */ @VisibleForTesting fun getActiveTasks(displayId: Int): ArraySet<Int> = @@ -332,19 +339,27 @@ class DesktopRepository( /** * Returns the minimized tasks in the given display's active desk. * - * TODO: b/389960283 - add explicit [deskId] argument. + * TODO: b/389960283 - migrate callers to [getMinimizedTaskIdsInDesk]. */ fun getMinimizedTasks(displayId: Int): ArraySet<Int> = ArraySet(desktopData.getActiveDesk(displayId)?.minimizedTasks) + @VisibleForTesting + fun getMinimizedTaskIdsInDesk(deskId: Int): ArraySet<Int> = + ArraySet(desktopData.getDesk(deskId)?.minimizedTasks) + /** * Returns all active non-minimized tasks for [displayId] ordered from top to bottom. * - * TODO: b/389960283 - add explicit [deskId] argument. + * TODO: b/389960283 - migrate callers to [getExpandedTasksIdsInDeskOrdered]. */ fun getExpandedTasksOrdered(displayId: Int): List<Int> = getFreeformTasksInZOrder(displayId).filter { !isMinimizedTask(it) } + @VisibleForTesting + fun getExpandedTasksIdsInDeskOrdered(deskId: Int): List<Int> = + getFreeformTasksIdsInDeskInZOrder(deskId).filter { !isMinimizedTask(it) } + /** * Returns the count of active non-minimized tasks for [displayId]. * @@ -357,11 +372,15 @@ class DesktopRepository( /** * Returns a list of freeform tasks, ordered from top-bottom (top at index 0). * - * TODO: b/389960283 - add explicit [deskId] argument. + * TODO: b/389960283 - migrate callers to [getFreeformTasksIdsInDeskInZOrder]. */ @VisibleForTesting fun getFreeformTasksInZOrder(displayId: Int): ArrayList<Int> = - ArrayList(desktopData.getActiveDesk(displayId)?.freeformTasksInZOrder ?: emptyList()) + ArrayList(desktopData.getDefaultDesk(displayId)?.freeformTasksInZOrder ?: emptyList()) + + @VisibleForTesting + fun getFreeformTasksIdsInDeskInZOrder(deskId: Int): ArrayList<Int> = + ArrayList(desktopData.getDesk(deskId)?.freeformTasksInZOrder ?: emptyList()) /** Returns the tasks inside the given desk. */ fun getActiveTaskIdsInDesk(deskId: Int): Set<Int> = @@ -401,8 +420,8 @@ class DesktopRepository( } val prevCount = getVisibleTaskCount(displayId) if (isVisible) { - desktopData.getActiveDesk(displayId)?.visibleTasks?.add(taskId) - ?: error("Expected non-null active desk in display $displayId") + desktopData.getDefaultDesk(displayId)?.visibleTasks?.add(taskId) + ?: error("Expected non-null desk in display $displayId") unminimizeTask(displayId, taskId) } else { desktopData.getActiveDesk(displayId)?.visibleTasks?.remove(taskId) @@ -587,17 +606,15 @@ class DesktopRepository( * TODO: b/389960283 - add explicit [deskId] argument. */ private fun addOrMoveFreeformTaskToTop(displayId: Int, taskId: Int) { - val activeDesk = - desktopData.getActiveDesk(displayId) - ?: error("Expected a desk to be active in display: $displayId") + val desk = getDefaultDesk(displayId) ?: error("Expected a desk in display: $displayId") logD( "Add or move task to top: display=%d taskId=%d deskId=%d", taskId, displayId, - activeDesk.deskId, + desk.deskId, ) - desktopData.forAllDesks { _, desk -> desk.freeformTasksInZOrder.remove(taskId) } - activeDesk.freeformTasksInZOrder.add(0, taskId) + desktopData.forAllDesks { _, desk1 -> desk1.freeformTasksInZOrder.remove(taskId) } + desk.freeformTasksInZOrder.add(0, taskId) // Unminimize the task if it is minimized. unminimizeTask(displayId, taskId) if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) { @@ -835,13 +852,8 @@ class DesktopRepository( /** An interface for the desktop hierarchy's data managed by this repository. */ private interface DesktopData { - /** - * Returns the existing desk or creates a new entry if needed. - * - * TODO: 389787966 - consider removing this as it cannot be assumed a desk can be created in - * all devices / form-factors. - */ - fun getOrCreateDesk(displayId: Int, deskId: Int): Desk + /** Creates a desk record. */ + fun createDesk(displayId: Int, deskId: Int) /** Returns the desk with the given id, or null if it does not exist. */ fun getDesk(deskId: Int): Desk? @@ -894,7 +906,8 @@ class DesktopRepository( /** * A [DesktopData] implementation that only supports one desk per display. * - * Internally, it reuses the displayId as that display's single desk's id. + * Internally, it reuses the displayId as that display's single desk's id. It also never truly + * "removes" a desk, it just clears its content. */ private class SingleDesktopData : DesktopData { private val deskByDisplayId = @@ -907,12 +920,16 @@ class DesktopRepository( } } - override fun getOrCreateDesk(displayId: Int, deskId: Int): Desk { - check(displayId == deskId) - return deskByDisplayId.getOrCreate(displayId) + override fun createDesk(displayId: Int, deskId: Int) { + check(displayId == deskId) { "Display and desk ids must match" } + deskByDisplayId.getOrCreate(displayId) } - override fun getDesk(deskId: Int): Desk = getOrCreateDesk(deskId, deskId) + override fun getDesk(deskId: Int): Desk = + // TODO: b/362720497 - consider enforcing that the desk has been created before trying + // to use it. As of now, there are cases where a task may be created faster than a + // desk is, so just create it here if needed. See b/391984373. + deskByDisplayId.getOrCreate(deskId) override fun getActiveDesk(displayId: Int): Desk { // TODO: 389787966 - consider migrating to an "active" state instead of checking the @@ -927,7 +944,7 @@ class DesktopRepository( // existence of visible desktop windows, among other factors. } - override fun getDefaultDesk(displayId: Int): Desk = getOrCreateDesk(displayId, displayId) + override fun getDefaultDesk(displayId: Int): Desk = getDesk(deskId = displayId) override fun getAllActiveDesks(): Set<Desk> = deskByDisplayId.valueIterator().asSequence().toSet() @@ -943,7 +960,7 @@ class DesktopRepository( } override fun forAllDesks(displayId: Int, consumer: (Desk) -> Unit) { - consumer(getOrCreateDesk(displayId, displayId)) + consumer(getDesk(deskId = displayId)) } override fun desksSequence(): Sequence<Desk> = deskByDisplayId.valueIterator().asSequence() @@ -962,16 +979,14 @@ class DesktopRepository( private class MultiDesktopData : DesktopData { private val desktopDisplays = SparseArray<DesktopDisplay>() - override fun getOrCreateDesk(displayId: Int, deskId: Int): Desk { + override fun createDesk(displayId: Int, deskId: Int) { val display = desktopDisplays[displayId] ?: DesktopDisplay(displayId).also { desktopDisplays[displayId] = it } - val desk = - display.orderedDesks.find { desk -> desk.deskId == deskId } - ?: Desk(deskId = deskId, displayId = displayId).also { - display.orderedDesks.add(it) - } - return desk + check(display.orderedDesks.none { desk -> desk.deskId == deskId }) { + "Attempting to create desk#$deskId that already exists in display#$displayId" + } + display.orderedDesks.add(Desk(deskId = deskId, displayId = displayId)) } override fun getDesk(deskId: Int): Desk? { @@ -999,7 +1014,8 @@ class DesktopRepository( override fun getDefaultDesk(displayId: Int): Desk? { val display = desktopDisplays[displayId] ?: return null - return display.orderedDesks.firstOrNull() + return display.orderedDesks.find { it.deskId == display.activeDeskId } + ?: display.orderedDesks.firstOrNull() } override fun getAllActiveDesks(): Set<Desk> { 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 c958a0975f11..4d87b2189115 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 @@ -70,8 +70,7 @@ class DesktopTaskChangeListener(private val desktopUserRepositories: DesktopUser if (!isFreeformTask(taskInfo)) { desktopRepository.removeTask(taskInfo.displayId, taskInfo.taskId) } - // TODO: b/367268953 - Connect this with DesktopRepository for handling - // task moving to front for tasks in windowing mode. + desktopRepository.addTask(taskInfo.displayId, taskInfo.taskId, taskInfo.isVisible) } override fun onTaskMovingToBack(taskInfo: RunningTaskInfo) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index 2b4b27046ac3..ca71cf303a1c 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 @@ -16,6 +16,7 @@ package com.android.wm.shell.desktopmode +import android.annotation.UserIdInt import android.app.ActivityManager import android.app.ActivityManager.RunningTaskInfo import android.app.ActivityOptions @@ -85,8 +86,7 @@ import com.android.wm.shell.common.RemoteCallable 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.compatui.isTransparentTask +import com.android.wm.shell.common.UserProfileContexts import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger @@ -102,6 +102,8 @@ import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler.FULLSCR import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider import com.android.wm.shell.desktopmode.minimize.DesktopWindowLimitRemoteHandler +import com.android.wm.shell.desktopmode.multidesks.DesksOrganizer +import com.android.wm.shell.desktopmode.multidesks.OnDeskRemovedListener import com.android.wm.shell.draganddrop.DragAndDropController import com.android.wm.shell.freeform.FreeformTaskTransitionStarter import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE @@ -113,6 +115,7 @@ import com.android.wm.shell.recents.RecentsTransitionStateListener.TRANSITION_ST import com.android.wm.shell.shared.TransitionUtil import com.android.wm.shell.shared.annotations.ExternalThread import com.android.wm.shell.shared.annotations.ShellMainThread +import com.android.wm.shell.shared.desktopmode.DesktopModeCompatPolicy import com.android.wm.shell.shared.desktopmode.DesktopModeStatus import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.DESKTOP_DENSITY_OVERRIDE import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.useDesktopOverrideDensity @@ -179,6 +182,10 @@ class DesktopTasksController( private val desktopTilingDecorViewModel: DesktopTilingDecorViewModel, private val desktopWallpaperActivityTokenProvider: DesktopWallpaperActivityTokenProvider, private val bubbleController: Optional<BubbleController>, + private val overviewToDesktopTransitionObserver: OverviewToDesktopTransitionObserver, + private val desksOrganizer: DesksOrganizer, + private val userProfileContexts: UserProfileContexts, + private val desktopModeCompatPolicy: DesktopModeCompatPolicy, ) : RemoteCallable<DesktopTasksController>, Transitions.TransitionHandler, @@ -231,6 +238,9 @@ class DesktopTasksController( // Used to prevent handleRequest from moving the new fullscreen task to freeform. private var dragAndDropFullscreenCookie: Binder? = null + // A listener that is invoked after a desk has been remove from the system. */ + var onDeskRemovedListener: OnDeskRemovedListener? = null + init { desktopMode = DesktopModeImpl() if (DesktopModeStatus.canEnterDesktopMode(context)) { @@ -414,18 +424,38 @@ class DesktopTasksController( return isFreeformDisplay } + /** Creates a new desk in the given display. */ + fun createDesk(displayId: Int) { + if (Flags.enableMultipleDesktopsBackend()) { + desksOrganizer.createDesk(displayId) { deskId -> + taskRepository.addDesk(displayId = displayId, deskId = deskId) + } + } else { + // In single-desk, the desk reuses the display id. + taskRepository.addDesk(displayId = displayId, deskId = displayId) + } + } + /** Moves task to desktop mode if task is running, else launches it in desktop mode. */ + @JvmOverloads fun moveTaskToDesktop( taskId: Int, wct: WindowContainerTransaction = WindowContainerTransaction(), transitionSource: DesktopModeTransitionSource, remoteTransition: RemoteTransition? = null, + callback: IMoveToDesktopCallback? = null, ): Boolean { val runningTask = shellTaskOrganizer.getRunningTaskInfo(taskId) if (runningTask == null) { - return moveBackgroundTaskToDesktop(taskId, wct, transitionSource, remoteTransition) + return moveBackgroundTaskToDesktop( + taskId, + wct, + transitionSource, + remoteTransition, + callback, + ) } - moveRunningTaskToDesktop(runningTask, wct, transitionSource, remoteTransition) + moveRunningTaskToDesktop(runningTask, wct, transitionSource, remoteTransition, callback) return true } @@ -434,6 +464,7 @@ class DesktopTasksController( wct: WindowContainerTransaction, transitionSource: DesktopModeTransitionSource, remoteTransition: RemoteTransition? = null, + callback: IMoveToDesktopCallback? = null, ): Boolean { if (recentTasksController?.findTaskInBackground(taskId) == null) { logW("moveBackgroundTaskToDesktop taskId=%d not found", taskId) @@ -466,6 +497,7 @@ class DesktopTasksController( } else { // TODO(343149901): Add DPI changes for task launch transition = enterDesktopTaskTransitionHandler.moveToDesktop(wct, transitionSource) + invokeCallbackToOverview(transition, callback) } desktopModeEnterExitTransitionListener?.onEnterDesktopModeTransitionStarted( FREEFORM_ANIMATION_DURATION @@ -483,11 +515,9 @@ class DesktopTasksController( wct: WindowContainerTransaction = WindowContainerTransaction(), transitionSource: DesktopModeTransitionSource, remoteTransition: RemoteTransition? = null, + callback: IMoveToDesktopCallback? = null, ) { - if ( - DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue() && - isTopActivityExemptFromDesktopWindowing(context, task) - ) { + if (desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(task)) { logW("Cannot enter desktop for taskId %d, ineligible top activity found", task.taskId) return } @@ -514,6 +544,7 @@ class DesktopTasksController( remoteTransitionHandler.setTransition(transition) } else { transition = enterDesktopTaskTransitionHandler.moveToDesktop(wct, transitionSource) + invokeCallbackToOverview(transition, callback) } desktopModeEnterExitTransitionListener?.onEnterDesktopModeTransitionStarted( FREEFORM_ANIMATION_DURATION @@ -524,6 +555,15 @@ class DesktopTasksController( exitResult.asExit()?.runOnTransitionStart?.invoke(transition) } + private fun invokeCallbackToOverview(transition: IBinder, callback: IMoveToDesktopCallback?) { + // TODO: b/333524374 - Remove this later. + // This is a temporary implementation for adding CUJ end and + // should be removed when animation is moved to launcher through remote transition. + if (callback != null) { + overviewToDesktopTransitionObserver.addPendingOverviewTransition(transition, callback) + } + } + /** * The first part of the animated drag to desktop transition. This is followed with a call to * [finalizeDragToDesktop] or [cancelDragToDesktop]. @@ -1446,6 +1486,7 @@ class DesktopTasksController( } private fun addLaunchHomePendingIntent(wct: WindowContainerTransaction, displayId: Int) { + val userHandle = UserHandle.of(userId) val launchHomeIntent = Intent(Intent.ACTION_MAIN).apply { if (displayId != DEFAULT_DISPLAY) { @@ -1461,11 +1502,13 @@ class DesktopTasksController( ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS } val pendingIntent = - PendingIntent.getActivity( + PendingIntent.getActivityAsUser( context, - /* requestCode = */ 0, + /* requestCode= */ 0, launchHomeIntent, PendingIntent.FLAG_IMMUTABLE, + /* options= */ null, + userHandle, ) wct.sendPendingIntent(pendingIntent, launchHomeIntent, options.toBundle()) } @@ -1776,8 +1819,7 @@ class DesktopTasksController( taskRepository.isActiveTask(triggerTask.taskId)) private fun isIncompatibleTask(task: RunningTaskInfo) = - DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue() && - isTopActivityExemptFromDesktopWindowing(context, task) + desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(task) private fun shouldHandleTaskClosing(request: TransitionRequestInfo): Boolean = ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY.isTrue() && @@ -1819,7 +1861,9 @@ class DesktopTasksController( // need updates in some cases. val baseActivity = callingTaskInfo.baseActivity ?: return val fillIn: Intent = - context.packageManager.getLaunchIntentForPackage(baseActivity.packageName) ?: return + userProfileContexts[callingTaskInfo.userId] + ?.packageManager + ?.getLaunchIntentForPackage(baseActivity.packageName) ?: return fillIn.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK) val launchIntent = PendingIntent.getActivity( @@ -2046,11 +2090,11 @@ class DesktopTasksController( */ private fun handleIncompatibleTaskLaunch(task: RunningTaskInfo): WindowContainerTransaction? { logV("handleIncompatibleTaskLaunch") - if (!isDesktopModeShowing(task.displayId)) return null + if (!isDesktopModeShowing(task.displayId) && !forceEnterDesktop(task.displayId)) return null // Only update task repository for transparent task. if ( DesktopModeFlags.INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC - .isTrue() && isTransparentTask(task) + .isTrue() && desktopModeCompatPolicy.isTransparentTask(task) ) { taskRepository.setTopTransparentFullscreenTaskId(task.displayId, task.taskId) } @@ -2698,6 +2742,7 @@ class DesktopTasksController( // TODO(b/358114479): Move this implementation into a separate class. override fun onUnhandledDrag( launchIntent: PendingIntent, + @UserIdInt userId: Int, dragEvent: DragEvent, onFinishCallback: Consumer<Boolean>, ): Boolean { @@ -2706,8 +2751,10 @@ class DesktopTasksController( // Not currently in desktop mode, ignore the drop return false } + + // TODO: val launchComponent = getComponent(launchIntent) - if (!multiInstanceHelper.supportsMultiInstanceSplit(launchComponent)) { + if (!multiInstanceHelper.supportsMultiInstanceSplit(launchComponent, userId)) { // TODO(b/320797628): Should only return early if there is an existing running task, and // notify the user as well. But for now, just ignore the drop. logV("Dropped intent does not support multi-instance") @@ -2967,6 +3014,14 @@ class DesktopTasksController( controller = null } + override fun createDesk(displayId: Int) { + // TODO: b/362720497 - Implement this API. + } + + override fun activateDesk(deskId: Int, remoteTransition: RemoteTransition?) { + // TODO: b/362720497 - Implement this API. + } + override fun showDesktopApps(displayId: Int, remoteTransition: RemoteTransition?) { executeRemoteCallWithTaskPermission(controller, "showDesktopApps") { c -> c.showDesktopApps(displayId, remoteTransition) @@ -2994,17 +3049,6 @@ class DesktopTasksController( ) } - override fun getVisibleTaskCount(displayId: Int): Int { - val result = IntArray(1) - executeRemoteCallWithTaskPermission( - controller, - "visibleTaskCount", - { controller -> result[0] = controller.visibleTaskCount(displayId) }, - /* blocking= */ true, - ) - return result[0] - } - override fun onDesktopSplitSelectAnimComplete(taskInfo: RunningTaskInfo) { executeRemoteCallWithTaskPermission(controller, "onDesktopSplitSelectAnimComplete") { c -> @@ -3023,12 +3067,14 @@ class DesktopTasksController( taskId: Int, transitionSource: DesktopModeTransitionSource, remoteTransition: RemoteTransition?, + callback: IMoveToDesktopCallback?, ) { executeRemoteCallWithTaskPermission(controller, "moveTaskToDesktop") { c -> c.moveTaskToDesktop( taskId, transitionSource = transitionSource, remoteTransition = remoteTransition, + callback = callback, ) } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl index ae4c2773215b..44f7e16e98c3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl @@ -23,11 +23,17 @@ import android.window.RemoteTransition; import com.android.wm.shell.desktopmode.IDesktopTaskListener; import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource; import com.android.wm.shell.shared.desktopmode.DesktopTaskToFrontReason; +import com.android.wm.shell.desktopmode.IMoveToDesktopCallback; /** * Interface that is exposed to remote callers to manipulate desktop mode features. */ interface IDesktopMode { + /** If possible, creates a new desk on the display whose ID is `displayId`. */ + oneway void createDesk(int displayId); + + /** Activates the desk whose ID is `deskId` on whatever display it currently exists on. */ + oneway void activateDesk(int deskId, in RemoteTransition remoteTransition); /** Show apps on the desktop on the given display */ void showDesktopApps(int displayId, in RemoteTransition remoteTransition); @@ -47,9 +53,6 @@ interface IDesktopMode { oneway void showDesktopApp(int taskId, in @nullable RemoteTransition remoteTransition, in DesktopTaskToFrontReason toFrontReason); - /** Get count of visible desktop tasks on the given display */ - int getVisibleTaskCount(int displayId); - /** Perform cleanup transactions after the animation to split select is complete */ oneway void onDesktopSplitSelectAnimComplete(in RunningTaskInfo taskInfo); @@ -58,7 +61,8 @@ interface IDesktopMode { /** Move a task with given `taskId` to desktop */ void moveToDesktop(int taskId, in DesktopModeTransitionSource transitionSource, - in @nullable RemoteTransition remoteTransition); + in @nullable RemoteTransition remoteTransition, + in @nullable IMoveToDesktopCallback callback); /** Remove desktop on the given display */ oneway void removeDesktop(int displayId); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IMoveToDesktopCallback.aidl index fc51c754e06b..6342528f9cc7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IMoveToDesktopCallback.aidl @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 The Android Open Source Project + * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,17 +14,9 @@ * limitations under the License. */ -package com.android.wm.shell.automotive; +package com.android.wm.shell.desktopmode; -import com.android.wm.shell.dagger.WMSingleton; +interface IMoveToDesktopCallback { -import dagger.Binds; -import dagger.Module; - - -@Module -public abstract class AutoShellModule { - @WMSingleton - @Binds - abstract AutoTaskStackController provideTaskStackController(AutoTaskStackControllerImpl impl); -} + void onTaskMovedToDesktop(); +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OverviewToDesktopTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OverviewToDesktopTransitionObserver.kt new file mode 100644 index 000000000000..873f98389723 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OverviewToDesktopTransitionObserver.kt @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.os.IBinder +import android.os.RemoteException +import android.util.Slog +import com.android.wm.shell.sysui.ShellInit +import com.android.wm.shell.transition.Transitions + +/** Callback to Launcher overview to notify that add to desktop from overview menu is completed. */ +class OverviewToDesktopTransitionObserver( + private val transitions: Transitions, + shellInit: ShellInit, +) : Transitions.TransitionObserver { + + private val transitionToCallback = mutableMapOf<IBinder?, IMoveToDesktopCallback?>() + + init { + shellInit.addInitCallback(::onInit, this) + } + + fun onInit() { + transitions.registerObserver(this) + } + + override fun onTransitionFinished(transition: IBinder, aborted: Boolean) { + try { + transitionToCallback[transition]?.onTaskMovedToDesktop() + transitionToCallback.clear() + } catch (e: RemoteException) { + Slog.e(TAG, "onTransitionFinished: Error calling onTaskMovedToDesktop", e) + } + } + + fun addPendingOverviewTransition(transition: IBinder?, callback: IMoveToDesktopCallback?) { + transitionToCallback += transition to callback + } + + companion object { + const val TAG = "OverviewToDesktopTransitionObserver" + } +} 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 a428ce18a49e..224ff37a1dca 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 @@ -17,6 +17,7 @@ package com.android.wm.shell.desktopmode.compatui import android.animation.ValueAnimator +import android.app.ActivityManager.RunningTaskInfo import android.content.Context import android.os.IBinder import android.view.Display.DEFAULT_DISPLAY @@ -28,13 +29,14 @@ import androidx.core.animation.addListener 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.DesktopUserRepositories +import com.android.wm.shell.desktopmode.DesktopWallpaperActivity 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 import com.android.wm.shell.shared.TransitionUtil.isOpeningMode import com.android.wm.shell.shared.TransitionUtil.isOpeningType +import com.android.wm.shell.shared.desktopmode.DesktopModeCompatPolicy import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.Transitions import com.android.wm.shell.transition.Transitions.TransitionHandler @@ -47,6 +49,7 @@ class SystemModalsTransitionHandler( private val shellInit: ShellInit, private val transitions: Transitions, private val desktopUserRepositories: DesktopUserRepositories, + private val desktopModeCompatPolicy: DesktopModeCompatPolicy, ) : TransitionHandler { private val showingSystemModalsIds = mutableSetOf<Int>() @@ -128,7 +131,7 @@ class SystemModalsTransitionHandler( return@find false } val taskInfo = change.taskInfo ?: return@find false - return@find isTopActivityExemptFromDesktopWindowing(context, taskInfo) + return@find isSystemModal(taskInfo) } private fun getClosingSystemModal(info: TransitionInfo): TransitionInfo.Change? = @@ -137,10 +140,13 @@ class SystemModalsTransitionHandler( return@find false } val taskInfo = change.taskInfo ?: return@find false - return@find isTopActivityExemptFromDesktopWindowing(context, taskInfo) || - showingSystemModalsIds.contains(taskInfo.taskId) + return@find isSystemModal(taskInfo) || showingSystemModalsIds.contains(taskInfo.taskId) } + private fun isSystemModal(taskInfo: RunningTaskInfo): Boolean = + !DesktopWallpaperActivity.isWallpaperTask(taskInfo) && + desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(taskInfo) + private fun createAlphaAnimator( transaction: SurfaceControl.Transaction, leash: SurfaceControl, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationController.kt index 45d1281ba0e0..b614b3f4d025 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationController.kt @@ -22,7 +22,6 @@ import android.content.Context import android.content.res.Resources import android.graphics.Point import android.os.SystemProperties -import android.util.Slog import com.android.window.flags.Flags import com.android.wm.shell.R import com.android.wm.shell.desktopmode.CaptionState @@ -32,28 +31,17 @@ import com.android.wm.shell.shared.annotations.ShellBackgroundThread import com.android.wm.shell.shared.annotations.ShellMainThread import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.canEnterDesktopMode import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource -import com.android.wm.shell.windowdecor.common.DecorThemeUtil -import com.android.wm.shell.windowdecor.common.Theme import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationTooltipController import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationTooltipController.TooltipColorScheme import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationTooltipController.TooltipEducationViewConfig -import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.MainCoroutineDispatcher -import kotlinx.coroutines.TimeoutCancellationException -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.debounce -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOn -import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.take -import kotlinx.coroutines.flow.timeout import kotlinx.coroutines.launch /** @@ -73,59 +61,75 @@ class AppHandleEducationController( @ShellMainThread private val applicationCoroutineScope: CoroutineScope, @ShellBackgroundThread private val backgroundDispatcher: MainCoroutineDispatcher, ) { - private val decorThemeUtil = DecorThemeUtil(context) private lateinit var openHandleMenuCallback: (Int) -> Unit private lateinit var toDesktopModeCallback: (Int, DesktopModeTransitionSource) -> Unit + private val onTertiaryFixedColor = + context.getColor(com.android.internal.R.color.materialColorOnTertiaryFixed) + private val tertiaryFixedColor = + context.getColor(com.android.internal.R.color.materialColorTertiaryFixed) init { runIfEducationFeatureEnabled { + // Coroutine block for the first hint that appears on a full-screen app's app handle to + // encourage users to open the app handle menu. applicationCoroutineScope.launch { - // Central block handling the app handle's educational flow end-to-end. - isAppHandleHintViewedFlow() - .flatMapLatest { isAppHandleHintViewed -> - if (isAppHandleHintViewed) { - // If the education is viewed then return emptyFlow() that completes - // immediately. - // This will help us to not listen to [captionHandleStateFlow] after the - // education - // has been viewed already. - emptyFlow() - } else { - // Listen for changes to window decor's caption handle. - windowDecorCaptionHandleRepository.captionStateFlow - // Wait for few seconds before emitting the latest state. - .debounce(APP_HANDLE_EDUCATION_DELAY_MILLIS) - .filter { captionState -> - captionState is CaptionState.AppHandle && - appHandleEducationFilter.shouldShowAppHandleEducation( - captionState - ) - } - } + if (isAppHandleHintViewed()) return@launch + windowDecorCaptionHandleRepository.captionStateFlow + .debounce(APP_HANDLE_EDUCATION_DELAY_MILLIS) + .filter { captionState -> + captionState is CaptionState.AppHandle && + !captionState.isHandleMenuExpanded && + !isAppHandleHintViewed() && + appHandleEducationFilter.shouldShowDesktopModeEducation(captionState) } + .take(1) .flowOn(backgroundDispatcher) .collectLatest { captionState -> - val tooltipColorScheme = tooltipColorScheme(captionState) - - showEducation(captionState, tooltipColorScheme) - // After showing first tooltip, mark education as viewed + showEducation(captionState) appHandleEducationDatastoreRepository .updateAppHandleHintViewedTimestampMillis(true) } } + // Coroutine block for the hint that appears when an app handle is expanded to + // encourage users to enter desktop mode. applicationCoroutineScope.launch { - if (isAppHandleHintUsed()) return@launch + if (isEnterDesktopModeHintViewed()) return@launch windowDecorCaptionHandleRepository.captionStateFlow + .debounce(ENTER_DESKTOP_MODE_EDUCATION_DELAY_MILLIS) .filter { captionState -> - captionState is CaptionState.AppHandle && captionState.isHandleMenuExpanded + captionState is CaptionState.AppHandle && + captionState.isHandleMenuExpanded && + !isEnterDesktopModeHintViewed() && + appHandleEducationFilter.shouldShowDesktopModeEducation(captionState) } .take(1) .flowOn(backgroundDispatcher) - .collect { - // If user expands app handle, mark user has used the app handle hint + .collectLatest { captionState -> + showWindowingImageButtonTooltip(captionState as CaptionState.AppHandle) appHandleEducationDatastoreRepository - .updateAppHandleHintUsedTimestampMillis(true) + .updateEnterDesktopModeHintViewedTimestampMillis(true) + } + } + + // Coroutine block for the hint that appears on the window app header in freeform mode + // to let users know how to exit desktop mode. + applicationCoroutineScope.launch { + if (isExitDesktopModeHintViewed()) return@launch + windowDecorCaptionHandleRepository.captionStateFlow + .debounce(APP_HANDLE_EDUCATION_DELAY_MILLIS) + .filter { captionState -> + captionState is CaptionState.AppHeader && + !captionState.isHeaderMenuExpanded && + !isExitDesktopModeHintViewed() && + appHandleEducationFilter.shouldShowDesktopModeEducation(captionState) + } + .take(1) + .flowOn(backgroundDispatcher) + .collectLatest { captionState -> + showExitWindowingTooltip(captionState as CaptionState.AppHeader) + appHandleEducationDatastoreRepository + .updateExitDesktopModeHintViewedTimestampMillis(true) } } } @@ -136,7 +140,7 @@ class AppHandleEducationController( block() } - private fun showEducation(captionState: CaptionState, tooltipColorScheme: TooltipColorScheme) { + private fun showEducation(captionState: CaptionState) { val appHandleBounds = (captionState as CaptionState.AppHandle).globalAppHandleBounds val tooltipGlobalCoordinates = Point(appHandleBounds.left + appHandleBounds.width() / 2, appHandleBounds.bottom) @@ -146,21 +150,21 @@ class AppHandleEducationController( val appHandleTooltipConfig = TooltipEducationViewConfig( tooltipViewLayout = R.layout.desktop_windowing_education_top_arrow_tooltip, - tooltipColorScheme = tooltipColorScheme, + tooltipColorScheme = + TooltipColorScheme( + tertiaryFixedColor, + onTertiaryFixedColor, + onTertiaryFixedColor, + ), tooltipViewGlobalCoordinates = tooltipGlobalCoordinates, tooltipText = getString(R.string.windowing_app_handle_education_tooltip), arrowDirection = DesktopWindowingEducationTooltipController.TooltipArrowDirection.UP, onEducationClickAction = { - launchWithExceptionHandling { - showWindowingImageButtonTooltip(tooltipColorScheme) - } openHandleMenuCallback(captionState.runningTaskInfo.taskId) }, onDismissAction = { - launchWithExceptionHandling { - showWindowingImageButtonTooltip(tooltipColorScheme) - } + // TODO: b/341320146 - Log previous tooltip was dismissed }, ) @@ -171,7 +175,7 @@ class AppHandleEducationController( } /** Show tooltip that points to windowing image button in app handle menu */ - private suspend fun showWindowingImageButtonTooltip(tooltipColorScheme: TooltipColorScheme) { + private suspend fun showWindowingImageButtonTooltip(captionState: CaptionState.AppHandle) { val appInfoPillHeight = getSize(R.dimen.desktop_mode_handle_menu_app_info_pill_height) val windowingOptionPillHeight = getSize(R.dimen.desktop_mode_handle_menu_windowing_pill_height) @@ -182,128 +186,81 @@ class AppHandleEducationController( getSize(R.dimen.desktop_mode_handle_menu_margin_top) + getSize(R.dimen.desktop_mode_handle_menu_pill_spacing_margin) - windowDecorCaptionHandleRepository.captionStateFlow - // After the first tooltip was dismissed, wait for 400 ms and see if the app handle menu - // has been expanded. - .timeout(APP_HANDLE_EDUCATION_TIMEOUT_MILLIS.milliseconds) - .catchTimeoutAndLog { - // TODO: b/341320146 - Log previous tooltip was dismissed - } - // Wait for few milliseconds before emitting the latest state. - .debounce(APP_HANDLE_EDUCATION_DELAY_MILLIS) - .filter { captionState -> - // Filter out states when app handle is not visible or not expanded. - captionState is CaptionState.AppHandle && captionState.isHandleMenuExpanded - } - // Before showing this tooltip, stop listening to further emissions to avoid - // accidentally - // showing the same tooltip on future emissions. - .take(1) - .flowOn(backgroundDispatcher) - .collectLatest { captionState -> - captionState as CaptionState.AppHandle - val appHandleBounds = captionState.globalAppHandleBounds - val tooltipGlobalCoordinates = - Point( - appHandleBounds.left + appHandleBounds.width() / 2 + appHandleMenuWidth / 2, - appHandleBounds.top + - appHandleMenuMargins + - appInfoPillHeight + - windowingOptionPillHeight / 2, - ) - // Populate information important to inflate windowing image button education - // tooltip. - val windowingImageButtonTooltipConfig = - TooltipEducationViewConfig( - tooltipViewLayout = R.layout.desktop_windowing_education_left_arrow_tooltip, - tooltipColorScheme = tooltipColorScheme, - tooltipViewGlobalCoordinates = tooltipGlobalCoordinates, - tooltipText = - getString( - R.string.windowing_desktop_mode_image_button_education_tooltip - ), - arrowDirection = - DesktopWindowingEducationTooltipController.TooltipArrowDirection.LEFT, - onEducationClickAction = { - launchWithExceptionHandling { - showExitWindowingTooltip(tooltipColorScheme) - } - toDesktopModeCallback( - captionState.runningTaskInfo.taskId, - DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON, - ) - }, - onDismissAction = { - launchWithExceptionHandling { - showExitWindowingTooltip(tooltipColorScheme) - } - }, + val appHandleBounds = captionState.globalAppHandleBounds + val tooltipGlobalCoordinates = + Point( + appHandleBounds.left + appHandleBounds.width() / 2 + appHandleMenuWidth / 2, + appHandleBounds.top + + appHandleMenuMargins + + appInfoPillHeight + + windowingOptionPillHeight / 2, + ) + // Populate information important to inflate windowing image button education + // tooltip. + val windowingImageButtonTooltipConfig = + TooltipEducationViewConfig( + tooltipViewLayout = R.layout.desktop_windowing_education_left_arrow_tooltip, + tooltipColorScheme = + TooltipColorScheme( + tertiaryFixedColor, + onTertiaryFixedColor, + onTertiaryFixedColor, + ), + tooltipViewGlobalCoordinates = tooltipGlobalCoordinates, + tooltipText = + getString(R.string.windowing_desktop_mode_image_button_education_tooltip), + arrowDirection = + DesktopWindowingEducationTooltipController.TooltipArrowDirection.LEFT, + onEducationClickAction = { + toDesktopModeCallback( + captionState.runningTaskInfo.taskId, + DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON, ) + }, + onDismissAction = { + // TODO: b/341320146 - Log previous tooltip was dismissed + }, + ) - windowingEducationViewController.showEducationTooltip( - taskId = captionState.runningTaskInfo.taskId, - tooltipViewConfig = windowingImageButtonTooltipConfig, - ) - } + windowingEducationViewController.showEducationTooltip( + taskId = captionState.runningTaskInfo.taskId, + tooltipViewConfig = windowingImageButtonTooltipConfig, + ) } /** Show tooltip that points to app chip button and educates user on how to exit desktop mode */ - private suspend fun showExitWindowingTooltip(tooltipColorScheme: TooltipColorScheme) { - windowDecorCaptionHandleRepository.captionStateFlow - // After the previous tooltip was dismissed, wait for 400 ms and see if the user entered - // desktop mode. - .timeout(APP_HANDLE_EDUCATION_TIMEOUT_MILLIS.milliseconds) - .catchTimeoutAndLog { - // TODO: b/341320146 - Log previous tooltip was dismissed - } - // Wait for few milliseconds before emitting the latest state. - .debounce(APP_HANDLE_EDUCATION_DELAY_MILLIS) - .filter { captionState -> - // Filter out states when app header is not visible or expanded. - captionState is CaptionState.AppHeader && !captionState.isHeaderMenuExpanded - } - // Before showing this tooltip, stop listening to further emissions to avoid - // accidentally - // showing the same tooltip on future emissions. - .take(1) - .flowOn(backgroundDispatcher) - .collectLatest { captionState -> - captionState as CaptionState.AppHeader - val globalAppChipBounds = captionState.globalAppChipBounds - val tooltipGlobalCoordinates = - Point( - globalAppChipBounds.right, - globalAppChipBounds.top + globalAppChipBounds.height() / 2, - ) - // Populate information important to inflate exit desktop mode education tooltip. - val exitWindowingTooltipConfig = - TooltipEducationViewConfig( - tooltipViewLayout = R.layout.desktop_windowing_education_left_arrow_tooltip, - tooltipColorScheme = tooltipColorScheme, - tooltipViewGlobalCoordinates = tooltipGlobalCoordinates, - tooltipText = - getString(R.string.windowing_desktop_mode_exit_education_tooltip), - arrowDirection = - DesktopWindowingEducationTooltipController.TooltipArrowDirection.LEFT, - onDismissAction = {}, - onEducationClickAction = { - openHandleMenuCallback(captionState.runningTaskInfo.taskId) - }, - ) - windowingEducationViewController.showEducationTooltip( - taskId = captionState.runningTaskInfo.taskId, - tooltipViewConfig = exitWindowingTooltipConfig, - ) - } - } - - private fun tooltipColorScheme(captionState: CaptionState): TooltipColorScheme { - val onTertiaryFixed = - context.getColor(com.android.internal.R.color.materialColorOnTertiaryFixed) - val tertiaryFixed = - context.getColor(com.android.internal.R.color.materialColorTertiaryFixed) - - return TooltipColorScheme(tertiaryFixed, onTertiaryFixed, onTertiaryFixed) + private suspend fun showExitWindowingTooltip(captionState: CaptionState.AppHeader) { + val globalAppChipBounds = captionState.globalAppChipBounds + val tooltipGlobalCoordinates = + Point( + globalAppChipBounds.right, + globalAppChipBounds.top + globalAppChipBounds.height() / 2, + ) + // Populate information important to inflate exit desktop mode education tooltip. + val exitWindowingTooltipConfig = + TooltipEducationViewConfig( + tooltipViewLayout = R.layout.desktop_windowing_education_left_arrow_tooltip, + tooltipColorScheme = + TooltipColorScheme( + tertiaryFixedColor, + onTertiaryFixedColor, + onTertiaryFixedColor, + ), + tooltipViewGlobalCoordinates = tooltipGlobalCoordinates, + tooltipText = getString(R.string.windowing_desktop_mode_exit_education_tooltip), + arrowDirection = + DesktopWindowingEducationTooltipController.TooltipArrowDirection.LEFT, + onDismissAction = { + // TODO: b/341320146 - Log previous tooltip was dismissed + }, + onEducationClickAction = { + openHandleMenuCallback(captionState.runningTaskInfo.taskId) + }, + ) + windowingEducationViewController.showEducationTooltip( + taskId = captionState.runningTaskInfo.taskId, + tooltipViewConfig = exitWindowingTooltipConfig, + ) } /** @@ -320,43 +277,20 @@ class AppHandleEducationController( this.toDesktopModeCallback = toDesktopModeCallback } - private inline fun <T> Flow<T>.catchTimeoutAndLog(crossinline block: () -> Unit) = - catch { exception -> - if (exception is TimeoutCancellationException) block() else throw exception - } - - private fun launchWithExceptionHandling(block: suspend () -> Unit) = - applicationCoroutineScope.launch { - try { - block() - } catch (e: Throwable) { - Slog.e(TAG, "Error: ", e) - } - } + private suspend fun isAppHandleHintViewed(): Boolean = + appHandleEducationDatastoreRepository.dataStoreFlow + .first() + .hasAppHandleHintViewedTimestampMillis() && !FORCE_SHOW_DESKTOP_MODE_EDUCATION - /** - * Listens to the changes to [WindowingEducationProto#hasAppHandleHintViewedTimestampMillis()] - * in datastore proto object. - * - * If [SHOULD_OVERRIDE_EDUCATION_CONDITIONS] is true, this flow will always emit false. That - * means it will always emit app handle hint has not been viewed yet. - */ - private fun isAppHandleHintViewedFlow(): Flow<Boolean> = + private suspend fun isEnterDesktopModeHintViewed(): Boolean = appHandleEducationDatastoreRepository.dataStoreFlow - .map { preferences -> - preferences.hasAppHandleHintViewedTimestampMillis() && - !SHOULD_OVERRIDE_EDUCATION_CONDITIONS - } - .distinctUntilChanged() + .first() + .hasEnterDesktopModeHintViewedTimestampMillis() && !FORCE_SHOW_DESKTOP_MODE_EDUCATION - /** - * Listens to the changes to [WindowingEducationProto#hasAppHandleHintUsedTimestampMillis()] in - * datastore proto object. - */ - private suspend fun isAppHandleHintUsed(): Boolean = + private suspend fun isExitDesktopModeHintViewed(): Boolean = appHandleEducationDatastoreRepository.dataStoreFlow .first() - .hasAppHandleHintUsedTimestampMillis() + .hasExitDesktopModeHintViewedTimestampMillis() && !FORCE_SHOW_DESKTOP_MODE_EDUCATION private fun getSize(@DimenRes resourceId: Int): Int { if (resourceId == Resources.ID_NULL) return 0 @@ -370,13 +304,17 @@ class AppHandleEducationController( val APP_HANDLE_EDUCATION_DELAY_MILLIS: Long get() = SystemProperties.getLong("persist.windowing_app_handle_education_delay", 3000L) - val APP_HANDLE_EDUCATION_TIMEOUT_MILLIS: Long - get() = SystemProperties.getLong("persist.windowing_app_handle_education_timeout", 400L) + val ENTER_DESKTOP_MODE_EDUCATION_DELAY_MILLIS: Long + get() = + SystemProperties.getLong( + "persist.windowing_enter_desktop_mode_education_timeout", + 400L, + ) - val SHOULD_OVERRIDE_EDUCATION_CONDITIONS: Boolean + val FORCE_SHOW_DESKTOP_MODE_EDUCATION: Boolean get() = SystemProperties.getBoolean( - "persist.desktop_windowing_app_handle_education_override_conditions", + "persist.windowing_force_show_desktop_mode_education", false, ) } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationFilter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationFilter.kt index 9990846fc92e..4d219b5544aa 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationFilter.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationFilter.kt @@ -17,13 +17,14 @@ package com.android.wm.shell.desktopmode.education import android.annotation.IntegerRes +import android.app.ActivityManager.RunningTaskInfo import android.app.usage.UsageStatsManager import android.content.Context import android.os.SystemClock import android.provider.Settings.Secure import com.android.wm.shell.R import com.android.wm.shell.desktopmode.CaptionState -import com.android.wm.shell.desktopmode.education.AppHandleEducationController.Companion.SHOULD_OVERRIDE_EDUCATION_CONDITIONS +import com.android.wm.shell.desktopmode.education.AppHandleEducationController.Companion.FORCE_SHOW_DESKTOP_MODE_EDUCATION import com.android.wm.shell.desktopmode.education.data.AppHandleEducationDatastoreRepository import com.android.wm.shell.desktopmode.education.data.WindowingEducationProto import java.time.Duration @@ -37,26 +38,28 @@ class AppHandleEducationFilter( private val usageStatsManager = context.getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager + suspend fun shouldShowDesktopModeEducation(captionState: CaptionState.AppHeader): Boolean = + shouldShowDesktopModeEducation(captionState.runningTaskInfo) + + suspend fun shouldShowDesktopModeEducation(captionState: CaptionState.AppHandle): Boolean = + shouldShowDesktopModeEducation(captionState.runningTaskInfo) + /** - * Returns true if conditions to show app handle education are met, returns false otherwise. + * Returns true if conditions to show app handle, enter desktop mode and exit desktop mode + * education are met based on the app info and usage, returns false otherwise. * - * If [SHOULD_OVERRIDE_EDUCATION_CONDITIONS] is true, this method will always return - * ![captionState.isHandleMenuExpanded]. + * If [FORCE_SHOW_DESKTOP_MODE_EDUCATION] is true, this method will always return true. */ - suspend fun shouldShowAppHandleEducation(captionState: CaptionState): Boolean { - if ((captionState as CaptionState.AppHandle).isHandleMenuExpanded) return false - if (SHOULD_OVERRIDE_EDUCATION_CONDITIONS) return true + private suspend fun shouldShowDesktopModeEducation(taskInfo: RunningTaskInfo): Boolean { + if (FORCE_SHOW_DESKTOP_MODE_EDUCATION) return true - val focusAppPackageName = - captionState.runningTaskInfo.topActivityInfo?.packageName ?: return false + val focusAppPackageName = taskInfo.topActivityInfo?.packageName ?: return false val windowingEducationProto = appHandleEducationDatastoreRepository.windowingEducationProto() return isFocusAppInAllowlist(focusAppPackageName) && !isOtherEducationShowing() && hasSufficientTimeSinceSetup() && - !isAppHandleHintViewedBefore(windowingEducationProto) && - !isAppHandleHintUsedBefore(windowingEducationProto) && hasMinAppUsage(windowingEducationProto, focusAppPackageName) } @@ -79,14 +82,6 @@ class AppHandleEducationFilter( R.integer.desktop_windowing_education_required_time_since_setup_seconds ) - private fun isAppHandleHintViewedBefore( - windowingEducationProto: WindowingEducationProto - ): Boolean = windowingEducationProto.hasAppHandleHintViewedTimestampMillis() - - private fun isAppHandleHintUsedBefore( - windowingEducationProto: WindowingEducationProto - ): Boolean = windowingEducationProto.hasAppHandleHintUsedTimestampMillis() - private suspend fun hasMinAppUsage( windowingEducationProto: WindowingEducationProto, focusAppPackageName: String, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppHandleEducationDatastoreRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppHandleEducationDatastoreRepository.kt index 3e120b09a0b6..d061e03b9be5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppHandleEducationDatastoreRepository.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppHandleEducationDatastoreRepository.kt @@ -91,6 +91,40 @@ constructor(private val dataStore: DataStore<WindowingEducationProto>) { } /** + * Updates [WindowingEducationProto.enterDesktopModeHintViewedTimestampMillis_] field in + * datastore with current timestamp if [isViewed] is true, if not then clears the field. + */ + suspend fun updateEnterDesktopModeHintViewedTimestampMillis(isViewed: Boolean) { + dataStore.updateData { preferences -> + if (isViewed) { + preferences + .toBuilder() + .setEnterDesktopModeHintViewedTimestampMillis(System.currentTimeMillis()) + .build() + } else { + preferences.toBuilder().clearEnterDesktopModeHintViewedTimestampMillis().build() + } + } + } + + /** + * Updates [WindowingEducationProto.exitDesktopModeHintViewedTimestampMillis_] field in + * datastore with current timestamp if [isViewed] is true, if not then clears the field. + */ + suspend fun updateExitDesktopModeHintViewedTimestampMillis(isViewed: Boolean) { + dataStore.updateData { preferences -> + if (isViewed) { + preferences + .toBuilder() + .setExitDesktopModeHintViewedTimestampMillis(System.currentTimeMillis()) + .build() + } else { + preferences.toBuilder().clearExitDesktopModeHintViewedTimestampMillis().build() + } + } + } + + /** * Updates [WindowingEducationProto.appHandleHintUsedTimestampMillis_] field in datastore with * current timestamp if [isViewed] is true, if not then clears the field. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksOrganizer.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksOrganizer.kt new file mode 100644 index 000000000000..5cbb59fbf323 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksOrganizer.kt @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.multidesks + +import android.app.ActivityManager +import android.window.TransitionInfo +import android.window.WindowContainerTransaction + +/** An organizer of desk containers in which to host child desktop windows. */ +interface DesksOrganizer { + /** Creates a new desk container in the given display. */ + fun createDesk(displayId: Int, callback: OnCreateCallback) + + /** Activates the given desk, making it visible in its display. */ + fun activateDesk(wct: WindowContainerTransaction, deskId: Int) + + /** Removes the given desk and its desktop windows. */ + fun removeDesk(wct: WindowContainerTransaction, deskId: Int) + + /** Moves the given task to the given desk. */ + fun moveTaskToDesk( + wct: WindowContainerTransaction, + deskId: Int, + task: ActivityManager.RunningTaskInfo, + ) + + /** + * Returns the desk id in which the task in the given change is located at the end of a + * transition, if any. + */ + fun getDeskAtEnd(change: TransitionInfo.Change): Int? + + /** A callback that is invoked when the desk container is created. */ + fun interface OnCreateCallback { + /** Calls back when the [deskId] has been created. */ + fun onCreated(deskId: Int) + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/OnDeskRemovedListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/OnDeskRemovedListener.kt new file mode 100644 index 000000000000..452ddb1ff8fb --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/OnDeskRemovedListener.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.wm.shell.desktopmode.multidesks + +/** A listener for removals of desks. */ +fun interface OnDeskRemovedListener { + /** Called when a desk has been removed from the system. */ + fun onDeskRemoved(lastDisplayId: Int, deskId: Int) +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizer.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizer.kt new file mode 100644 index 000000000000..79c48c5e9594 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizer.kt @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.multidesks + +import android.app.ActivityManager.RunningTaskInfo +import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD +import android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED +import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM +import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED +import android.util.SparseArray +import android.view.SurfaceControl +import android.window.TransitionInfo +import android.window.WindowContainerTransaction +import androidx.core.util.forEach +import com.android.internal.annotations.VisibleForTesting +import com.android.internal.protolog.ProtoLog +import com.android.window.flags.Flags +import com.android.wm.shell.ShellTaskOrganizer +import com.android.wm.shell.desktopmode.multidesks.DesksOrganizer.OnCreateCallback +import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE +import com.android.wm.shell.sysui.ShellCommandHandler +import com.android.wm.shell.sysui.ShellInit +import java.io.PrintWriter + +/** A [DesksOrganizer] that uses root tasks as the container of each desk. */ +class RootTaskDesksOrganizer( + shellInit: ShellInit, + shellCommandHandler: ShellCommandHandler, + private val shellTaskOrganizer: ShellTaskOrganizer, +) : DesksOrganizer, ShellTaskOrganizer.TaskListener { + + private val deskCreateRequests = mutableListOf<CreateRequest>() + @VisibleForTesting val roots = SparseArray<DeskRoot>() + + init { + if (Flags.enableMultipleDesktopsBackend()) { + shellInit.addInitCallback( + { shellCommandHandler.addDumpCallback(this::dump, this) }, + this, + ) + } + } + + override fun createDesk(displayId: Int, callback: OnCreateCallback) { + logV("createDesk in display: %d", displayId) + deskCreateRequests += CreateRequest(displayId, callback) + shellTaskOrganizer.createRootTask( + displayId, + WINDOWING_MODE_FREEFORM, + /* listener = */ this, + /* removeWithTaskOrganizer = */ true, + ) + } + + override fun removeDesk(wct: WindowContainerTransaction, deskId: Int) { + logV("removeDesk %d", deskId) + val desk = checkNotNull(roots[deskId]) { "Root not found for desk: $deskId" } + wct.removeRootTask(desk.taskInfo.token) + } + + override fun activateDesk(wct: WindowContainerTransaction, deskId: Int) { + logV("activateDesk %d", deskId) + val root = checkNotNull(roots[deskId]) { "Root not found for desk: $deskId" } + wct.reorder(root.taskInfo.token, /* onTop= */ true) + wct.setLaunchRoot( + /* container= */ root.taskInfo.token, + /* windowingModes= */ intArrayOf(WINDOWING_MODE_FREEFORM, WINDOWING_MODE_UNDEFINED), + /* activityTypes= */ intArrayOf(ACTIVITY_TYPE_UNDEFINED, ACTIVITY_TYPE_STANDARD), + ) + } + + override fun moveTaskToDesk( + wct: WindowContainerTransaction, + deskId: Int, + task: RunningTaskInfo, + ) { + val root = roots[deskId] ?: error("Root not found for desk: $deskId") + wct.reparent(task.token, root.taskInfo.token, /* onTop= */ true) + } + + override fun getDeskAtEnd(change: TransitionInfo.Change): Int? = + change.taskInfo?.parentTaskId?.takeIf { it in roots } + + override fun onTaskAppeared(taskInfo: RunningTaskInfo, leash: SurfaceControl) { + if (taskInfo.parentTaskId in roots) { + val deskId = taskInfo.parentTaskId + val taskId = taskInfo.taskId + logV("Task #$taskId appeared in desk #$deskId") + addChildToDesk(taskId = taskId, deskId = deskId) + return + } + val deskId = taskInfo.taskId + check(deskId !in roots) { "A root already exists for desk: $deskId" } + val request = + checkNotNull(deskCreateRequests.firstOrNull { it.displayId == taskInfo.displayId }) { + "Task ${taskInfo.taskId} appeared without pending create request" + } + logV("Desk #$deskId appeared") + roots[deskId] = DeskRoot(deskId, taskInfo, leash) + deskCreateRequests.remove(request) + request.onCreateCallback.onCreated(deskId) + } + + override fun onTaskInfoChanged(taskInfo: RunningTaskInfo) { + if (roots.contains(taskInfo.taskId)) { + val deskId = taskInfo.taskId + roots[deskId] = roots[deskId].copy(taskInfo = taskInfo) + } + } + + override fun onTaskVanished(taskInfo: RunningTaskInfo) { + if (roots.contains(taskInfo.taskId)) { + val deskId = taskInfo.taskId + val deskRoot = roots[deskId] + // Use the last saved taskInfo to obtain the displayId. Using the local one here will + // return -1 since the task is not unassociated with a display. + val displayId = deskRoot.taskInfo.displayId + logV("Desk #$deskId vanished from display #$displayId") + roots.remove(deskId) + return + } + // At this point, [parentTaskId] may be unset even if this is a task vanishing from a desk, + // so search through each root to remove this if it's a child. + roots.forEach { deskId, deskRoot -> + if (deskRoot.children.remove(taskInfo.taskId)) { + logV("Task #${taskInfo.taskId} vanished from desk #$deskId") + return + } + } + } + + @VisibleForTesting + data class DeskRoot( + val deskId: Int, + val taskInfo: RunningTaskInfo, + val leash: SurfaceControl, + val children: MutableSet<Int> = mutableSetOf(), + ) + + override fun dump(pw: PrintWriter, prefix: String) { + val innerPrefix = "$prefix " + pw.println("$prefix$TAG") + pw.println("${innerPrefix}Desk Roots:") + roots.forEach { deskId, root -> + pw.println("$innerPrefix #$deskId visible=${root.taskInfo.isVisible}") + pw.println("$innerPrefix children=${root.children}") + } + } + + private fun addChildToDesk(taskId: Int, deskId: Int) { + roots.forEach { _, deskRoot -> + if (deskRoot.deskId == deskId) { + deskRoot.children.add(taskId) + } else { + deskRoot.children.remove(taskId) + } + } + } + + private data class CreateRequest(val displayId: Int, val onCreateCallback: OnCreateCallback) + + private fun logV(msg: String, vararg arguments: Any?) { + ProtoLog.v(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments) + } + + companion object { + private const val TAG = "RootTaskDesksOrganizer" + } +} 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 58a49a035bb6..5a89451ffdbc 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 @@ -18,6 +18,7 @@ package com.android.wm.shell.desktopmode.persistence import android.content.Context import android.window.DesktopModeFlags +import com.android.window.flags.Flags import com.android.wm.shell.desktopmode.DesktopRepository import com.android.wm.shell.desktopmode.DesktopUserRepositories import com.android.wm.shell.shared.annotations.ShellMainThread @@ -54,10 +55,22 @@ class DesktopRepositoryInitializerImpl( DesktopModeStatus.getMaxTaskLimit(context).takeIf { it > 0 } ?: persistentDesktop.zOrderedTasksCount var visibleTasksCount = 0 + repository.addDesk( + displayId = persistentDesktop.displayId, + deskId = + if (Flags.enableMultipleDesktopsBackend()) { + persistentDesktop.desktopId + } else { + // When disabled, desk ids are always the display id. + persistentDesktop.displayId + }, + ) persistentDesktop.zOrderedTasksList // Reverse it so we initialize the repo from bottom to top. .reversed() .mapNotNull { taskId -> persistentDesktop.tasksByTaskIdMap[taskId] } + // TODO: b/362720497 - add tasks to their respective desk when multi-desk + // persistence is implemented. .forEach { task -> if ( task.desktopTaskState == DesktopTaskState.VISIBLE && 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 e24b2c5f0134..e8996bc03eeb 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,6 +31,7 @@ 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 android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.ActivityTaskManager; import android.app.PendingIntent; @@ -125,6 +126,7 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll * drag. */ default boolean onUnhandledDrag(@NonNull PendingIntent launchIntent, + @UserIdInt int userId, @NonNull DragEvent dragEvent, @NonNull Consumer<Boolean> onFinishCallback) { return false; @@ -444,8 +446,10 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll return; } + // TODO(b/391624027): Consider piping through launch intent user if needed later + final int userId = launchIntent.getCreatorUserHandle().getIdentifier(); final boolean handled = notifyListeners( - l -> l.onUnhandledDrag(launchIntent, dragEvent, onFinishCallback)); + l -> l.onUnhandledDrag(launchIntent, userId, dragEvent, onFinishCallback)); if (!handled) { // Nobody handled this, we still have to notify WM onFinishCallback.accept(false); 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 0d5aa0105659..897e2d1601a5 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 @@ -29,6 +29,7 @@ import android.window.DesktopModeFlags; import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.LaunchAdjacentController; +import com.android.wm.shell.desktopmode.DesktopModeLoggerTransitionObserver; import com.android.wm.shell.desktopmode.DesktopRepository; import com.android.wm.shell.desktopmode.DesktopTasksController; import com.android.wm.shell.desktopmode.DesktopUserRepositories; @@ -52,6 +53,7 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener, private final ShellTaskOrganizer mShellTaskOrganizer; private final Optional<DesktopUserRepositories> mDesktopUserRepositories; private final Optional<DesktopTasksController> mDesktopTasksController; + private final DesktopModeLoggerTransitionObserver mDesktopModeLoggerTransitionObserver; private final WindowDecorViewModel mWindowDecorationViewModel; private final LaunchAdjacentController mLaunchAdjacentController; private final Optional<TaskChangeListener> mTaskChangeListener; @@ -64,6 +66,7 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener, ShellTaskOrganizer shellTaskOrganizer, Optional<DesktopUserRepositories> desktopUserRepositories, Optional<DesktopTasksController> desktopTasksController, + DesktopModeLoggerTransitionObserver desktopModeLoggerTransitionObserver, LaunchAdjacentController launchAdjacentController, WindowDecorViewModel windowDecorationViewModel, Optional<TaskChangeListener> taskChangeListener) { @@ -72,6 +75,7 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener, mWindowDecorationViewModel = windowDecorationViewModel; mDesktopUserRepositories = desktopUserRepositories; mDesktopTasksController = desktopTasksController; + mDesktopModeLoggerTransitionObserver = desktopModeLoggerTransitionObserver; mLaunchAdjacentController = launchAdjacentController; mTaskChangeListener = taskChangeListener; if (shellInit != null) { @@ -130,6 +134,9 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener, repository.removeTask(taskInfo.displayId, taskInfo.taskId); } } + // TODO: b/367268649 - This listener shouldn't need to call the transition observer directly + // for logging once the logic in the observer is moved. + mDesktopModeLoggerTransitionObserver.onTaskVanished(taskInfo); mWindowDecorationViewModel.onTaskVanished(taskInfo); updateLaunchAdjacentController(); } @@ -171,7 +178,8 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener, @Override public void onFocusTaskChanged(RunningTaskInfo taskInfo) { - if (taskInfo.getWindowingMode() != WINDOWING_MODE_FREEFORM) { + if (taskInfo.getWindowingMode() != WINDOWING_MODE_FREEFORM + || DesktopModeFlags.ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS.isTrue()) { return; } ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, 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 bec75b3d865c..20b8c5ec45ce 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 @@ -21,6 +21,9 @@ import static android.view.Surface.ROTATION_0; import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_90; +import static com.android.wm.shell.pip.PipTransitionController.ANIM_TYPE_ALPHA; +import static com.android.wm.shell.pip.PipTransitionController.ANIM_TYPE_BOUNDS; + import android.animation.AnimationHandler; import android.animation.Animator; import android.animation.RectEvaluator; @@ -59,16 +62,6 @@ public class PipAnimationController { static final float FRACTION_START = 0f; static final float FRACTION_END = 1f; - public static final int ANIM_TYPE_BOUNDS = 0; - public static final int ANIM_TYPE_ALPHA = 1; - - @IntDef(prefix = { "ANIM_TYPE_" }, value = { - ANIM_TYPE_BOUNDS, - ANIM_TYPE_ALPHA - }) - @Retention(RetentionPolicy.SOURCE) - public @interface AnimationType {} - /** * The alpha type is set for swiping to home. But the swiped task may not enter PiP. And if * another task enters PiP by non-swipe ways, e.g. call API in foreground or switch to 3-button @@ -125,7 +118,7 @@ public class PipAnimationController { }); private PipTransitionAnimator mCurrentAnimator; - @AnimationType + @PipTransitionController.AnimationType private int mOneShotAnimationType = ANIM_TYPE_BOUNDS; private long mLastOneShotAlphaAnimationTime; @@ -157,9 +150,9 @@ public class PipAnimationController { /** * Construct and return an animator that animates from the {@param startBounds} to the * {@param endBounds} with the given {@param direction}. If {@param direction} is type - * {@link ANIM_TYPE_BOUNDS}, then {@param sourceHintRect} will be used to animate - * in a better, more smooth manner. If the original bound was rotated and a reset needs to - * happen, pass in {@param startingAngle}. + * {@link PipTransitionController#ANIM_TYPE_BOUNDS}, then {@param sourceHintRect} will be used + * to animate in a better, more smooth manner. If the original bound was rotated and a reset + * needs to happen, pass in {@param startingAngle}. * * In the case where one wants to start animation during an intermediate animation (for example, * if the user is currently doing a pinch-resize, and upon letting go now PiP needs to animate @@ -244,12 +237,13 @@ public class PipAnimationController { /** * Sets the preferred enter animation type for one time. This is typically used to set the - * animation type to {@link PipAnimationController#ANIM_TYPE_ALPHA}. + * animation type to {@link PipTransitionController#ANIM_TYPE_ALPHA}. * <p> * For example, gesture navigation would first fade out the PiP activity, and the transition * should be responsible to animate in (such as fade in) the PiP. */ - public void setOneShotEnterAnimationType(@AnimationType int animationType) { + public void setOneShotEnterAnimationType( + @PipTransitionController.AnimationType int animationType) { mOneShotAnimationType = animationType; if (animationType == ANIM_TYPE_ALPHA) { mLastOneShotAlphaAnimationTime = SystemClock.uptimeMillis(); @@ -257,7 +251,7 @@ public class PipAnimationController { } /** Returns the preferred animation type and consumes the one-shot type if needed. */ - @AnimationType + @PipTransitionController.AnimationType public int takeOneShotEnterAnimationType() { final int type = mOneShotAnimationType; if (type == ANIM_TYPE_ALPHA) { @@ -321,7 +315,7 @@ public class PipAnimationController { ValueAnimator.AnimatorListener { private final TaskInfo mTaskInfo; private final SurfaceControl mLeash; - private final @AnimationType int mAnimationType; + private final @PipTransitionController.AnimationType int mAnimationType; private final Rect mDestinationBounds = new Rect(); private final Point mLeashOffset = new Point(); @@ -341,16 +335,17 @@ public class PipAnimationController { private boolean mHasRequestedEnd; private PipTransitionAnimator(@NonNull TaskInfo taskInfo, @NonNull SurfaceControl leash, - @AnimationType int animationType, @NonNull Rect destinationBounds, - @NonNull T baseValue, @NonNull T startValue, @NonNull T endValue) { + @PipTransitionController.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) { + @PipTransitionController.AnimationType int animationType, + @NonNull Rect destinationBounds, @NonNull Point leashOffset, + @NonNull T baseValue, @NonNull T startValue, @NonNull T endValue) { mTaskInfo = taskInfo; mLeash = leash; mAnimationType = animationType; @@ -408,7 +403,8 @@ public class PipAnimationController { @Override public void onAnimationRepeat(Animator animation) {} @VisibleForTesting - @AnimationType public int getAnimationType() { + @PipTransitionController.AnimationType + public int getAnimationType() { return mAnimationType; } 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 af187682d379..61a193c7d523 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 @@ -28,8 +28,6 @@ import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_PIP; import static com.android.wm.shell.ShellTaskOrganizer.taskListenerTypeToString; import static com.android.wm.shell.desktopmode.DesktopModeUtils.calculateInitialBounds; import static com.android.wm.shell.desktopmode.DesktopTasksController.DESKTOP_MODE_INITIAL_BOUNDS_SCALE; -import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA; -import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_BOUNDS; import static com.android.wm.shell.pip.PipAnimationController.FRACTION_START; import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_EXPAND_OR_UNEXPAND; import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_LEAVE_PIP; @@ -43,6 +41,8 @@ import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTI import static com.android.wm.shell.pip.PipAnimationController.isInPipDirection; import static com.android.wm.shell.pip.PipAnimationController.isOutPipDirection; import static com.android.wm.shell.pip.PipAnimationController.isRemovePipDirection; +import static com.android.wm.shell.pip.PipTransitionController.ANIM_TYPE_ALPHA; +import static com.android.wm.shell.pip.PipTransitionController.ANIM_TYPE_BOUNDS; import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS; @@ -1716,7 +1716,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, private void finishResize(SurfaceControl.Transaction tx, Rect destinationBounds, @PipAnimationController.TransitionDirection int direction, - @PipAnimationController.AnimationType int type) { + @PipTransitionController.AnimationType int type) { final Rect preResizeBounds = new Rect(mPipBoundsState.getBounds()); mPipBoundsState.setBounds(destinationBounds); if (direction == TRANSITION_DIRECTION_REMOVE_STACK) { 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 bba778d9f438..2f3c15208621 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 @@ -32,8 +32,6 @@ import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; import static android.window.TransitionInfo.FLAG_IS_DISPLAY; -import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA; -import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_BOUNDS; import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_LEAVE_PIP; import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_SAME; import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP; @@ -116,7 +114,7 @@ public class PipTransition extends PipTransitionController { private final HomeTransitionObserver mHomeTransitionObserver; private final Optional<SplitScreenController> mSplitScreenOptional; private final PipAnimationController mPipAnimationController; - private @PipAnimationController.AnimationType int mEnterAnimationType = ANIM_TYPE_BOUNDS; + private @AnimationType int mEnterAnimationType = ANIM_TYPE_BOUNDS; private Transitions.TransitionFinishCallback mFinishCallback; private SurfaceControl.Transaction mFinishTransaction; private final Rect mExitDestinationBounds = new Rect(); @@ -992,7 +990,7 @@ public class PipTransition extends PipTransitionController { } @Override - public void setEnterAnimationType(@PipAnimationController.AnimationType int type) { + public void setEnterAnimationType(@AnimationType int type) { mEnterAnimationType = type; } 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 91150b0070ce..da3181096d98 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 @@ -23,6 +23,7 @@ import static android.view.WindowManager.TRANSIT_PIP; import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_REMOVE_STACK; import static com.android.wm.shell.pip.PipAnimationController.isInPipDirection; +import android.annotation.IntDef; import android.annotation.Nullable; import android.app.ActivityTaskManager; import android.app.Flags; @@ -55,6 +56,8 @@ import com.android.wm.shell.transition.DefaultMixedHandler; import com.android.wm.shell.transition.Transitions; import java.io.PrintWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.HashMap; import java.util.Map; import java.util.concurrent.Executor; @@ -73,6 +76,17 @@ public abstract class PipTransitionController implements Transitions.TransitionH protected PipTaskOrganizer mPipOrganizer; protected DefaultMixedHandler mMixedHandler; + public static final int ANIM_TYPE_BOUNDS = 0; + public static final int ANIM_TYPE_ALPHA = 1; + + @IntDef(prefix = { "ANIM_TYPE_" }, value = { + ANIM_TYPE_BOUNDS, + ANIM_TYPE_ALPHA + }) + @Retention(RetentionPolicy.SOURCE) + public @interface AnimationType {} + + protected final PipAnimationController.PipAnimationCallback mPipAnimationCallback = new PipAnimationController.PipAnimationCallback() { @Override @@ -357,7 +371,7 @@ public abstract class PipTransitionController implements Transitions.TransitionH } /** Sets the type of animation when a PiP task appears. */ - public void setEnterAnimationType(@PipAnimationController.AnimationType int type) { + public void setEnterAnimationType(@AnimationType int type) { } /** Play a transition animation for entering PiP on a specific PiP change. */ 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 912d3839fae7..9cf822b32a74 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 @@ -22,7 +22,6 @@ import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE; import static android.view.WindowManager.INPUT_CONSUMER_PIP; import static com.android.internal.jank.InteractionJankMonitor.CUJ_PIP_TRANSITION; -import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA; import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_EXPAND_OR_UNEXPAND; import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_LEAVE_PIP; import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN; @@ -32,6 +31,7 @@ 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.pip.PipTransitionController.ANIM_TYPE_ALPHA; import android.app.ActivityManager; import android.app.ActivityTaskManager; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java index 44900ce1db8a..65099c2dfb9d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java @@ -38,6 +38,7 @@ import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SystemWindows; import com.android.wm.shell.common.pip.PipBoundsState; +import com.android.wm.shell.common.pip.PipDisplayLayoutState; import com.android.wm.shell.common.pip.PipMediaController; import com.android.wm.shell.common.pip.PipMediaController.ActionListener; import com.android.wm.shell.common.pip.PipMenuController; @@ -121,6 +122,9 @@ public class PhonePipMenuController implements PipMenuController, @NonNull private final PipTransitionState mPipTransitionState; + @NonNull + private final PipDisplayLayoutState mPipDisplayLayoutState; + private SurfaceControl mLeash; private ActionListener mMediaActionListener = new ActionListener() { @@ -134,7 +138,8 @@ public class PhonePipMenuController implements PipMenuController, public PhonePipMenuController(Context context, PipBoundsState pipBoundsState, PipMediaController mediaController, SystemWindows systemWindows, PipUiEventLogger pipUiEventLogger, PipTaskListener pipTaskListener, - @NonNull PipTransitionState pipTransitionState, ShellExecutor mainExecutor, + @NonNull PipTransitionState pipTransitionState, + @NonNull PipDisplayLayoutState pipDisplayLayoutState, ShellExecutor mainExecutor, Handler mainHandler) { mContext = context; mPipBoundsState = pipBoundsState; @@ -142,6 +147,7 @@ public class PhonePipMenuController implements PipMenuController, mSystemWindows = systemWindows; mPipTaskListener = pipTaskListener; mPipTransitionState = pipTransitionState; + mPipDisplayLayoutState = pipDisplayLayoutState; mMainExecutor = mainExecutor; mMainHandler = mainHandler; mPipUiEventLogger = pipUiEventLogger; @@ -218,7 +224,7 @@ public class PhonePipMenuController implements PipMenuController, mSystemWindows.addView(mPipMenuView, getPipMenuLayoutParams(mContext, MENU_WINDOW_TITLE, 0 /* width */, 0 /* height */), - 0, SHELL_ROOT_LAYER_PIP); + mPipDisplayLayoutState.getDisplayId(), SHELL_ROOT_LAYER_PIP); setShellRootAccessibilityWindow(); // Make sure the initial actions are set 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 b1984ccef4cb..99c9302edb75 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 @@ -19,6 +19,7 @@ package com.android.wm.shell.pip2.phone; 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 android.view.Display.DEFAULT_DISPLAY; import android.annotation.NonNull; import android.app.ActivityManager; @@ -219,6 +220,7 @@ public class PipController implements ConfigurationChangeListener, mPipDisplayLayoutState.setDisplayLayout(layout); mDisplayController.addDisplayChangingController(this); + mDisplayController.addDisplayWindowListener(this); mDisplayInsetsController.addInsetsChangedListener(mPipDisplayLayoutState.getDisplayId(), new ImeListener(mDisplayController, mPipDisplayLayoutState.getDisplayId()) { @Override @@ -297,6 +299,22 @@ public class PipController implements ConfigurationChangeListener, setDisplayLayout(mDisplayController.getDisplayLayout(displayId)); } + @Override + public void onDisplayRemoved(int displayId) { + // If PiP was active on an external display that is removed, clean up states and set + // {@link PipDisplayLayoutState} to DEFAULT_DISPLAY. + if (Flags.enableConnectedDisplaysPip() && mPipTransitionState.isInPip() + && displayId == mPipDisplayLayoutState.getDisplayId() + && displayId != DEFAULT_DISPLAY) { + mPipTransitionState.setState(PipTransitionState.EXITING_PIP); + mPipTransitionState.setState(PipTransitionState.EXITED_PIP); + + mPipDisplayLayoutState.setDisplayId(DEFAULT_DISPLAY); + mPipDisplayLayoutState.setDisplayLayout( + mDisplayController.getDisplayLayout(DEFAULT_DISPLAY)); + } + } + /** * A callback for any observed transition that contains a display change in its * {@link android.window.TransitionRequestInfo}, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipDismissTargetHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipDismissTargetHandler.java index b3070f29c6e2..71697596afd3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipDismissTargetHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipDismissTargetHandler.java @@ -23,6 +23,7 @@ import android.content.res.Resources; import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.Rect; +import android.view.Display; import android.view.MotionEvent; import android.view.SurfaceControl; import android.view.View; @@ -34,7 +35,9 @@ import androidx.annotation.NonNull; import com.android.wm.shell.R; import com.android.wm.shell.bubbles.DismissViewUtils; +import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.pip.PipDisplayLayoutState; import com.android.wm.shell.common.pip.PipUiEventLogger; import com.android.wm.shell.shared.bubbles.DismissCircleView; import com.android.wm.shell.shared.bubbles.DismissView; @@ -50,6 +53,9 @@ public class PipDismissTargetHandler implements ViewTreeObserver.OnPreDrawListen /* The multiplier to apply scale the target size by when applying the magnetic field radius */ private static final float MAGNETIC_FIELD_RADIUS_MULTIPLIER = 1.25f; + /* The window type to apply to the display */ + private static final int WINDOW_TYPE = WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL; + /** * MagnetizedObject wrapper for PIP. This allows the magnetic target library to locate and move * PIP. @@ -84,16 +90,22 @@ public class PipDismissTargetHandler implements ViewTreeObserver.OnPreDrawListen private final Context mContext; private final PipMotionHelper mMotionHelper; private final PipUiEventLogger mPipUiEventLogger; - private final WindowManager mWindowManager; + private WindowManager mWindowManager; + /** The display id for the display that is associated with mWindowManager. */ + private int mWindowManagerDisplayId = -1; + private final PipDisplayLayoutState mPipDisplayLayoutState; + private final DisplayController mDisplayController; private final ShellExecutor mMainExecutor; public PipDismissTargetHandler(Context context, PipUiEventLogger pipUiEventLogger, - PipMotionHelper motionHelper, ShellExecutor mainExecutor) { + PipMotionHelper motionHelper, PipDisplayLayoutState pipDisplayLayoutState, + DisplayController displayController, ShellExecutor mainExecutor) { mContext = context; mPipUiEventLogger = pipUiEventLogger; mMotionHelper = motionHelper; + mPipDisplayLayoutState = pipDisplayLayoutState; + mDisplayController = displayController; mMainExecutor = mainExecutor; - mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); } void init() { @@ -240,6 +252,8 @@ public class PipDismissTargetHandler implements ViewTreeObserver.OnPreDrawListen /** Adds the magnetic target view to the WindowManager so it's ready to be animated in. */ public void createOrUpdateDismissTarget() { + getWindowManager(); + if (mTargetViewContainer.getParent() == null) { mTargetViewContainer.cancelAnimators(); @@ -262,7 +276,7 @@ public class PipDismissTargetHandler implements ViewTreeObserver.OnPreDrawListen WindowManager.LayoutParams.MATCH_PARENT, height, 0, windowSize.y - height, - WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, + WINDOW_TYPE, WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, @@ -308,4 +322,16 @@ public class PipDismissTargetHandler implements ViewTreeObserver.OnPreDrawListen mWindowManager.removeViewImmediate(mTargetViewContainer); } } + + /** Sets mWindowManager to WindowManager associated with the display where PiP is active on. */ + private void getWindowManager() { + final int pipDisplayId = mPipDisplayLayoutState.getDisplayId(); + if (mWindowManager != null && pipDisplayId == mWindowManagerDisplayId) { + return; + } + mWindowManagerDisplayId = pipDisplayId; + Display display = mDisplayController.getDisplay(mWindowManagerDisplayId); + Context uiContext = mContext.createWindowContext(display, WINDOW_TYPE, null); + mWindowManager = uiContext.getSystemService(WindowManager.class); + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipInputConsumer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipInputConsumer.java index ffda56d89276..0a0ecffbea1f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipInputConsumer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipInputConsumer.java @@ -16,8 +16,6 @@ package com.android.wm.shell.pip2.phone; -import static android.view.Display.DEFAULT_DISPLAY; - import android.os.Binder; import android.os.IBinder; import android.os.Looper; @@ -30,6 +28,7 @@ import android.view.InputEvent; import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.pip.PipDisplayLayoutState; import com.android.wm.shell.protolog.ShellProtoLogGroup; import java.io.PrintWriter; @@ -84,6 +83,7 @@ public class PipInputConsumer { private final IWindowManager mWindowManager; private final IBinder mToken; private final String mName; + private final PipDisplayLayoutState mPipDisplayLayoutState; private final ShellExecutor mMainExecutor; private InputEventReceiver mInputEventReceiver; @@ -94,10 +94,11 @@ public class PipInputConsumer { * @param name the name corresponding to the input consumer that is defined in the system. */ public PipInputConsumer(IWindowManager windowManager, String name, - ShellExecutor mainExecutor) { + PipDisplayLayoutState pipDisplayLayoutState, ShellExecutor mainExecutor) { mWindowManager = windowManager; mToken = new Binder(); mName = name; + mPipDisplayLayoutState = pipDisplayLayoutState; mMainExecutor = mainExecutor; } @@ -138,9 +139,9 @@ public class PipInputConsumer { } final InputChannel inputChannel = new InputChannel(); try { - // TODO(b/113087003): Support Picture-in-picture in multi-display. - mWindowManager.destroyInputConsumer(mToken, DEFAULT_DISPLAY); - mWindowManager.createInputConsumer(mToken, mName, DEFAULT_DISPLAY, inputChannel); + final int displayId = mPipDisplayLayoutState.getDisplayId(); + mWindowManager.destroyInputConsumer(mToken, displayId); + mWindowManager.createInputConsumer(mToken, mName, displayId, inputChannel); } catch (RemoteException e) { ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: Failed to create input consumer, %s", TAG, e); @@ -162,8 +163,7 @@ public class PipInputConsumer { return; } try { - // TODO(b/113087003): Support Picture-in-picture in multi-display. - mWindowManager.destroyInputConsumer(mToken, DEFAULT_DISPLAY); + mWindowManager.destroyInputConsumer(mToken, mPipDisplayLayoutState.getDisplayId()); } catch (RemoteException e) { ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: Failed to destroy input consumer, %s", TAG, e); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java index d98be55f28e1..e4be3f60f86e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java @@ -44,6 +44,7 @@ import com.android.wm.shell.R; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.pip.PipBoundsAlgorithm; import com.android.wm.shell.common.pip.PipBoundsState; +import com.android.wm.shell.common.pip.PipDisplayLayoutState; import com.android.wm.shell.common.pip.PipPerfHintController; import com.android.wm.shell.common.pip.PipPinchResizingAlgorithm; import com.android.wm.shell.common.pip.PipUiEventLogger; @@ -70,9 +71,9 @@ public class PipResizeGestureHandler implements private final PipScheduler mPipScheduler; private final PipTransitionState mPipTransitionState; private final PhonePipMenuController mPhonePipMenuController; + private final PipDisplayLayoutState mPipDisplayLayoutState; private final PipUiEventLogger mPipUiEventLogger; private final PipPinchResizingAlgorithm mPinchResizingAlgorithm; - private final int mDisplayId; private final ShellExecutor mMainExecutor; private final PointF mDownPoint = new PointF(); @@ -120,10 +121,10 @@ public class PipResizeGestureHandler implements PipTransitionState pipTransitionState, PipUiEventLogger pipUiEventLogger, PhonePipMenuController menuActivityController, + PipDisplayLayoutState pipDisplayLayoutState, ShellExecutor mainExecutor, @Nullable PipPerfHintController pipPerfHintController) { mContext = context; - mDisplayId = context.getDisplayId(); mMainExecutor = mainExecutor; mPipPerfHintController = pipPerfHintController; mPipBoundsAlgorithm = pipBoundsAlgorithm; @@ -135,6 +136,7 @@ public class PipResizeGestureHandler implements mPipTransitionState.addPipTransitionStateChangedListener(this); mPhonePipMenuController = menuActivityController; + mPipDisplayLayoutState = pipDisplayLayoutState; mPipUiEventLogger = pipUiEventLogger; mPinchResizingAlgorithm = new PipPinchResizingAlgorithm(); } @@ -197,7 +199,7 @@ public class PipResizeGestureHandler implements if (mIsEnabled) { // Register input event receiver mInputMonitor = mContext.getSystemService(InputManager.class).monitorGestureInput( - "pip-resize", mDisplayId); + "pip-resize", mPipDisplayLayoutState.getDisplayId()); try { mMainExecutor.executeBlocking(() -> { mInputEventReceiver = new PipResizeInputEventReceiver( 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 fc3fbe299605..35cd1a2e681f 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 @@ -54,10 +54,12 @@ import android.view.accessibility.AccessibilityWindowInfo; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.R; +import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.FloatingContentCoordinator; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.pip.PipBoundsAlgorithm; import com.android.wm.shell.common.pip.PipBoundsState; +import com.android.wm.shell.common.pip.PipDisplayLayoutState; import com.android.wm.shell.common.pip.PipDoubleTapHelper; import com.android.wm.shell.common.pip.PipPerfHintController; import com.android.wm.shell.common.pip.PipUiEventLogger; @@ -91,6 +93,7 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha @NonNull private final PipTransitionState mPipTransitionState; @NonNull private final PipScheduler mPipScheduler; @NonNull private final SizeSpecSource mSizeSpecSource; + @NonNull private final PipDisplayLayoutState mPipDisplayLayoutState; private final PipUiEventLogger mPipUiEventLogger; private final PipDismissTargetHandler mPipDismissTargetHandler; private final ShellExecutor mMainExecutor; @@ -183,6 +186,8 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha @NonNull PipTransitionState pipTransitionState, @NonNull PipScheduler pipScheduler, @NonNull SizeSpecSource sizeSpecSource, + @NonNull PipDisplayLayoutState pipDisplayLayoutState, + DisplayController displayController, PipMotionHelper pipMotionHelper, FloatingContentCoordinator floatingContentCoordinator, PipUiEventLogger pipUiEventLogger, @@ -200,6 +205,7 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha mPipTransitionState.addPipTransitionStateChangedListener(this::onPipTransitionStateChanged); mPipScheduler = pipScheduler; mSizeSpecSource = sizeSpecSource; + mPipDisplayLayoutState = pipDisplayLayoutState; mMenuController = menuController; mPipUiEventLogger = pipUiEventLogger; mFloatingContentCoordinator = floatingContentCoordinator; @@ -208,7 +214,7 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha mMotionHelper = pipMotionHelper; mPipScheduler.setUpdateMovementBoundsRunnable(this::updateMovementBounds); mPipDismissTargetHandler = new PipDismissTargetHandler(context, pipUiEventLogger, - mMotionHelper, mainExecutor); + mMotionHelper, mPipDisplayLayoutState, displayController, mainExecutor); mTouchState = new PipTouchState(ViewConfiguration.get(context), () -> { mMenuController.showMenuWithPossibleDelay(MENU_STATE_FULL, @@ -220,8 +226,7 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha mainExecutor); mPipResizeGestureHandler = new PipResizeGestureHandler(context, pipBoundsAlgorithm, pipBoundsState, mTouchState, mPipScheduler, mPipTransitionState, pipUiEventLogger, - menuController, mainExecutor, - mPipPerfHintController); + menuController, mPipDisplayLayoutState, mainExecutor, mPipPerfHintController); mPipBoundsState.addOnAspectRatioChangedCallback(aspectRatio -> { updateMinMaxSize(aspectRatio); onAspectRatioChanged(); @@ -264,7 +269,7 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha mPipDismissTargetHandler.init(); mPipInputConsumer = new PipInputConsumer(WindowManagerGlobal.getWindowManagerService(), - INPUT_CONSUMER_PIP, mMainExecutor); + INPUT_CONSUMER_PIP, mPipDisplayLayoutState, mMainExecutor); mPipInputConsumer.setInputListener(this::handleTouchEvent); mPipInputConsumer.setRegistrationListener(this::onRegistrationChanged); 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 8cba076d28f2..03327bf463e3 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 @@ -70,6 +70,7 @@ import com.android.wm.shell.desktopmode.DesktopRepository; import com.android.wm.shell.desktopmode.DesktopUserRepositories; import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider; import com.android.wm.shell.pip.PipTransitionController; +import com.android.wm.shell.pip2.PipSurfaceTransactionHelper; import com.android.wm.shell.pip2.animation.PipAlphaAnimator; import com.android.wm.shell.pip2.animation.PipEnterAnimator; import com.android.wm.shell.pip2.animation.PipExpandAnimator; @@ -115,6 +116,7 @@ public class PipTransition extends PipTransitionController implements private final PipTransitionState mPipTransitionState; private final PipDisplayLayoutState mPipDisplayLayoutState; private final DisplayController mDisplayController; + private final PipSurfaceTransactionHelper mPipSurfaceTransactionHelper; private final Optional<DesktopUserRepositories> mDesktopUserRepositoriesOptional; private final Optional<DesktopWallpaperActivityTokenProvider> mDesktopWallpaperActivityTokenProviderOptional; @@ -141,6 +143,8 @@ public class PipTransition extends PipTransitionController implements private ValueAnimator mTransitionAnimator; + private @AnimationType int mEnterAnimationType = ANIM_TYPE_BOUNDS; + public PipTransition( Context context, @NonNull ShellInit shellInit, @@ -169,6 +173,7 @@ public class PipTransition extends PipTransitionController implements mPipTransitionState.addPipTransitionStateChangedListener(this); mPipDisplayLayoutState = pipDisplayLayoutState; mDisplayController = displayController; + mPipSurfaceTransactionHelper = new PipSurfaceTransactionHelper(mContext); mDesktopUserRepositoriesOptional = desktopUserRepositoriesOptional; mDesktopWallpaperActivityTokenProviderOptional = desktopWallpaperActivityTokenProviderOptional; @@ -341,6 +346,25 @@ public class PipTransition extends PipTransitionController implements } } + @Override + public boolean syncPipSurfaceState(@NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction) { + final TransitionInfo.Change pipChange = getPipChange(info); + if (pipChange == null) return false; + + // add shadow and corner radii + final SurfaceControl leash = pipChange.getLeash(); + final boolean isInPip = mPipTransitionState.isInPip(); + + mPipSurfaceTransactionHelper.round(startTransaction, leash, isInPip) + .shadow(startTransaction, leash, isInPip); + mPipSurfaceTransactionHelper.round(finishTransaction, leash, isInPip) + .shadow(finishTransaction, leash, isInPip); + + return true; + } + // // Animation schedulers and entry points // @@ -812,12 +836,11 @@ public class PipTransition extends PipTransitionController implements if (TransitionUtil.isOpeningType(info.getType())) { for (TransitionInfo.Change change : info.getChanges()) { if (change.getLeash() == null) continue; - if (change.getMode() == TRANSIT_OPEN || change.getMode() == TRANSIT_TO_FRONT) { + if (TransitionUtil.isOpeningMode(change.getMode())) { startTransaction.setAlpha(change.getLeash(), 1f); } } } - } private WindowContainerTransaction getEnterPipTransaction(@NonNull IBinder transition, @@ -901,10 +924,19 @@ public class PipTransition extends PipTransitionController implements private boolean isLegacyEnter(@NonNull TransitionInfo info) { TransitionInfo.Change pipChange = getPipChange(info); - // If the only change in the changes list is a opening type PiP task, - // then this is legacy-enter PiP. - return pipChange != null && info.getChanges().size() == 1 - && (pipChange.getMode() == TRANSIT_TO_FRONT || pipChange.getMode() == TRANSIT_OPEN); + if (pipChange != null) { + if (mEnterAnimationType == ANIM_TYPE_ALPHA) { + // If enter animation type is force overridden to an alpha type, + // treat this as legacy, and reset the animation type to default (i.e. bounds type). + setEnterAnimationType(ANIM_TYPE_BOUNDS); + return true; + } + // If the only change in the changes list is a opening type PiP task, + // then this is legacy-enter PiP. + return info.getChanges().size() == 1 + && TransitionUtil.isOpeningMode(pipChange.getMode()); + } + return false; } private boolean isRemovePipTransition(@NonNull TransitionInfo info) { @@ -951,6 +983,20 @@ public class PipTransition extends PipTransitionController implements initActivityPos.y); } } + + /** + * Sets the type of animation to run upon entering PiP. + * + * By default, {@link PipTransition} uses various signals from Transitions to figure out + * the animation type. But we should provide the ability to override this animation type to + * mixed handlers, for instance, as they can split up and modify incoming {@link TransitionInfo} + * to pass it onto multiple {@link Transitions.TransitionHandler}s. + */ + @Override + public void setEnterAnimationType(@AnimationType int type) { + mEnterAnimationType = type; + } + void cacheAndStartTransitionAnimator(@NonNull ValueAnimator animator) { mTransitionAnimator = animator; mTransitionAnimator.start(); 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 2d4d458292ea..4f2e028a1df0 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 @@ -311,6 +311,9 @@ public class RecentTasksController implements TaskStackListenerCallback, public void onTaskAdded(RunningTaskInfo taskInfo) { notifyRunningTaskAppeared(taskInfo); + if (!enableShellTopTaskTracking()) { + notifyRecentTasksChanged(); + } } public void onTaskRemoved(RunningTaskInfo taskInfo) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java index afc6fee2eca3..55133780f517 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java @@ -222,7 +222,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, RecentsMixedHandler mixer = null; Consumer<IBinder> setTransitionForMixer = null; for (int i = 0; i < mMixers.size(); ++i) { - setTransitionForMixer = mMixers.get(i).handleRecentsRequest(wct); + setTransitionForMixer = mMixers.get(i).handleRecentsRequest(); if (setTransitionForMixer != null) { mixer = mMixers.get(i); break; @@ -1455,6 +1455,11 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, } } + // Notify the mixers of the pending finish + for (int i = 0; i < mMixers.size(); ++i) { + mMixers.get(i).handleFinishRecents(returningToApp, wct, t); + } + if (Flags.enableRecentsBookendTransition()) { if (!wct.isEmpty()) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, @@ -1653,15 +1658,22 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, */ public interface RecentsMixedHandler extends Transitions.TransitionHandler { /** - * Called when a recents request comes in. The handler can add operations to outWCT. If - * the handler wants to "accept" the transition, it should return a Consumer accepting the - * IBinder for the transition. If not, it should return `null`. + * Called when a recents request comes in. If the handler wants to "accept" the transition, + * it should return a Consumer accepting the IBinder for the transition. If not, it should + * return `null`. * * If a mixed-handler accepts this recents, it will be the de-facto handler for this * transition and is required to call the associated {@link #startAnimation}, * {@link #mergeAnimation}, and {@link #onTransitionConsumed} methods. */ @Nullable - Consumer<IBinder> handleRecentsRequest(WindowContainerTransaction outWCT); + Consumer<IBinder> handleRecentsRequest(); + + /** + * Called when a recents transition has finished, with a WCT and SurfaceControl Transaction + * that can be used to add to any changes needed to restore the state. + */ + void handleFinishRecents(boolean returnToApp, @NonNull WindowContainerTransaction finishWct, + @NonNull SurfaceControl.Transaction finishT); } } 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 9e88a260ac44..ae0159263364 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 @@ -300,7 +300,7 @@ public class SplitScreenController implements SplitDragPolicy.Starter, mTaskOrganizer, mDisplayController, mDisplayImeController, mDisplayInsetsController, mTransitions, mTransactionPool, mIconProvider, mMainExecutor, mMainHandler, mRecentTasksOptional, mLaunchAdjacentController, - mWindowDecorViewModel, mSplitState, mDesktopTasksController); + mWindowDecorViewModel, mSplitState, mDesktopTasksController, mRootTDAOrganizer); } @Override @@ -441,7 +441,7 @@ public class SplitScreenController implements SplitDragPolicy.Starter, */ public void prepareExitSplitScreen(WindowContainerTransaction wct, @StageType int stageToTop, @ExitReason int reason) { - mStageCoordinator.prepareExitSplitScreen(stageToTop, wct); + mStageCoordinator.prepareExitSplitScreen(stageToTop, wct, reason); mStageCoordinator.clearSplitPairedInRecents(reason); } @@ -649,11 +649,12 @@ public class SplitScreenController implements SplitDragPolicy.Starter, @Nullable Bundle options, UserHandle user) { if (options == null) options = new Bundle(); final ActivityOptions activityOptions = ActivityOptions.fromBundle(options); + final int userId = user.getIdentifier(); if (samePackage(packageName, getPackageName(reverseSplitPosition(position), null), - user.getIdentifier(), getUserId(reverseSplitPosition(position), null))) { + userId, getUserId(reverseSplitPosition(position), null))) { if (mMultiInstanceHelpher.supportsMultiInstanceSplit( - getShortcutComponent(packageName, shortcutId, user, mLauncherApps))) { + getShortcutComponent(packageName, shortcutId, user, mLauncherApps), userId)) { activityOptions.setApplyMultipleTaskFlagForShortcut(true); ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK"); } else if (isSplitScreenVisible()) { @@ -687,7 +688,8 @@ public class SplitScreenController implements SplitDragPolicy.Starter, final int userId1 = shortcutInfo.getUserId(); final int userId2 = SplitScreenUtils.getUserId(taskId, mTaskOrganizer); if (samePackage(packageName1, packageName2, userId1, userId2)) { - if (mMultiInstanceHelpher.supportsMultiInstanceSplit(shortcutInfo.getActivity())) { + if (mMultiInstanceHelpher.supportsMultiInstanceSplit(shortcutInfo.getActivity(), + userId1)) { activityOptions.setApplyMultipleTaskFlagForShortcut(true); ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK"); } else { @@ -735,7 +737,8 @@ public class SplitScreenController implements SplitDragPolicy.Starter, final int userId2 = SplitScreenUtils.getUserId(taskId, mTaskOrganizer); boolean setSecondIntentMultipleTask = false; if (samePackage(packageName1, packageName2, userId1, userId2)) { - if (mMultiInstanceHelpher.supportsMultiInstanceSplit(getComponent(pendingIntent))) { + if (mMultiInstanceHelpher.supportsMultiInstanceSplit(getComponent(pendingIntent), + userId1)) { setSecondIntentMultipleTask = true; ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK"); } else { @@ -775,7 +778,8 @@ public class SplitScreenController implements SplitDragPolicy.Starter, ? ActivityOptions.fromBundle(options2) : ActivityOptions.makeBasic(); boolean setSecondIntentMultipleTask = false; if (samePackage(packageName1, packageName2, userId1, userId2)) { - if (mMultiInstanceHelpher.supportsMultiInstanceSplit(getComponent(pendingIntent1))) { + if (mMultiInstanceHelpher.supportsMultiInstanceSplit(getComponent(pendingIntent1), + userId1)) { fillInIntent1 = new Intent(); fillInIntent1.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); setSecondIntentMultipleTask = true; @@ -858,7 +862,7 @@ public class SplitScreenController implements SplitDragPolicy.Starter, return; } if (samePackage(packageName1, packageName2, userId1, userId2)) { - if (mMultiInstanceHelpher.supportsMultiInstanceSplit(getComponent(intent))) { + if (mMultiInstanceHelpher.supportsMultiInstanceSplit(getComponent(intent), userId1)) { // Flag with MULTIPLE_TASK if this is launching the same activity into both sides of // the split and there is no reusable background task. fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); 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 37c93518998a..722494c05e32 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 @@ -129,7 +129,9 @@ import com.android.internal.logging.InstanceId; import com.android.internal.policy.FoldLockSettingsObserver; import com.android.internal.protolog.ProtoLog; import com.android.launcher3.icons.IconProvider; +import com.android.wm.shell.Flags; import com.android.wm.shell.R; +import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.ComponentUtils; import com.android.wm.shell.common.DisplayController; @@ -168,6 +170,7 @@ import java.util.HashMap; 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 java.util.concurrent.Executor; @@ -218,6 +221,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, private final SplitscreenEventLogger mLogger; private final ShellExecutor mMainExecutor; private final Handler mMainHandler; + private final RootTaskDisplayAreaOrganizer mRootTDAOrganizer; // Cache live tile tasks while entering recents, evict them from stages in finish transaction // if user is opening another task(s). private final ArrayList<Integer> mPausingTasks = new ArrayList<>(); @@ -354,7 +358,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, Handler mainHandler, Optional<RecentTasksController> recentTasks, LaunchAdjacentController launchAdjacentController, Optional<WindowDecorViewModel> windowDecorViewModel, SplitState splitState, - Optional<DesktopTasksController> desktopTasksController) { + Optional<DesktopTasksController> desktopTasksController, + RootTaskDisplayAreaOrganizer rootTDAOrganizer) { mContext = context; mDisplayId = displayId; mSyncQueue = syncQueue; @@ -367,6 +372,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mWindowDecorViewModel = windowDecorViewModel; mSplitState = splitState; mDesktopTasksController = desktopTasksController; + mRootTDAOrganizer = rootTDAOrganizer; taskOrganizer.createRootTask(displayId, WINDOWING_MODE_FULLSCREEN, this /* listener */); @@ -425,7 +431,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, Handler mainHandler, Optional<RecentTasksController> recentTasks, LaunchAdjacentController launchAdjacentController, Optional<WindowDecorViewModel> windowDecorViewModel, SplitState splitState, - Optional<DesktopTasksController> desktopTasksController) { + Optional<DesktopTasksController> desktopTasksController, + RootTaskDisplayAreaOrganizer rootTDAOrganizer) { mContext = context; mDisplayId = displayId; mSyncQueue = syncQueue; @@ -447,6 +454,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mWindowDecorViewModel = windowDecorViewModel; mSplitState = splitState; mDesktopTasksController = desktopTasksController; + mRootTDAOrganizer = rootTDAOrganizer; mDisplayController.addDisplayWindowListener(this); transitions.addHandler(this); @@ -880,7 +888,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, private void startSingleTask(int taskId, Bundle options, WindowContainerTransaction wct, RemoteTransition remoteTransition) { if (mMainStage.containsTask(taskId) || mSideStage.containsTask(taskId)) { - prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, wct); + prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, wct, EXIT_REASON_FULLSCREEN_REQUEST); } if (mRecentTasks.isPresent()) { mRecentTasks.get().removeSplitPair(taskId); @@ -1433,7 +1441,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // on top and lead to no visible change. clearSplitPairedInRecents(reason); final WindowContainerTransaction wct = new WindowContainerTransaction(); - prepareExitSplitScreen(mLastActiveStage, wct); + prepareExitSplitScreen(mLastActiveStage, wct, reason); mSplitTransitions.startDismissTransition(wct, this, mLastActiveStage, reason); setSplitsVisible(false); mBreakOnNextWake = false; @@ -1527,7 +1535,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (!isSplitActive()) return; final int stage = getStageOfTask(toTopTaskId); final WindowContainerTransaction wct = new WindowContainerTransaction(); - prepareExitSplitScreen(stage, wct); + prepareExitSplitScreen(stage, wct, exitReason); mSplitTransitions.startDismissTransition(wct, this, stage, exitReason); // reset stages to their default sides. setSideStagePosition(SPLIT_POSITION_BOTTOM_OR_RIGHT, null); @@ -1646,7 +1654,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, * to be used when exiting split might be bundled with other window operations. */ void prepareExitSplitScreen(@StageType int stageToTop, - @NonNull WindowContainerTransaction wct) { + @NonNull WindowContainerTransaction wct, @ExitReason int exitReason) { if (!isSplitActive()) return; ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "prepareExitSplitScreen: stageToTop=%s", stageTypeToString(stageToTop)); @@ -1657,6 +1665,26 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } else { mSideStage.removeAllTasks(wct, stageToTop == STAGE_TYPE_SIDE); } + + if (exitReason != EXIT_REASON_DESKTOP_MODE) { + StageTaskListener toTopStage = stageToTop == STAGE_TYPE_MAIN ? mMainStage : mSideStage; + if (enableFlexibleSplit()) { + toTopStage = mStageOrderOperator.getAllStages().stream() + .filter(stage -> stage.getId() == stageToTop) + .findFirst().orElse(null); + } + final DisplayAreaInfo tdaInfo = mRootTDAOrganizer.getDisplayAreaInfo(mDisplayId); + Objects.requireNonNull(tdaInfo); + final int displayWindowingMode = + tdaInfo.configuration.windowConfiguration.getWindowingMode(); + // In freeform-first env, we need to explicitly set the windowing mode when leaving + // the split-screen to be fullscreen. + final int targetWindowingMode = displayWindowingMode == WINDOWING_MODE_FREEFORM + ? WINDOWING_MODE_FULLSCREEN : WINDOWING_MODE_UNDEFINED; + toTopStage.doForAllChildTaskInfos(taskInfo -> { + wct.setWindowingMode(taskInfo.token, targetWindowingMode); + }); + } deactivateSplit(wct, stageToTop); mSplitState.exit(); } @@ -2331,7 +2359,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, stageType = isMainStage ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE; } final WindowContainerTransaction wct = new WindowContainerTransaction(); - prepareExitSplitScreen(stageType, wct); + prepareExitSplitScreen(stageType, wct, EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW); clearSplitPairedInRecents(EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW); mSplitTransitions.startDismissTransition(wct, StageCoordinator.this, stageType, EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW); @@ -2363,10 +2391,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } final WindowContainerTransaction wct = new WindowContainerTransaction(); toTopStage.resetBounds(wct); - prepareExitSplitScreen(dismissTop, wct); + prepareExitSplitScreen(dismissTop, wct, EXIT_REASON_DRAG_DIVIDER); if (mRootTaskInfo != null) { wct.setDoNotPip(mRootTaskInfo.token); } + mSplitTransitions.startDismissTransition(wct, this, dismissTop, EXIT_REASON_DRAG_DIVIDER); } @@ -2801,7 +2830,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // The top should be the opposite side that is closing: int dismissTop = getStageType(stage) == STAGE_TYPE_MAIN ? STAGE_TYPE_SIDE : STAGE_TYPE_MAIN; - prepareExitSplitScreen(dismissTop, out); + prepareExitSplitScreen(dismissTop, out, EXIT_REASON_APP_FINISHED); mSplitTransitions.setDismissTransition(transition, dismissTop, EXIT_REASON_APP_FINISHED); } else if (isOpening && !mPausingTasks.isEmpty()) { @@ -2809,7 +2838,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // recents, which means to dismiss the split pair to this task. int dismissTop = getStageType(stage) == STAGE_TYPE_MAIN ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE; - prepareExitSplitScreen(dismissTop, out); + prepareExitSplitScreen(dismissTop, out, EXIT_REASON_APP_FINISHED); mSplitTransitions.setDismissTransition(transition, dismissTop, EXIT_REASON_APP_FINISHED); } else if (!isSplitScreenVisible() && isOpening) { @@ -2822,7 +2851,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // If the trigger task is in fullscreen and in split, exit split and place // task on top final int stageType = getStageOfTask(triggerTask.taskId); - prepareExitSplitScreen(stageType, out); + prepareExitSplitScreen(stageType, out, EXIT_REASON_FULLSCREEN_REQUEST); mSplitTransitions.setDismissTransition(transition, stageType, EXIT_REASON_FULLSCREEN_REQUEST); } @@ -2850,7 +2879,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (anyStageContainsSingleFullscreenTask) { // A splitting task is opening to fullscreen causes one side of the split empty, // so appends operations to exit split. - prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, out); + prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, out, + EXIT_REASON_FULLSCREEN_REQUEST); } } else if (type == TRANSIT_KEYGUARD_OCCLUDE && triggerTask.topActivity != null && isSplitScreenVisible()) { @@ -2858,7 +2888,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // stage and move it to the top. int top = triggerTask.topActivity.equals(mMainStage.mRootTaskInfo.topActivity) ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE; - prepareExitSplitScreen(top, out); + prepareExitSplitScreen(top, out, EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP); mSplitTransitions.setDismissTransition(transition, top, EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP); } @@ -2940,7 +2970,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, topStage = STAGE_TYPE_SIDE; } } - prepareExitSplitScreen(topStage, outWCT); + prepareExitSplitScreen(topStage, outWCT, EXIT_REASON_UNKNOWN); } } @@ -3127,7 +3157,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // If there is a fullscreen opening change, we should not bring stage to top. prepareExitSplitScreen( !record.mContainShowFullscreenChange && isSplitScreenVisible() - ? dismissTop : STAGE_TYPE_UNDEFINED, wct); + ? dismissTop : STAGE_TYPE_UNDEFINED, wct, EXIT_REASON_APP_FINISHED); mSplitTransitions.startDismissTransition(wct, this, dismissTop, EXIT_REASON_APP_FINISHED); // This can happen in some pathological cases. For example: @@ -3368,7 +3398,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, (sideChild != null ? STAGE_TYPE_SIDE : STAGE_TYPE_UNDEFINED); pendingEnter.cancel( (cancelWct, cancelT) -> { - prepareExitSplitScreen(dismissTop, cancelWct); + prepareExitSplitScreen(dismissTop, cancelWct, EXIT_REASON_UNKNOWN); logExit(EXIT_REASON_UNKNOWN); }); Log.w(TAG, splitFailureMessage("startPendingEnterAnimation", @@ -3737,13 +3767,31 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mTaskOrganizer.applyTransaction(wct); } + public void onRecentsInSplitAnimationFinishing(boolean returnToApp, + @NonNull WindowContainerTransaction finishWct, + @NonNull SurfaceControl.Transaction finishT) { + if (!Flags.enableRecentsBookendTransition()) { + // The non-bookend recents transition case will be handled by + // RecentsMixedTransition wrapping the finish callback and calling + // onRecentsInSplitAnimationFinish() + return; + } + + onRecentsInSplitAnimationFinishInner(returnToApp, finishWct, finishT); + } + /** Call this when the recents animation during split-screen finishes. */ - public void onRecentsInSplitAnimationFinish(WindowContainerTransaction finishWct, - SurfaceControl.Transaction finishT) { - ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onRecentsInSplitAnimationFinish"); - mPausingTasks.clear(); + public void onRecentsInSplitAnimationFinish(@NonNull WindowContainerTransaction finishWct, + @NonNull SurfaceControl.Transaction finishT) { + if (Flags.enableRecentsBookendTransition()) { + // The bookend recents transition case will be handled by + // onRecentsInSplitAnimationFinishing above + return; + } + // Check if the recent transition is finished by returning to the current // split, so we can restore the divider bar. + boolean returnToApp = false; for (int i = 0; i < finishWct.getHierarchyOps().size(); ++i) { final WindowContainerTransaction.HierarchyOp op = finishWct.getHierarchyOps().get(i); @@ -3758,13 +3806,26 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } if (op.getType() == HIERARCHY_OP_TYPE_REORDER && op.getToTop() && anyStageContainsContainer) { - updateSurfaceBounds(mSplitLayout, finishT, - false /* applyResizingOffset */); - finishT.reparent(mSplitLayout.getDividerLeash(), mRootTaskLeash); - setDividerVisibility(true, finishT); - return; + returnToApp = true; } } + onRecentsInSplitAnimationFinishInner(returnToApp, finishWct, finishT); + } + + /** Call this when the recents animation during split-screen finishes. */ + public void onRecentsInSplitAnimationFinishInner(boolean returnToApp, + @NonNull WindowContainerTransaction finishWct, + @NonNull SurfaceControl.Transaction finishT) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onRecentsInSplitAnimationFinish: returnToApp=%b", + returnToApp); + mPausingTasks.clear(); + if (returnToApp) { + updateSurfaceBounds(mSplitLayout, finishT, + false /* applyResizingOffset */); + finishT.reparent(mSplitLayout.getDividerLeash(), mRootTaskLeash); + setDividerVisibility(true, finishT); + return; + } setSplitsVisible(false); finishWct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java index 021f6595d984..194114db0169 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java @@ -379,6 +379,13 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener { } } + void doForAllChildTaskInfos(Consumer<ActivityManager.RunningTaskInfo> consumer) { + for (int i = mChildrenTaskInfo.size() - 1; i >= 0; i--) { + final ActivityManager.RunningTaskInfo taskInfo = mChildrenTaskInfo.valueAt(i); + consumer.accept(taskInfo); + } + } + /** Collects all the current child tasks and prepares transaction to evict them to display. */ void evictAllChildren(WindowContainerTransaction wct) { for (int i = mChildrenTaskInfo.size() - 1; i >= 0; i--) { 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 c5e158c6b452..d71b7a1183f4 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 @@ -62,6 +62,7 @@ public class TvSplitScreenController extends SplitScreenController { private final Optional<RecentTasksController> mRecentTasksOptional; private final LaunchAdjacentController mLaunchAdjacentController; private final SplitState mSplitState; + private final RootTaskDisplayAreaOrganizer mRootTDAOrganizer; private final Handler mMainHandler; private final SystemWindows mSystemWindows; @@ -109,6 +110,7 @@ public class TvSplitScreenController extends SplitScreenController { mMainHandler = mainHandler; mSystemWindows = systemWindows; + mRootTDAOrganizer = rootTDAOrganizer; } /** @@ -121,7 +123,8 @@ public class TvSplitScreenController extends SplitScreenController { mTaskOrganizer, mDisplayController, mDisplayImeController, mDisplayInsetsController, mTransitions, mTransactionPool, mIconProvider, mMainExecutor, mMainHandler, - mRecentTasksOptional, mLaunchAdjacentController, mSplitState, mSystemWindows); + mRecentTasksOptional, mLaunchAdjacentController, mSplitState, mSystemWindows, + mRootTDAOrganizer); } } 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 e1bf12fc6082..d7f1ced1b432 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 @@ -20,6 +20,7 @@ import android.content.Context; import android.os.Handler; import com.android.launcher3.icons.IconProvider; +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.DisplayImeController; @@ -55,11 +56,11 @@ public class TvStageCoordinator extends StageCoordinator Optional<RecentTasksController> recentTasks, LaunchAdjacentController launchAdjacentController, SplitState splitState, - SystemWindows systemWindows) { + SystemWindows systemWindows, RootTaskDisplayAreaOrganizer rootTDAOrganizer) { super(context, displayId, syncQueue, taskOrganizer, displayController, displayImeController, displayInsetsController, transitions, transactionPool, iconProvider, mainExecutor, mainHandler, recentTasks, launchAdjacentController, - Optional.empty(), splitState, Optional.empty()); + Optional.empty(), splitState, Optional.empty(), rootTDAOrganizer); mTvSplitMenuController = new TvSplitMenuController(context, this, systemWindows, mainHandler); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java index 08211ab5df9c..cc962acf1182 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java @@ -370,7 +370,8 @@ class SplashscreenWindowCreator extends AbsSplashWindowCreator { private void removeWindowInner(@NonNull View decorView, boolean hideView) { requestTopUi(false); - if (!decorView.isAttachedToWindow()) { + if (decorView.getParent() == null) { + Slog.w(TAG, "This root view has no parent, never been added to a ViewRootImpl?"); return; } if (hideView) { 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 0445add9cba9..13d87eda085b 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 @@ -92,6 +92,10 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback, getHolder().addCallback(this); } + public TaskViewTaskController getController() { + return mTaskViewTaskController; + } + /** * Launch a new activity. * diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java index d19a7eac6ad2..a0cc2bc8887b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java @@ -417,7 +417,8 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener { } } - void notifyTaskRemovalStarted(@NonNull ActivityManager.RunningTaskInfo taskInfo) { + /** Notifies listeners of a task being removed. */ + public void notifyTaskRemovalStarted(@NonNull ActivityManager.RunningTaskInfo taskInfo) { if (mListener == null) return; final int taskId = taskInfo.taskId; mListenerExecutor.execute(() -> mListener.onTaskRemovalStarted(taskId)); @@ -448,7 +449,7 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener { * have the pending info, we'll do it when we receive it in * {@link #onTaskAppeared(ActivityManager.RunningTaskInfo, SurfaceControl)}. */ - void setTaskNotFound() { + public void setTaskNotFound() { mTaskNotFound = true; if (mPendingInfo != null) { cleanUpPendingTask(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java index 6c90a9060523..1eaae7ec83d9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java @@ -20,6 +20,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE; +import static android.view.WindowManager.TRANSIT_NONE; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; @@ -66,7 +67,7 @@ public class TaskViewTransitions implements Transitions.TransitionHandler, TaskV static final String TAG = "TaskViewTransitions"; /** - * Map of {@link TaskViewTaskController} to {@link TaskViewRequestedState}. + * Map of {@link TaskViewTaskController} to {@link TaskViewRepository.TaskViewState}. * <p> * {@link TaskView} keeps a reference to the {@link TaskViewTaskController} instance and * manages its lifecycle. @@ -95,6 +96,7 @@ public class TaskViewTransitions implements Transitions.TransitionHandler, TaskV final @WindowManager.TransitionType int mType; final WindowContainerTransaction mWct; final @NonNull TaskViewTaskController mTaskView; + ExternalTransition mExternalTransition; IBinder mClaimed; /** @@ -182,6 +184,32 @@ public class TaskViewTransitions implements Transitions.TransitionHandler, TaskV } /** + * Starts or queues an "external" runnable into the pending queue. This means it will run + * in order relative to the local transitions. + * + * The external operation *must* call {@link #onExternalDone} once it has finished. + * + * In practice, the external is usually another transition on a different handler. + */ + public void enqueueExternal(@NonNull TaskViewTaskController taskView, ExternalTransition ext) { + final PendingTransition pending = new PendingTransition( + TRANSIT_NONE, null /* wct */, taskView, null /* cookie */); + pending.mExternalTransition = ext; + mPending.add(pending); + startNextTransition(); + } + + /** + * An external transition run in this "queue" is required to call this once it becomes ready. + */ + public void onExternalDone(IBinder key) { + final PendingTransition pending = findPending(key); + if (pending == null) return; + mPending.remove(pending); + startNextTransition(); + } + + /** * Looks through the pending transitions for a opening transaction that matches the provided * `taskView`. * @@ -191,6 +219,7 @@ public class TaskViewTransitions implements Transitions.TransitionHandler, TaskV PendingTransition findPendingOpeningTransition(TaskViewTaskController taskView) { for (int i = mPending.size() - 1; i >= 0; --i) { if (mPending.get(i).mTaskView != taskView) continue; + if (mPending.get(i).mExternalTransition != null) continue; if (TransitionUtil.isOpeningType(mPending.get(i).mType)) { return mPending.get(i); } @@ -207,6 +236,7 @@ public class TaskViewTransitions implements Transitions.TransitionHandler, TaskV PendingTransition findPending(TaskViewTaskController taskView, int type) { for (int i = mPending.size() - 1; i >= 0; --i) { if (mPending.get(i).mTaskView != taskView) continue; + if (mPending.get(i).mExternalTransition != null) continue; if (mPending.get(i).mType == type) { return mPending.get(i); } @@ -518,7 +548,11 @@ public class TaskViewTransitions implements Transitions.TransitionHandler, TaskV // Wait for this to start animating. return; } - pending.mClaimed = mTransitions.startTransition(pending.mType, pending.mWct, this); + if (pending.mExternalTransition != null) { + pending.mClaimed = pending.mExternalTransition.start(); + } else { + pending.mClaimed = mTransitions.startTransition(pending.mType, pending.mWct, this); + } } @Override @@ -641,7 +675,7 @@ public class TaskViewTransitions implements Transitions.TransitionHandler, TaskV } @VisibleForTesting - void prepareOpenAnimation(TaskViewTaskController taskView, + public void prepareOpenAnimation(TaskViewTaskController taskView, final boolean newTask, SurfaceControl.Transaction startTransaction, SurfaceControl.Transaction finishTransaction, @@ -695,4 +729,10 @@ public class TaskViewTransitions implements Transitions.TransitionHandler, TaskV taskView.notifyAppeared(newTask); } + + /** Interface for running an external transition in this object's pending queue. */ + public interface ExternalTransition { + /** Starts a transition and returns an identifying key for lookup. */ + IBinder start(); + } } 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 2177986bccd5..d8e7c2ccb15f 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 @@ -367,7 +367,7 @@ public class DefaultMixedHandler implements MixedTransitionHandler, } @Override - public Consumer<IBinder> handleRecentsRequest(WindowContainerTransaction outWCT) { + public Consumer<IBinder> handleRecentsRequest() { if (mRecentsHandler != null) { if (mSplitHandler.isSplitScreenVisible()) { return this::setRecentsTransitionDuringSplit; @@ -383,6 +383,21 @@ public class DefaultMixedHandler implements MixedTransitionHandler, return null; } + @Override + public void handleFinishRecents(boolean returnToApp, + @NonNull WindowContainerTransaction finishWct, + @NonNull SurfaceControl.Transaction finishT) { + if (mRecentsHandler != null) { + for (int i = mActiveTransitions.size() - 1; i >= 0; --i) { + final MixedTransition mixed = mActiveTransitions.get(i); + if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) { + ((RecentsMixedTransition) mixed).onAnimateRecentsDuringSplitFinishing( + returnToApp, finishWct, finishT); + } + } + } + } + private void setRecentsTransitionDuringSplit(IBinder transition) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a recents request while " + "Split-Screen is foreground, so treat it as Mixed."); 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 b0547a2a47b1..29a58d7f75dc 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 @@ -407,8 +407,6 @@ class DefaultMixedTransition extends DefaultMixedHandler.MixedTransition { mLeftoversHandler.mergeAnimation( transition, info, t, mergeTarget, finishCallback); } - } else { - mPipHandler.end(); } return; case TYPE_KEYGUARD: 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 36c3e9711f5c..ac6e4c5cd69e 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 @@ -306,10 +306,10 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { } // Early check if the transition doesn't warrant an animation. - if (Transitions.isAllNoAnimation(info) || Transitions.isAllOrderOnly(info) + if (TransitionUtil.isAllNoAnimation(info) || TransitionUtil.isAllOrderOnly(info) || (info.getFlags() & WindowManager.TRANSIT_FLAG_INVISIBLE) != 0) { startTransaction.apply(); - finishTransaction.apply(); + // As a contract, finishTransaction should only be applied in Transitions#onFinish finishCallback.onTransitionFinished(null /* wct */); return true; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java index 2133275cde61..357861cc3ca7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java @@ -21,7 +21,7 @@ import static android.view.WindowManager.TRANSIT_PIP; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; -import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA; +import static com.android.wm.shell.pip.PipTransitionController.ANIM_TYPE_ALPHA; import static com.android.wm.shell.shared.TransitionUtil.isOpeningMode; import static com.android.wm.shell.shared.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR; import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; @@ -148,6 +148,7 @@ public class MixedTransitionHelper { } } + pipHandler.setEnterAnimationType(ANIM_TYPE_ALPHA); if (PipUtils.isPip2ExperimentEnabled()) { TransitionInfo pipInfo = subCopy(info, TRANSIT_PIP, false /* withChanges */); pipInfo.getChanges().add(pipChange); @@ -157,17 +158,13 @@ public class MixedTransitionHelper { pipHandler.startAnimation(mixed.mTransition, pipInfo, startTransaction, finishTransaction, finishCB); } else { - pipHandler.setEnterAnimationType(ANIM_TYPE_ALPHA); pipHandler.startEnterAnimation(pipChange, startTransaction, finishTransaction, finishCB); } - // make a new finishTransaction because pip's startEnterAnimation "consumes" it so - // we need a separate one to send over to launcher. - SurfaceControl.Transaction otherFinishT = new SurfaceControl.Transaction(); // Dispatch the rest of the transition normally. This will most-likely be taken by // recents or default handler. mixed.mLeftoversHandler = player.dispatchTransition(mixed.mTransition, everythingElse, - otherStartT, otherFinishT, finishCB, mixedHandler); + otherStartT, finishTransaction, finishCB, mixedHandler); } else { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Not leaving split, so just " + "forward animation to Pip-Handler."); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java index 8cdbe26a2c76..1847af07f275 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java @@ -159,6 +159,8 @@ class RecentsMixedTransition extends DefaultMixedHandler.MixedTransition { // If pair-to-pair switching, the post-recents clean-up isn't needed. wct = wct != null ? wct : new WindowContainerTransaction(); if (mAnimType != ANIM_TYPE_PAIR_TO_PAIR) { + // TODO(b/346588978): Only called if !enableRecentsBookendTransition(), can remove + // once that rolls out mSplitHandler.onRecentsInSplitAnimationFinish(wct, finishTransaction); } else { // notify pair-to-pair recents animation finish @@ -177,6 +179,17 @@ class RecentsMixedTransition extends DefaultMixedHandler.MixedTransition { return handled; } + /** + * Called when the recents animation during split is about to finish. + */ + void onAnimateRecentsDuringSplitFinishing(boolean returnToApp, + @NonNull WindowContainerTransaction finishWct, + @NonNull SurfaceControl.Transaction finishT) { + if (mAnimType != ANIM_TYPE_PAIR_TO_PAIR) { + mSplitHandler.onRecentsInSplitAnimationFinishing(returnToApp, finishWct, finishT); + } + } + @Override void mergeAnimation( @NonNull IBinder transition, @NonNull TransitionInfo info, 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 8c9407b38d9e..b83b7e2f07a3 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 @@ -672,46 +672,6 @@ public class Transitions implements RemoteCallable<Transitions>, return -1; } - /** - * Look through a transition and see if all non-closing changes are no-animation. If so, no - * animation should play. - */ - static boolean isAllNoAnimation(TransitionInfo info) { - if (isClosingType(info.getType())) { - // no-animation is only relevant for launching (open) activities. - return false; - } - boolean hasNoAnimation = false; - final int changeSize = info.getChanges().size(); - for (int i = changeSize - 1; i >= 0; --i) { - final TransitionInfo.Change change = info.getChanges().get(i); - if (isClosingType(change.getMode())) { - // ignore closing apps since they are a side-effect of the transition and don't - // animate. - continue; - } - if (change.hasFlags(FLAG_NO_ANIMATION)) { - hasNoAnimation = true; - } else if (!TransitionUtil.isOrderOnly(change) && !change.hasFlags(FLAG_IS_OCCLUDED)) { - // Ignore the order only or occluded changes since they shouldn't be visible during - // animation. For anything else, we need to animate if at-least one relevant - // participant *is* animated, - return false; - } - } - return hasNoAnimation; - } - - /** - * Check if all changes in this transition are only ordering changes. If so, we won't animate. - */ - static boolean isAllOrderOnly(TransitionInfo info) { - for (int i = info.getChanges().size() - 1; i >= 0; --i) { - if (!TransitionUtil.isOrderOnly(info.getChanges().get(i))) return false; - } - return true; - } - private Track getOrCreateTrack(int trackId) { while (trackId >= mTracks.size()) { mTracks.add(new Track()); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecorViewModel.java index 7948eadb28f4..2b2cdf84005c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecorViewModel.java @@ -16,13 +16,17 @@ package com.android.wm.shell.windowdecor; import android.app.ActivityManager.RunningTaskInfo; +import android.app.ActivityTaskManager; +import android.app.IActivityTaskManager; import android.content.Context; import android.hardware.input.InputManager; +import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; import android.util.Log; import android.util.SparseArray; import android.view.InputDevice; +import android.view.InsetsState; import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.SurfaceControl; @@ -33,6 +37,7 @@ import android.window.WindowContainerTransaction; import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.freeform.FreeformTaskTransitionStarter; @@ -49,7 +54,8 @@ import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSuppl * Works with decorations that extend {@link CarWindowDecoration}. */ public abstract class CarWindowDecorViewModel - implements WindowDecorViewModel, FocusTransitionListener { + implements WindowDecorViewModel, FocusTransitionListener, + DisplayInsetsController.OnInsetsChangedListener { private static final String TAG = "CarWindowDecorViewModel"; private final ShellTaskOrganizer mTaskOrganizer; @@ -57,31 +63,37 @@ public abstract class CarWindowDecorViewModel private final @ShellBackgroundThread ShellExecutor mBgExecutor; private final ShellExecutor mMainExecutor; private final DisplayController mDisplayController; + private final DisplayInsetsController mDisplayInsetsController; private final FocusTransitionObserver mFocusTransitionObserver; private final SyncTransactionQueue mSyncQueue; private final SparseArray<CarWindowDecoration> mWindowDecorByTaskId = new SparseArray<>(); private final WindowDecorViewHostSupplier<WindowDecorViewHost> mWindowDecorViewHostSupplier; + private final IActivityTaskManager mActivityTaskManager; public CarWindowDecorViewModel( Context context, + @ShellMainThread ShellExecutor mainExecutor, @ShellBackgroundThread ShellExecutor bgExecutor, - @ShellMainThread ShellExecutor shellExecutor, ShellInit shellInit, ShellTaskOrganizer taskOrganizer, DisplayController displayController, + DisplayInsetsController displayInsetsController, SyncTransactionQueue syncQueue, FocusTransitionObserver focusTransitionObserver, WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier) { mContext = context; - mMainExecutor = shellExecutor; + mMainExecutor = mainExecutor; mBgExecutor = bgExecutor; mTaskOrganizer = taskOrganizer; mDisplayController = displayController; + mDisplayInsetsController = displayInsetsController; mFocusTransitionObserver = focusTransitionObserver; mSyncQueue = syncQueue; mWindowDecorViewHostSupplier = windowDecorViewHostSupplier; + mActivityTaskManager = ActivityTaskManager.getService(); shellInit.addInitCallback(this::onInit, this); + displayInsetsController.addGlobalInsetsChangedListener(this); } private void onInit() { @@ -187,6 +199,26 @@ public abstract class CarWindowDecorViewModel decoration.close(); } + @Override + public void insetsChanged(int displayId, InsetsState insetsState) { + final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + try { + mActivityTaskManager.getTasks(/* maxNum= */ Integer.MAX_VALUE, + /* filterOnlyVisibleRecents= */ false, /* keepIntentExtra= */ false, + displayId) + .stream().filter(taskInfo -> taskInfo.isVisible && taskInfo.isRunning) + .forEach(taskInfo -> { + final CarWindowDecoration decoration = mWindowDecorByTaskId.get( + taskInfo.taskId); + if (decoration != null) { + decoration.relayout(taskInfo, t, t); + } + }); + } catch (RemoteException e) { + Log.e(TAG, "Cannot update decoration on inset change on displayId: " + displayId); + } + } + /** * @return {@code true} if the task/activity associated with {@code taskInfo} should show * window decoration. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecoration.java index 39437845301e..3182745d813e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CarWindowDecoration.java @@ -30,7 +30,6 @@ import android.view.WindowInsets; import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; @@ -47,6 +46,7 @@ public class CarWindowDecoration extends WindowDecoration<WindowDecorLinearLayou private WindowDecorLinearLayout mRootView; private @ShellBackgroundThread final ShellExecutor mBgExecutor; private final View.OnClickListener mClickListener; + private final RelayoutParams mRelayoutParams = new RelayoutParams(); private final RelayoutResult<WindowDecorLinearLayout> mResult = new RelayoutResult<>(); CarWindowDecoration( @@ -75,7 +75,8 @@ public class CarWindowDecoration extends WindowDecoration<WindowDecorLinearLayou @SuppressLint("MissingPermission") void relayout(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) { - relayout(taskInfo, startT, finishT, /* isCaptionVisible= */ true); + relayout(taskInfo, startT, finishT, + /* isCaptionVisible= */ mRelayoutParams.mIsCaptionVisible); } @SuppressLint("MissingPermission") @@ -84,12 +85,9 @@ public class CarWindowDecoration extends WindowDecoration<WindowDecorLinearLayou boolean isCaptionVisible) { final WindowContainerTransaction wct = new WindowContainerTransaction(); - RelayoutParams relayoutParams = new RelayoutParams(); + updateRelayoutParams(mRelayoutParams, taskInfo, isCaptionVisible); - updateRelayoutParams(relayoutParams, taskInfo, - mDisplayController.getInsetsState(taskInfo.displayId), isCaptionVisible); - - relayout(relayoutParams, startT, finishT, wct, mRootView, mResult); + relayout(mRelayoutParams, startT, finishT, wct, mRootView, mResult); // After this line, mTaskInfo is up-to-date and should be used instead of taskInfo mBgExecutor.execute(() -> mTaskOrganizer.applyTransaction(wct)); @@ -118,7 +116,6 @@ public class CarWindowDecoration extends WindowDecoration<WindowDecorLinearLayou private void updateRelayoutParams( RelayoutParams relayoutParams, ActivityManager.RunningTaskInfo taskInfo, - @Nullable InsetsState displayInsetsState, boolean isCaptionVisible) { relayoutParams.reset(); relayoutParams.mRunningTaskInfo = taskInfo; @@ -127,16 +124,19 @@ public class CarWindowDecoration extends WindowDecoration<WindowDecorLinearLayou relayoutParams.mCaptionHeightId = R.dimen.freeform_decor_caption_height; relayoutParams.mIsCaptionVisible = isCaptionVisible && mIsStatusBarVisible && !mIsKeyguardVisibleAndOccluded; - if (displayInsetsState != null) { - relayoutParams.mCaptionTopPadding = getTopPadding( - taskInfo.getConfiguration().windowConfiguration.getBounds(), - displayInsetsState); - } + relayoutParams.mCaptionTopPadding = getTopPadding(taskInfo, relayoutParams); + relayoutParams.mInsetSourceFlags |= FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR; relayoutParams.mApplyStartTransactionOnDraw = true; } - private static int getTopPadding(Rect taskBounds, @NonNull InsetsState insetsState) { + private int getTopPadding(ActivityManager.RunningTaskInfo taskInfo, + RelayoutParams relayoutParams) { + Rect taskBounds = taskInfo.getConfiguration().windowConfiguration.getBounds(); + InsetsState insetsState = mDisplayController.getInsetsState(taskInfo.displayId); + if (insetsState == null) { + return relayoutParams.mCaptionTopPadding; + } Insets systemDecor = insetsState.calculateInsets(taskBounds, WindowInsets.Type.systemBars() & ~WindowInsets.Type.captionBar(), false /* ignoreVisibility */); 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 51b0291cab91..2cf574158358 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 @@ -32,7 +32,6 @@ import static android.view.WindowInsets.Type.statusBars; import static com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_MENU; import static com.android.window.flags.Flags.enableDisplayFocusInShellTransitions; -import static com.android.wm.shell.compatui.AppCompatUtils.isTopActivityExemptFromDesktopWindowing; import static com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod; import static com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason; import static com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger; @@ -133,6 +132,7 @@ import com.android.wm.shell.recents.RecentsTransitionStateListener; 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.shared.desktopmode.DesktopModeCompatPolicy; import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource; import com.android.wm.shell.shared.split.SplitScreenConstants.SplitPosition; @@ -254,6 +254,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, private final DesktopModeUiEventLogger mDesktopModeUiEventLogger; private final WindowDecorTaskResourceLoader mTaskResourceLoader; private final RecentsTransitionHandler mRecentsTransitionHandler; + private final DesktopModeCompatPolicy mDesktopModeCompatPolicy; public DesktopModeWindowDecorViewModel( Context context, @@ -290,7 +291,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, DesktopModeEventLogger desktopModeEventLogger, DesktopModeUiEventLogger desktopModeUiEventLogger, WindowDecorTaskResourceLoader taskResourceLoader, - RecentsTransitionHandler recentsTransitionHandler) { + RecentsTransitionHandler recentsTransitionHandler, + DesktopModeCompatPolicy desktopModeCompatPolicy) { this( context, shellExecutor, @@ -332,7 +334,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, desktopModeEventLogger, desktopModeUiEventLogger, taskResourceLoader, - recentsTransitionHandler); + recentsTransitionHandler, + desktopModeCompatPolicy); } @VisibleForTesting @@ -377,7 +380,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, DesktopModeEventLogger desktopModeEventLogger, DesktopModeUiEventLogger desktopModeUiEventLogger, WindowDecorTaskResourceLoader taskResourceLoader, - RecentsTransitionHandler recentsTransitionHandler) { + RecentsTransitionHandler recentsTransitionHandler, + DesktopModeCompatPolicy desktopModeCompatPolicy) { mContext = context; mMainExecutor = shellExecutor; mMainHandler = mainHandler; @@ -447,6 +451,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, mDesktopModeUiEventLogger = desktopModeUiEventLogger; mTaskResourceLoader = taskResourceLoader; mRecentsTransitionHandler = recentsTransitionHandler; + mDesktopModeCompatPolicy = desktopModeCompatPolicy; shellInit.addInitCallback(this::onInit, this); } @@ -751,7 +756,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, // been added, so they must be added here decoration.addCaptionInset(wct); mDesktopTasksController.moveTaskToDesktop(taskId, wct, source, - /* remoteTransition= */ null); + /* remoteTransition= */ null, /* moveToDesktopCallback */ null); decoration.closeHandleMenu(); if (source == DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON) { @@ -1652,8 +1657,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, && mSplitScreenController.isTaskRootOrStageRoot(taskInfo.taskId)) { return false; } - if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue() - && isTopActivityExemptFromDesktopWindowing(mContext, taskInfo)) { + if (mDesktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(taskInfo)) { return false; } if (isPartOfDefaultHomePackage(taskInfo)) { 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 39a989ce7c7f..4e125d001076 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 @@ -423,6 +423,15 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin } @Override + void onExclusionRegionChanged(@NonNull Region exclusionRegion) { + if (Flags.appHandleNoRelayoutOnExclusionChange() && isAppHandle(mWindowDecorViewHolder)) { + // Avoid unnecessary relayouts for app handle. See b/383672263 + return; + } + relayout(mTaskInfo, mHasGlobalFocus, exclusionRegion); + } + + @Override void relayout(ActivityManager.RunningTaskInfo taskInfo, boolean hasGlobalFocus, @NonNull Region displayExclusionRegion) { final SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get(); @@ -533,6 +542,9 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin if (appHeader != null) { appHeader.setAppName(name); appHeader.setAppIcon(icon); + if (canEnterDesktopMode(mContext) && isEducationEnabled()) { + notifyCaptionStateChanged(); + } } }); } @@ -960,7 +972,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin final RelayoutParams.OccludingCaptionElement controlsElement = new RelayoutParams.OccludingCaptionElement(); controlsElement.mWidthResId = R.dimen.desktop_mode_customizable_caption_margin_end; - if (Flags.enableMinimizeButton()) { + if (DesktopModeFlags.ENABLE_MINIMIZE_BUTTON.isTrue()) { controlsElement.mWidthResId = R.dimen.desktop_mode_customizable_caption_with_minimize_button_margin_end; } @@ -1346,7 +1358,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin mWebUri = assistContent == null ? null : AppToWebUtils.getSessionWebUri(assistContent); updateGenericLink(); final boolean supportsMultiInstance = mMultiInstanceHelper - .supportsMultiInstanceSplit(mTaskInfo.baseActivity) + .supportsMultiInstanceSplit(mTaskInfo.baseActivity, mTaskInfo.userId) && Flags.enableDesktopWindowingMultiInstanceFeatures(); final boolean shouldShowManageWindowsButton = supportsMultiInstance && mMinimumInstancesFound; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoader.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoader.kt index d87da092cccf..1bc48f89ea6d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoader.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoader.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.windowdecor.common import android.annotation.DimenRes -import android.app.ActivityManager import android.app.ActivityManager.RunningTaskInfo import android.content.Context import android.content.pm.ActivityInfo @@ -29,6 +28,7 @@ import com.android.launcher3.icons.BaseIconFactory import com.android.launcher3.icons.BaseIconFactory.MODE_DEFAULT import com.android.launcher3.icons.IconProvider import com.android.wm.shell.R +import com.android.wm.shell.common.UserProfileContexts import com.android.wm.shell.shared.annotations.ShellBackgroundThread import com.android.wm.shell.sysui.ShellCommandHandler import com.android.wm.shell.sysui.ShellController @@ -41,10 +41,10 @@ import java.util.concurrent.ConcurrentHashMap * A utility and cache for window decoration UI resources. */ class WindowDecorTaskResourceLoader( - private val context: Context, shellInit: ShellInit, private val shellController: ShellController, private val shellCommandHandler: ShellCommandHandler, + private val userProfilesContexts: UserProfileContexts, private val iconProvider: IconProvider, private val headerIconFactory: BaseIconFactory, private val veilIconFactory: BaseIconFactory, @@ -54,11 +54,12 @@ class WindowDecorTaskResourceLoader( shellInit: ShellInit, shellController: ShellController, shellCommandHandler: ShellCommandHandler, + userProfileContexts: UserProfileContexts, ) : this( - context, shellInit, shellController, shellCommandHandler, + userProfileContexts, IconProvider(context), headerIconFactory = context.createIconFactory(R.dimen.desktop_mode_caption_icon_radius), veilIconFactory = context.createIconFactory(R.dimen.desktop_mode_resize_veil_icon_size), @@ -79,9 +80,6 @@ class WindowDecorTaskResourceLoader( */ private val existingTasks = mutableSetOf<Int>() - @VisibleForTesting - lateinit var currentUserContext: Context - init { shellInit.addInitCallback(this::onInit, this) } @@ -90,14 +88,10 @@ class WindowDecorTaskResourceLoader( shellCommandHandler.addDumpCallback(this::dump, this) shellController.addUserChangeListener(object : UserChangeListener { override fun onUserChanged(newUserId: Int, userContext: Context) { - currentUserContext = userContext // No need to hold on to resources for tasks of another profile. taskToResourceCache.clear() } }) - currentUserContext = context.createContextAsUser( - UserHandle.of(ActivityManager.getCurrentUser()), /* flags= */ 0 - ) } /** Returns the user readable name for this task. */ @@ -158,15 +152,20 @@ class WindowDecorTaskResourceLoader( private fun loadAppResources(taskInfo: RunningTaskInfo): AppResources { Trace.beginSection("$TAG#loadAppResources") - val pm = currentUserContext.packageManager - val activityInfo = getActivityInfo(taskInfo, pm) - val appName = pm.getApplicationLabel(activityInfo.applicationInfo) - val appIconDrawable = iconProvider.getIcon(activityInfo) - val badgedAppIconDrawable = pm.getUserBadgedIcon(appIconDrawable, taskInfo.userHandle()) - val appIcon = headerIconFactory.createIconBitmap(badgedAppIconDrawable, /* scale= */ 1f) - val veilIcon = veilIconFactory.createScaledBitmap(appIconDrawable, MODE_DEFAULT) - Trace.endSection() - return AppResources(appName = appName, appIcon = appIcon, veilIcon = veilIcon) + try { + val pm = checkNotNull(userProfilesContexts[taskInfo.userId]?.packageManager) { + "Could not get context for user ${taskInfo.userId}" + } + val activityInfo = getActivityInfo(taskInfo, pm) + val appName = pm.getApplicationLabel(activityInfo.applicationInfo) + val appIconDrawable = iconProvider.getIcon(activityInfo) + val badgedAppIconDrawable = pm.getUserBadgedIcon(appIconDrawable, taskInfo.userHandle()) + val appIcon = headerIconFactory.createIconBitmap(badgedAppIconDrawable, /* scale= */ 1f) + val veilIcon = veilIconFactory.createScaledBitmap(appIconDrawable, MODE_DEFAULT) + return AppResources(appName = appName, appIcon = appIcon, veilIcon = veilIcon) + } finally { + Trace.endSection() + } } private fun getActivityInfo(taskInfo: RunningTaskInfo, pm: PackageManager): ActivityInfo { 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 d72da3a08de5..8747f63e789f 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 @@ -29,6 +29,7 @@ import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.common.DisplayChangeController import com.android.wm.shell.common.DisplayController +import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.common.SyncTransactionQueue import com.android.wm.shell.desktopmode.DesktopModeEventLogger import com.android.wm.shell.desktopmode.DesktopTasksController @@ -37,6 +38,7 @@ import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator import com.android.wm.shell.desktopmode.ToggleResizeDesktopTaskTransitionHandler import com.android.wm.shell.shared.annotations.ShellBackgroundThread import com.android.wm.shell.shared.annotations.ShellMainThread +import com.android.wm.shell.transition.FocusTransitionObserver import com.android.wm.shell.transition.Transitions import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration import com.android.wm.shell.windowdecor.common.WindowDecorTaskResourceLoader @@ -58,6 +60,8 @@ class DesktopTilingDecorViewModel( private val desktopUserRepositories: DesktopUserRepositories, private val desktopModeEventLogger: DesktopModeEventLogger, private val taskResourceLoader: WindowDecorTaskResourceLoader, + private val focusTransitionObserver: FocusTransitionObserver, + private val mainExecutor: ShellExecutor, ) : DisplayChangeController.OnDisplayChangingListener { @VisibleForTesting var tilingTransitionHandlerByDisplayId = SparseArray<DesktopTilingWindowDecoration>() @@ -94,6 +98,8 @@ class DesktopTilingDecorViewModel( returnToDragStartAnimator, desktopUserRepositories, desktopModeEventLogger, + focusTransitionObserver, + mainExecutor, ) tilingTransitionHandlerByDisplayId.put(displayId, newHandler) newHandler @@ -112,9 +118,10 @@ class DesktopTilingDecorViewModel( } fun moveTaskToFrontIfTiled(taskInfo: RunningTaskInfo): Boolean { + // Always pass focus=true because taskInfo.isFocused is not updated yet. return tilingTransitionHandlerByDisplayId .get(taskInfo.displayId) - ?.moveTiledPairToFront(taskInfo, isTaskFocused = true) ?: false + ?.moveTiledPairToFront(taskInfo.taskId, isFocusedOnDisplay = true) ?: false } fun onOverviewAnimationStateChange(isRunning: Boolean) { 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 6f2323347468..666d4bd046dc 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 @@ -35,11 +35,14 @@ import android.window.TransitionRequestInfo import android.window.WindowContainerTransaction import com.android.internal.annotations.VisibleForTesting import com.android.launcher3.icons.BaseIconFactory +import com.android.window.flags.Flags +import com.android.wm.shell.shared.FocusTransitionListener import com.android.wm.shell.R 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.DisplayLayout +import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.common.SyncTransactionQueue import com.android.wm.shell.desktopmode.DesktopModeEventLogger import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger @@ -49,6 +52,7 @@ import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator import com.android.wm.shell.desktopmode.ToggleResizeDesktopTaskTransitionHandler import com.android.wm.shell.shared.annotations.ShellBackgroundThread import com.android.wm.shell.shared.annotations.ShellMainThread +import com.android.wm.shell.transition.FocusTransitionObserver import com.android.wm.shell.transition.Transitions import com.android.wm.shell.transition.Transitions.TRANSIT_MINIMIZE import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration @@ -78,13 +82,16 @@ class DesktopTilingWindowDecoration( private val returnToDragStartAnimator: ReturnToDragStartAnimator, private val desktopUserRepositories: DesktopUserRepositories, private val desktopModeEventLogger: DesktopModeEventLogger, + private val focusTransitionObserver: FocusTransitionObserver, + @ShellMainThread private val mainExecutor: ShellExecutor, private val transactionSupplier: Supplier<Transaction> = Supplier { Transaction() }, ) : Transitions.TransitionHandler, ShellTaskOrganizer.FocusListener, ShellTaskOrganizer.TaskVanishedListener, DragEventListener, - Transitions.TransitionObserver { + Transitions.TransitionObserver, + FocusTransitionListener { companion object { private val TAG: String = DesktopTilingWindowDecoration::class.java.simpleName private const val TILING_DIVIDER_TAG = "Tiling Divider" @@ -176,8 +183,13 @@ class DesktopTilingWindowDecoration( if (!isTilingManagerInitialised) { desktopTilingDividerWindowManager = initTilingManagerForDisplay(displayId, config) isTilingManagerInitialised = true - shellTaskOrganizer.addFocusListener(this) - isTilingFocused = true + + if (Flags.enableDisplayFocusInShellTransitions()) { + focusTransitionObserver.setLocalFocusTransitionListener(this, mainExecutor) + } else { + shellTaskOrganizer.addFocusListener(this) + isTilingFocused = true + } } leftTaskResizingHelper?.initIfNeeded() rightTaskResizingHelper?.initIfNeeded() @@ -474,23 +486,33 @@ class DesktopTilingWindowDecoration( } } - // Only called if [taskInfo] relates to a focused task - private fun isTilingFocusRemoved(taskInfo: RunningTaskInfo): Boolean { + // Only called if [taskId] relates to a focused task + private fun isTilingFocusRemoved(taskId: Int): Boolean { return isTilingFocused && - taskInfo.taskId != leftTaskResizingHelper?.taskInfo?.taskId && - taskInfo.taskId != rightTaskResizingHelper?.taskInfo?.taskId + taskId != leftTaskResizingHelper?.taskInfo?.taskId && + taskId != rightTaskResizingHelper?.taskInfo?.taskId } + // Overriding ShellTaskOrganizer.FocusListener override fun onFocusTaskChanged(taskInfo: RunningTaskInfo?) { + if (Flags.enableDisplayFocusInShellTransitions()) return if (taskInfo != null) { - moveTiledPairToFront(taskInfo) + moveTiledPairToFront(taskInfo.taskId, taskInfo.isFocused) } } + // Overriding FocusTransitionListener + override fun onFocusedTaskChanged(taskId: Int, + isFocusedOnDisplay: Boolean, + isFocusedGlobally: Boolean) { + if (!Flags.enableDisplayFocusInShellTransitions()) return + moveTiledPairToFront(taskId, isFocusedOnDisplay) + } + // Only called if [taskInfo] relates to a focused task - private fun isTilingRefocused(taskInfo: RunningTaskInfo): Boolean { - return taskInfo.taskId == leftTaskResizingHelper?.taskInfo?.taskId || - taskInfo.taskId == rightTaskResizingHelper?.taskInfo?.taskId + private fun isTilingRefocused(taskId: Int): Boolean { + return taskId == leftTaskResizingHelper?.taskInfo?.taskId || + taskId == rightTaskResizingHelper?.taskInfo?.taskId } private fun buildTiledTasksMoveToFront(leftOnTop: Boolean): WindowContainerTransaction { @@ -582,14 +604,13 @@ class DesktopTilingWindowDecoration( * If specified, [isTaskFocused] will override [RunningTaskInfo.isFocused]. This is to be used * when called when the task will be focused, but the [taskInfo] hasn't been updated yet. */ - fun moveTiledPairToFront(taskInfo: RunningTaskInfo, isTaskFocused: Boolean? = null): Boolean { + fun moveTiledPairToFront(taskId: Int, isFocusedOnDisplay: Boolean): Boolean { if (!isTilingManagerInitialised) return false - val isFocused = isTaskFocused ?: taskInfo.isFocused - if (!isFocused) return false + if (!isFocusedOnDisplay) return false // If a task that isn't tiled is being focused, let the generic handler do the work. - if (isTilingFocusRemoved(taskInfo)) { + if (!Flags.enableDisplayFocusInShellTransitions() && isTilingFocusRemoved(taskId)) { isTilingFocused = false return false } @@ -597,31 +618,29 @@ class DesktopTilingWindowDecoration( val leftTiledTask = leftTaskResizingHelper ?: return false val rightTiledTask = rightTaskResizingHelper ?: return false if (!allTiledTasksVisible()) return false - val isLeftOnTop = taskInfo.taskId == leftTiledTask.taskInfo.taskId - if (isTilingRefocused(taskInfo)) { - val t = transactionSupplier.get() - isTilingFocused = true - if (taskInfo.taskId == leftTaskResizingHelper?.taskInfo?.taskId) { - desktopTilingDividerWindowManager?.onRelativeLeashChanged( - leftTiledTask.getLeash(), - t, - ) - } - if (taskInfo.taskId == rightTaskResizingHelper?.taskInfo?.taskId) { - desktopTilingDividerWindowManager?.onRelativeLeashChanged( - rightTiledTask.getLeash(), - t, - ) - } - transitions.startTransition( - TRANSIT_TO_FRONT, - buildTiledTasksMoveToFront(isLeftOnTop), - null, - ) - t.apply() - return true + val isLeftOnTop = taskId == leftTiledTask.taskInfo.taskId + if (!isTilingRefocused(taskId)) return false + val t = transactionSupplier.get() + if (!Flags.enableDisplayFocusInShellTransitions()) isTilingFocused = true + if (taskId == leftTaskResizingHelper?.taskInfo?.taskId) { + desktopTilingDividerWindowManager?.onRelativeLeashChanged( + leftTiledTask.getLeash(), + t, + ) } - return false + if (taskId == rightTaskResizingHelper?.taskInfo?.taskId) { + desktopTilingDividerWindowManager?.onRelativeLeashChanged( + rightTiledTask.getLeash(), + t, + ) + } + transitions.startTransition( + TRANSIT_TO_FRONT, + buildTiledTasksMoveToFront(isLeftOnTop), + null, + ) + t.apply() + return true } private fun allTiledTasksVisible(): Boolean { @@ -706,7 +725,13 @@ class DesktopTilingWindowDecoration( } private fun tearDownTiling() { - if (isTilingManagerInitialised) shellTaskOrganizer.removeFocusListener(this) + if (isTilingManagerInitialised) { + if (Flags.enableDisplayFocusInShellTransitions()) { + focusTransitionObserver.unsetLocalFocusTransitionListener(this) + } else { + shellTaskOrganizer.removeFocusListener(this) + } + } if (leftTaskResizingHelper == null && rightTaskResizingHelper == null) { shellTaskOrganizer.removeTaskVanishedListener(this) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt index dc4fa3788778..9f8ca7740182 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt @@ -47,7 +47,6 @@ import com.android.internal.R.color.materialColorSurfaceContainerHigh import com.android.internal.R.color.materialColorSurfaceContainerLow import com.android.internal.R.color.materialColorSurfaceDim import com.android.window.flags.Flags -import com.android.window.flags.Flags.enableMinimizeButton import com.android.wm.shell.R import android.window.DesktopModeFlags import com.android.wm.shell.windowdecor.MaximizeButtonView @@ -226,7 +225,7 @@ class AppHeaderViewHolder( minimizeWindowButton.background = getDrawable(1) } maximizeButtonView.setAnimationTints(isDarkMode()) - minimizeWindowButton.isGone = !enableMinimizeButton() + minimizeWindowButton.isGone = !DesktopModeFlags.ENABLE_MINIMIZE_BUTTON.isTrue() } private fun bindDataWithThemedHeaders( @@ -276,7 +275,7 @@ class AppHeaderViewHolder( drawableInsets = minimizeDrawableInsets ) } - minimizeWindowButton.isGone = !enableMinimizeButton() + minimizeWindowButton.isGone = !DesktopModeFlags.ENABLE_MINIMIZE_BUTTON.isTrue() // Maximize button. maximizeButtonView.apply { setAnimationTints( @@ -329,11 +328,6 @@ class AppHeaderViewHolder( } fun runOnAppChipGlobalLayout(runnable: () -> Unit) { - if (openMenuButton.isAttachedToWindow) { - // App chip is already inflated. - runnable() - return - } // Wait for app chip to be inflated before notifying repository. openMenuButton.viewTreeObserver.addOnGlobalLayoutListener(object : OnGlobalLayoutListener { diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/OpenAppFromAllAppsLandscape.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/OpenAppFromAllAppsLandscape.kt new file mode 100644 index 000000000000..6b159a4152ac --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/OpenAppFromAllAppsLandscape.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker + +import android.tools.Rotation.ROTATION_90 +import android.tools.flicker.FlickerConfig +import android.tools.flicker.annotation.ExpectedScenarios +import android.tools.flicker.annotation.FlickerConfigProvider +import android.tools.flicker.config.FlickerConfig +import android.tools.flicker.config.FlickerServiceConfig +import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.CASCADE_APP +import com.android.wm.shell.scenarios.OpenAppFromAllApps +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class OpenAppFromAllAppsLandscape : OpenAppFromAllApps(rotation = ROTATION_90) { + + @ExpectedScenarios(["CASCADE_APP"]) + @Test + override fun openApp() = super.openApp() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT).use(CASCADE_APP) + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/OpenAppFromAllAppsPortrait.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/OpenAppFromAllAppsPortrait.kt new file mode 100644 index 000000000000..07b439284680 --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/OpenAppFromAllAppsPortrait.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker + +import android.tools.Rotation.ROTATION_0 +import android.tools.flicker.FlickerConfig +import android.tools.flicker.annotation.ExpectedScenarios +import android.tools.flicker.annotation.FlickerConfigProvider +import android.tools.flicker.config.FlickerConfig +import android.tools.flicker.config.FlickerServiceConfig +import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.CASCADE_APP +import com.android.wm.shell.scenarios.OpenAppFromAllApps +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class OpenAppFromAllAppsPortrait : OpenAppFromAllApps(rotation = ROTATION_0) { + + @ExpectedScenarios(["CASCADE_APP"]) + @Test + override fun openApp() = super.openApp() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT).use(CASCADE_APP) + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/OpenAppFromTaskbarLandscape.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/OpenAppFromTaskbarLandscape.kt new file mode 100644 index 000000000000..caadd3be0b9c --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/OpenAppFromTaskbarLandscape.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker + +import android.tools.Rotation.ROTATION_90 +import android.tools.flicker.FlickerConfig +import android.tools.flicker.annotation.ExpectedScenarios +import android.tools.flicker.annotation.FlickerConfigProvider +import android.tools.flicker.config.FlickerConfig +import android.tools.flicker.config.FlickerServiceConfig +import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.CASCADE_APP +import com.android.wm.shell.scenarios.OpenAppFromTaskbar +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class OpenAppFromTaskbarLandscape : OpenAppFromTaskbar(rotation = ROTATION_90) { + + @ExpectedScenarios(["CASCADE_APP"]) + @Test + override fun openApp() = super.openApp() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT).use(CASCADE_APP) + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/OpenAppFromTaskbarPortrait.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/OpenAppFromTaskbarPortrait.kt new file mode 100644 index 000000000000..77f5ab290e20 --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/OpenAppFromTaskbarPortrait.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker + +import android.tools.Rotation.ROTATION_0 +import android.tools.flicker.FlickerConfig +import android.tools.flicker.annotation.ExpectedScenarios +import android.tools.flicker.annotation.FlickerConfigProvider +import android.tools.flicker.config.FlickerConfig +import android.tools.flicker.config.FlickerServiceConfig +import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.CASCADE_APP +import com.android.wm.shell.scenarios.OpenAppFromTaskbar +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class OpenAppFromTaskbarPortrait : OpenAppFromTaskbar(rotation = ROTATION_0) { + + @ExpectedScenarios(["CASCADE_APP"]) + @Test + override fun openApp() = super.openApp() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT).use(CASCADE_APP) + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/OpenAppFromAllAppsTest.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/OpenAppFromAllAppsTest.kt new file mode 100644 index 000000000000..61af4a5f82d4 --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/OpenAppFromAllAppsTest.kt @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.functional + +import android.platform.test.annotations.Postsubmit +import com.android.wm.shell.scenarios.OpenAppFromAllApps +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner + +/* Functional test for [OpenAppsInDesktopMode]. */ +@RunWith(BlockJUnit4ClassRunner::class) +@Postsubmit +class OpenAppFromAllAppsTest : OpenAppFromAllApps()
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/OpenAppFromTaskbarTest.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/OpenAppFromTaskbarTest.kt new file mode 100644 index 000000000000..bb5697e6ddfc --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/OpenAppFromTaskbarTest.kt @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.functional + +import android.platform.test.annotations.Postsubmit +import com.android.wm.shell.scenarios.OpenAppFromTaskbar +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner + +/* Functional test for [OpenAppsInDesktopMode]. */ +@RunWith(BlockJUnit4ClassRunner::class) +@Postsubmit +class OpenAppFromTaskbarTest : OpenAppFromTaskbar()
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/UnminimizeAppFromAllAppsTest.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/UnminimizeAppFromAllAppsTest.kt new file mode 100644 index 000000000000..0c681f94d7ef --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/UnminimizeAppFromAllAppsTest.kt @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.functional + +import android.platform.test.annotations.Postsubmit +import com.android.wm.shell.scenarios.UnminimizeAppFromAllApps +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner + +/* Functional test for [OpenAppsInDesktopMode]. */ +@RunWith(BlockJUnit4ClassRunner::class) +@Postsubmit +class UnminimizeAppFromAllAppsTest : UnminimizeAppFromAllApps()
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/UnminimizeAppFromTaskbarTest.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/UnminimizeAppFromTaskbarTest.kt new file mode 100644 index 000000000000..4e65ad092881 --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/UnminimizeAppFromTaskbarTest.kt @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.functional + +import android.platform.test.annotations.Postsubmit +import com.android.wm.shell.scenarios.UnminimizeAppFromTaskbar +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner + +/* Functional test for [OpenAppsInDesktopMode]. */ +@RunWith(BlockJUnit4ClassRunner::class) +@Postsubmit +class UnminimizeAppFromTaskbarTest : UnminimizeAppFromTaskbar()
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindow.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindow.kt index 966aea3088c4..7855698d0151 100644 --- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindow.kt +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindow.kt @@ -21,6 +21,7 @@ import android.tools.NavBar import android.tools.Rotation import android.tools.flicker.rules.ChangeDisplayOrientationRule import android.tools.traces.parsers.WindowManagerStateHelper +import android.window.DesktopModeFlags import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import com.android.launcher3.tapl.LauncherInstrumentation @@ -59,7 +60,7 @@ constructor( fun setup() { Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet) if (usingKeyboard) { - Assume.assumeTrue(Flags.enableTaskResizingKeyboardShortcuts()) + Assume.assumeTrue(DesktopModeFlags.ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS.isTrue) } tapl.setEnableRotation(true) tapl.setExpectedRotation(rotation.value) diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeAppWindows.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeAppWindows.kt index 46c97b0a1397..2f99fbaba078 100644 --- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeAppWindows.kt +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeAppWindows.kt @@ -21,6 +21,7 @@ import android.tools.NavBar import android.tools.Rotation import android.tools.flicker.rules.ChangeDisplayOrientationRule import android.tools.traces.parsers.WindowManagerStateHelper +import android.window.DesktopModeFlags import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import com.android.launcher3.tapl.LauncherInstrumentation @@ -62,7 +63,7 @@ constructor( Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet) Assume.assumeTrue(Flags.enableMinimizeButton()) if (usingKeyboard) { - Assume.assumeTrue(Flags.enableTaskResizingKeyboardShortcuts()) + Assume.assumeTrue(DesktopModeFlags.ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS.isTrue) } tapl.setEnableRotation(true) tapl.setExpectedRotation(rotation.value) diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/OpenAppFromAllApps.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/OpenAppFromAllApps.kt new file mode 100644 index 000000000000..348219631245 --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/OpenAppFromAllApps.kt @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.scenarios + +import android.app.Instrumentation +import android.tools.NavBar +import android.tools.flicker.rules.ChangeDisplayOrientationRule +import android.tools.Rotation +import android.tools.device.apphelpers.CalculatorAppHelper +import android.tools.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.server.wm.flicker.helpers.DesktopModeAppHelper +import com.android.server.wm.flicker.helpers.SimpleAppHelper +import com.android.window.flags.Flags +import com.android.wm.shell.Utils +import org.junit.After +import org.junit.Assume +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Test Base Class") +abstract class OpenAppFromAllApps(val rotation: Rotation = Rotation.ROTATION_0) { + + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation)) + private val calculatorApp = CalculatorAppHelper(instrumentation) + + @Rule + @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation) + + @Before + fun setup() { + Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet) + tapl.setEnableRotation(true) + tapl.setExpectedRotation(rotation.value) + tapl.enableTransientTaskbar(false) + ChangeDisplayOrientationRule.setRotation(rotation) + testApp.enterDesktopMode(wmHelper, device) + tapl.showTaskbarIfHidden() + } + + @Test + open fun openApp() { + tapl.launchedAppState.taskbar + .openAllApps() + .getAppIcon(calculatorApp.appName) + .launch(calculatorApp.packageName) + } + + @After + fun teardown() { + calculatorApp.exit(wmHelper) + testApp.exit(wmHelper) + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/OpenAppFromTaskbar.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/OpenAppFromTaskbar.kt new file mode 100644 index 000000000000..2b456beff58f --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/OpenAppFromTaskbar.kt @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.scenarios + +import android.app.Instrumentation +import android.tools.NavBar +import android.tools.Rotation +import android.tools.device.apphelpers.BrowserAppHelper +import android.tools.flicker.rules.ChangeDisplayOrientationRule +import android.tools.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.server.wm.flicker.helpers.DesktopModeAppHelper +import com.android.server.wm.flicker.helpers.SimpleAppHelper +import com.android.window.flags.Flags +import com.android.wm.shell.Utils +import org.junit.After +import org.junit.Assume +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Test Base Class") +abstract class OpenAppFromTaskbar(val rotation: Rotation = Rotation.ROTATION_0) { + + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation)) + private val browserApp = BrowserAppHelper(instrumentation) + + @Rule + @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation) + + @Before + fun setup() { + Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet) + tapl.setEnableRotation(true) + tapl.setExpectedRotation(rotation.value) + tapl.enableTransientTaskbar(false) + ChangeDisplayOrientationRule.setRotation(rotation) + testApp.enterDesktopMode(wmHelper, device) + tapl.showTaskbarIfHidden() + } + + @Test + open fun openApp() { + tapl.launchedAppState.taskbar + .getAppIcon(browserApp.appName) + .launch(browserApp.packageName) + } + + @After + fun teardown() { + browserApp.exit(wmHelper) + testApp.exit(wmHelper) + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithKeyboardShortcuts.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithKeyboardShortcuts.kt index 068064402ee5..59d15ca4fa6b 100644 --- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithKeyboardShortcuts.kt +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithKeyboardShortcuts.kt @@ -20,6 +20,7 @@ import android.app.Instrumentation import android.tools.NavBar import android.tools.Rotation import android.tools.traces.parsers.WindowManagerStateHelper +import android.window.DesktopModeFlags import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import com.android.launcher3.tapl.LauncherInstrumentation @@ -59,7 +60,7 @@ abstract class SnapResizeAppWindowWithKeyboardShortcuts( @Before fun setup() { Assume.assumeTrue(Flags.enableDesktopWindowingMode() && - Flags.enableTaskResizingKeyboardShortcuts() && tapl.isTablet) + DesktopModeFlags.ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS.isTrue && tapl.isTablet) testApp.enterDesktopMode(wmHelper, device) } diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/UnminimizeAppFromAllApps.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/UnminimizeAppFromAllApps.kt new file mode 100644 index 000000000000..234b984a6cff --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/UnminimizeAppFromAllApps.kt @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.scenarios + +import android.app.Instrumentation +import android.tools.NavBar +import android.tools.Rotation +import android.tools.flicker.rules.ChangeDisplayOrientationRule +import android.tools.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.server.wm.flicker.helpers.DesktopModeAppHelper +import com.android.server.wm.flicker.helpers.MailAppHelper +import com.android.server.wm.flicker.helpers.SimpleAppHelper +import com.android.window.flags.Flags +import com.android.wm.shell.Utils +import org.junit.After +import org.junit.Assume +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Test Base Class") +abstract class UnminimizeAppFromAllApps(val rotation: Rotation = Rotation.ROTATION_0) { + + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation)) + private val mailHelper = MailAppHelper(instrumentation) + private val mailApp = DesktopModeAppHelper(mailHelper) + + @Rule + @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation) + + @Before + fun setup() { + Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet) + tapl.setEnableRotation(true) + tapl.setExpectedRotation(rotation.value) + tapl.enableTransientTaskbar(false) + ChangeDisplayOrientationRule.setRotation(rotation) + testApp.enterDesktopMode(wmHelper, device) + tapl.showTaskbarIfHidden() + mailApp.launchViaIntent(wmHelper) + mailApp.minimizeDesktopApp(wmHelper, device) + } + + @Test + open fun unminimizeApp() { + tapl.launchedAppState.taskbar + .openAllApps() + .getAppIcon(mailHelper.appName) + .launch(mailApp.packageName) + } + + @After + fun teardown() { + testApp.exit(wmHelper) + mailApp.exit(wmHelper) + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/UnminimizeAppFromTaskbar.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/UnminimizeAppFromTaskbar.kt new file mode 100644 index 000000000000..3e98e4342b3f --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/UnminimizeAppFromTaskbar.kt @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.scenarios + +import android.app.Instrumentation +import android.tools.NavBar +import android.tools.Rotation +import android.tools.device.apphelpers.BrowserAppHelper +import android.tools.flicker.rules.ChangeDisplayOrientationRule +import android.tools.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.server.wm.flicker.helpers.DesktopModeAppHelper +import com.android.server.wm.flicker.helpers.SimpleAppHelper +import com.android.window.flags.Flags +import com.android.wm.shell.Utils +import org.junit.After +import org.junit.Assume +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Test Base Class") +abstract class UnminimizeAppFromTaskbar(val rotation: Rotation = Rotation.ROTATION_0) { + + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation)) + private val browserHelper = BrowserAppHelper(instrumentation) + private val browserApp = DesktopModeAppHelper(browserHelper) + + @Rule + @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation) + + @Before + fun setup() { + Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet) + tapl.setEnableRotation(true) + tapl.setExpectedRotation(rotation.value) + tapl.enableTransientTaskbar(false) + ChangeDisplayOrientationRule.setRotation(rotation) + testApp.enterDesktopMode(wmHelper, device) + tapl.showTaskbarIfHidden() + browserApp.launchViaIntent(wmHelper) + browserApp.minimizeDesktopApp(wmHelper, device) + } + + @Test + open fun unminimizeApp() { + tapl.launchedAppState.taskbar + .getAppIcon(browserHelper.appName) + .launch(browserHelper.packageName) + } + + @After + fun teardown() { + testApp.exit(wmHelper) + browserApp.exit(wmHelper) + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/CopyContentInSplit.kt index 31d89f92f744..9d501d32fbc7 100644 --- a/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/CopyContentInSplit.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/CopyContentInSplit.kt @@ -23,8 +23,8 @@ import android.tools.traces.parsers.WindowManagerStateHelper import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.server.wm.flicker.helpers.RecentTasksUtils import com.android.wm.shell.Utils -import com.android.wm.shell.flicker.utils.RecentTasksUtils import com.android.wm.shell.flicker.utils.SplitScreenUtils import org.junit.After import org.junit.Before diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DismissSplitScreenByDivider.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DismissSplitScreenByDivider.kt index 1af6cac39085..f574f02ac3b3 100644 --- a/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DismissSplitScreenByDivider.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DismissSplitScreenByDivider.kt @@ -23,8 +23,8 @@ import android.tools.traces.parsers.WindowManagerStateHelper import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.server.wm.flicker.helpers.RecentTasksUtils import com.android.wm.shell.Utils -import com.android.wm.shell.flicker.utils.RecentTasksUtils import com.android.wm.shell.flicker.utils.SplitScreenUtils import org.junit.After import org.junit.Before diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DismissSplitScreenByGoHome.kt index 8ad8c7bd7a7f..60fcce2fbf18 100644 --- a/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DismissSplitScreenByGoHome.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DismissSplitScreenByGoHome.kt @@ -23,8 +23,8 @@ import android.tools.traces.parsers.WindowManagerStateHelper import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.server.wm.flicker.helpers.RecentTasksUtils import com.android.wm.shell.Utils -import com.android.wm.shell.flicker.utils.RecentTasksUtils import com.android.wm.shell.flicker.utils.SplitScreenUtils import org.junit.After import org.junit.Before diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DragDividerToResize.kt index da0ace472153..e6a080b2d258 100644 --- a/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DragDividerToResize.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DragDividerToResize.kt @@ -23,8 +23,8 @@ import android.tools.traces.parsers.WindowManagerStateHelper import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.server.wm.flicker.helpers.RecentTasksUtils import com.android.wm.shell.Utils -import com.android.wm.shell.flicker.utils.RecentTasksUtils import com.android.wm.shell.flicker.utils.SplitScreenUtils import org.junit.After import org.junit.Before 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 a6f8150ffc55..e51fa45c3010 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 @@ -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.component.ComponentNameMatcher +import android.view.WindowManagerGlobal import androidx.test.filters.FlakyTest import com.android.server.wm.flicker.entireScreenCovered import com.android.server.wm.flicker.helpers.FixedOrientationAppHelper @@ -38,13 +39,12 @@ import com.android.wm.shell.flicker.pip.common.PipTransition import com.android.wm.shell.flicker.pip.common.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE import com.android.wm.shell.flicker.pip.common.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_PORTRAIT import org.junit.Assume.assumeFalse -import org.junit.Assume.assumeTrue import org.junit.FixMethodOrder -import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized +import java.lang.AssertionError /** * Test entering pip while changing orientation (from app in landscape to pip window in portrait) @@ -75,7 +75,15 @@ import org.junit.runners.Parameterized open class EnterPipToOtherOrientation(flicker: LegacyFlickerTest) : PipTransition(flicker) { override val pipApp: PipAppHelper = PipAppHelper(instrumentation) internal val testApp = FixedOrientationAppHelper(instrumentation) - internal val startingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_90) + internal val ignoreOrientationRequest = WindowManagerGlobal.getWindowManagerService() + ?.getIgnoreOrientationRequest(WindowUtils.defaultDisplayId) + ?: throw AssertionError("WMS must not be null.") + internal val startingBounds = if (ignoreOrientationRequest) { + // If the device chooses to ignore orientation request, use the current display bounds. + WindowUtils.getDisplayBounds(Rotation.ROTATION_0) + } else { + WindowUtils.getDisplayBounds(Rotation.ROTATION_90) + } private val endingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0) override val thisTransition: FlickerBuilder.() -> Unit = { @@ -167,20 +175,14 @@ open class EnterPipToOtherOrientation(flicker: LegacyFlickerTest) : PipTransitio @Presubmit @Test open fun pipAppLayerCoversFullScreenOnStart() { - assumeFalse(tapl.isTablet) - flicker.assertLayersStart { visibleRegion(pipApp).coversExactly(startingBounds) } - } - - /** - * Checks that the visible region of [pipApp] covers the full display area at the start of the - * transition - */ - @Ignore("TODO(b/356277166): enable the tablet test") - @Test - open fun pipAppLayerPlusLetterboxCoversFullScreenOnStartTablet() { - assumeTrue(tapl.isTablet) flicker.assertLayersStart { - visibleRegion(pipApp.or(ComponentNameMatcher.LETTERBOX)).coversExactly(startingBounds) + visibleRegion( + if (ignoreOrientationRequest) { + pipApp.or(ComponentNameMatcher.LETTERBOX) + } else { + pipApp + } + ).coversExactly(startingBounds) } } diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt index f9c60ad14fae..aa893ed65e7c 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt @@ -19,7 +19,6 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Presubmit import android.platform.test.annotations.RequiresDevice -import android.platform.test.annotations.RequiresFlagsDisabled import android.tools.Rotation import android.tools.flicker.junit.FlickerParametersRunnerFactory import android.tools.flicker.legacy.FlickerBuilder @@ -29,7 +28,6 @@ import android.tools.helpers.WindowUtils import android.tools.traces.parsers.toFlickerComponent import com.android.server.wm.flicker.helpers.SimpleAppHelper import com.android.server.wm.flicker.testapp.ActivityOptions -import com.android.wm.shell.Flags import com.android.wm.shell.flicker.utils.SplitScreenUtils import org.junit.Assume import org.junit.FixMethodOrder @@ -64,7 +62,6 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2) open class FromSplitScreenAutoEnterPipOnGoToHomeTest(flicker: LegacyFlickerTest) : AutoEnterPipOnGoToHomeTest(flicker) { private val portraitDisplayBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0) 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 805f4c2fe7f8..8e7cb56c0f07 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 @@ -19,7 +19,6 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Presubmit import android.platform.test.annotations.RequiresDevice -import android.platform.test.annotations.RequiresFlagsDisabled import android.tools.Rotation import android.tools.flicker.junit.FlickerParametersRunnerFactory import android.tools.flicker.legacy.FlickerBuilder @@ -30,7 +29,6 @@ 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 import com.android.wm.shell.flicker.pip.common.EnterPipTransition import com.android.wm.shell.flicker.utils.SplitScreenUtils import org.junit.Assume @@ -66,7 +64,6 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2) @FlakyTest(bugId = 386333280) open class FromSplitScreenEnterPipOnUserLeaveHintTest(flicker: LegacyFlickerTest) : EnterPipTransition(flicker) { diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfEnterPipToOtherOrientation.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfEnterPipToOtherOrientation.kt index d65f158e00d6..676c9a4c7d8f 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfEnterPipToOtherOrientation.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfEnterPipToOtherOrientation.kt @@ -26,10 +26,7 @@ import com.android.server.wm.flicker.helpers.BottomHalfPipAppHelper import com.android.server.wm.flicker.helpers.PipAppHelper import com.android.wm.shell.Flags import com.android.wm.shell.flicker.pip.EnterPipToOtherOrientation -import org.junit.Assume.assumeFalse -import org.junit.Assume.assumeTrue import org.junit.FixMethodOrder -import org.junit.Ignore import org.junit.Test import org.junit.runners.MethodSorters import org.junit.runners.Parameterized @@ -71,19 +68,14 @@ class BottomHalfEnterPipToOtherOrientation(flicker: LegacyFlickerTest) : @Test override fun pipAppLayerCoversFullScreenOnStart() { // Test app and pip app should covers the entire screen on start. - assumeFalse(tapl.isTablet) flicker.assertLayersStart { - visibleRegion(pipApp.or(testApp)).coversExactly(startingBounds) - } - } - - @Ignore("TODO(b/356277166): enable the tablet test") - @Test - override fun pipAppLayerPlusLetterboxCoversFullScreenOnStartTablet() { - // Test app and pip app should covers the entire screen on start. - assumeTrue(tapl.isTablet) - flicker.assertLayersStart { - visibleRegion(pipApp.or(ComponentNameMatcher.LETTERBOX)).coversExactly(startingBounds) + visibleRegion( + if (ignoreOrientationRequest) { + pipApp.or(testApp).or(ComponentNameMatcher.LETTERBOX) + } else { + pipApp.or(testApp) + } + ).coversExactly(startingBounds) } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTransitionsTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTransitionsTest.java new file mode 100644 index 000000000000..9d0ddbc6de12 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTransitionsTest.java @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.wm.shell.bubbles; + +import static android.view.WindowManager.TRANSIT_CHANGE; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.ActivityManager; +import android.os.IBinder; +import android.view.SurfaceControl; +import android.view.ViewRootImpl; +import android.window.IWindowContainerToken; +import android.window.TransitionInfo; +import android.window.WindowContainerToken; + +import androidx.test.filters.SmallTest; + +import com.android.launcher3.icons.BubbleIconFactory; +import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.ShellTestCase; +import com.android.wm.shell.TestSyncExecutor; +import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView; +import com.android.wm.shell.bubbles.bar.BubbleBarLayerView; +import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.SyncTransactionQueue; +import com.android.wm.shell.taskview.TaskView; +import com.android.wm.shell.taskview.TaskViewRepository; +import com.android.wm.shell.taskview.TaskViewTaskController; +import com.android.wm.shell.taskview.TaskViewTransitions; +import com.android.wm.shell.transition.Transitions; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Tests of {@link BubbleTransitions}. + */ +@SmallTest +public class BubbleTransitionsTest extends ShellTestCase { + @Mock + private BubbleData mBubbleData; + @Mock + private Bubble mBubble; + @Mock + private Transitions mTransitions; + @Mock + private SyncTransactionQueue mSyncQueue; + @Mock + private BubbleExpandedViewManager mExpandedViewManager; + @Mock + private BubblePositioner mBubblePositioner; + @Mock + private BubbleLogger mBubbleLogger; + @Mock + private BubbleStackView mStackView; + @Mock + private BubbleBarLayerView mLayerView; + @Mock + private BubbleIconFactory mIconFactory; + + @Mock private ShellTaskOrganizer mTaskOrganizer; + private TaskViewTransitions mTaskViewTransitions; + private TaskViewRepository mRepository; + private BubbleTransitions mBubbleTransitions; + private BubbleTaskViewFactory mTaskViewFactory; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mRepository = new TaskViewRepository(); + ShellExecutor syncExecutor = new TestSyncExecutor(); + + when(mTransitions.getMainExecutor()).thenReturn(syncExecutor); + when(mTransitions.isRegistered()).thenReturn(true); + mTaskViewTransitions = new TaskViewTransitions(mTransitions, mRepository, mTaskOrganizer, + mSyncQueue); + mBubbleTransitions = new BubbleTransitions(mTransitions, mTaskOrganizer, mRepository, + mBubbleData, mTaskViewTransitions, mContext); + mTaskViewFactory = () -> { + TaskViewTaskController taskViewTaskController = new TaskViewTaskController( + mContext, mTaskOrganizer, mTaskViewTransitions, mSyncQueue); + TaskView taskView = new TaskView(mContext, mTaskViewTransitions, + taskViewTaskController); + return new BubbleTaskView(taskView, syncExecutor); + }; + final BubbleBarExpandedView bbev = mock(BubbleBarExpandedView.class); + final ViewRootImpl vri = mock(ViewRootImpl.class); + when(bbev.getViewRootImpl()).thenReturn(vri); + when(mBubble.getBubbleBarExpandedView()).thenReturn(bbev); + } + + private ActivityManager.RunningTaskInfo setupBubble() { + ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo(); + final IWindowContainerToken itoken = mock(IWindowContainerToken.class); + final IBinder asBinder = mock(IBinder.class); + when(itoken.asBinder()).thenReturn(asBinder); + WindowContainerToken token = new WindowContainerToken(itoken); + taskInfo.token = token; + final TaskView tv = mock(TaskView.class); + final TaskViewTaskController tvtc = mock(TaskViewTaskController.class); + when(tvtc.getTaskInfo()).thenReturn(taskInfo); + when(tv.getController()).thenReturn(tvtc); + when(mBubble.getTaskView()).thenReturn(tv); + mRepository.add(tvtc); + return taskInfo; + } + + @Test + public void testConvertToBubble() { + // Basic walk-through of convert-to-bubble transition stages + ActivityManager.RunningTaskInfo taskInfo = setupBubble(); + final BubbleTransitions.BubbleTransition bt = mBubbleTransitions.startConvertToBubble( + mBubble, taskInfo, mExpandedViewManager, mTaskViewFactory, mBubblePositioner, + mBubbleLogger, mStackView, mLayerView, mIconFactory, false); + final BubbleTransitions.ConvertToBubble ctb = (BubbleTransitions.ConvertToBubble) bt; + ctb.onInflated(mBubble); + when(mLayerView.canExpandView(any())).thenReturn(true); + verify(mTransitions).startTransition(anyInt(), any(), eq(ctb)); + verify(mBubble).setPreparingTransition(eq(bt)); + // Ensure we are communicating with the taskviewtransitions queue + assertTrue(mTaskViewTransitions.hasPending()); + + final TransitionInfo info = new TransitionInfo(TRANSIT_CHANGE, 0); + final TransitionInfo.Change chg = new TransitionInfo.Change(taskInfo.token, + mock(SurfaceControl.class)); + chg.setTaskInfo(taskInfo); + chg.setMode(TRANSIT_CHANGE); + info.addChange(chg); + info.addRoot(new TransitionInfo.Root(0, mock(SurfaceControl.class), 0, 0)); + SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class); + SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class); + final boolean[] finishCalled = new boolean[]{false}; + Transitions.TransitionFinishCallback finishCb = wct -> { + assertFalse(finishCalled[0]); + finishCalled[0] = true; + }; + ctb.startAnimation(ctb.mTransition, info, startT, finishT, finishCb); + assertFalse(mTaskViewTransitions.hasPending()); + + verify(mBubbleData).notificationEntryUpdated(eq(mBubble), anyBoolean(), anyBoolean()); + ctb.continueExpand(); + + clearInvocations(mBubble); + verify(mBubble, never()).setPreparingTransition(any()); + + ctb.surfaceCreated(); + verify(mBubble).setPreparingTransition(isNull()); + ArgumentCaptor<Runnable> animCb = ArgumentCaptor.forClass(Runnable.class); + verify(mLayerView).animateConvert(any(), any(), any(), any(), animCb.capture()); + assertFalse(finishCalled[0]); + animCb.getValue().run(); + assertTrue(finishCalled[0]); + } + + @Test + public void testConvertFromBubble() { + ActivityManager.RunningTaskInfo taskInfo = setupBubble(); + final BubbleTransitions.BubbleTransition bt = mBubbleTransitions.startConvertFromBubble( + mBubble, taskInfo); + final BubbleTransitions.ConvertFromBubble cfb = (BubbleTransitions.ConvertFromBubble) bt; + verify(mTransitions).startTransition(anyInt(), any(), eq(cfb)); + verify(mBubble).setPreparingTransition(eq(bt)); + assertTrue(mTaskViewTransitions.hasPending()); + + final TransitionInfo info = new TransitionInfo(TRANSIT_CHANGE, 0); + final TransitionInfo.Change chg = new TransitionInfo.Change(taskInfo.token, + mock(SurfaceControl.class)); + chg.setMode(TRANSIT_CHANGE); + chg.setTaskInfo(taskInfo); + info.addChange(chg); + info.addRoot(new TransitionInfo.Root(0, mock(SurfaceControl.class), 0, 0)); + SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class); + SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class); + Transitions.TransitionFinishCallback finishCb = wct -> {}; + cfb.startAnimation(cfb.mTransition, info, startT, finishT, finishCb); + + // Can really only verify that it interfaces with the taskViewTransitions queue. + // The actual functioning of this is tightly-coupled with SurfaceFlinger and renderthread + // in order to properly synchronize surface manipulation with drawing and thus can't be + // directly tested. + assertFalse(mTaskViewTransitions.hasPending()); + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiInstanceHelperTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiInstanceHelperTest.kt index bec91e910cf7..6b0c390ac239 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiInstanceHelperTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiInstanceHelperTest.kt @@ -33,8 +33,6 @@ import org.junit.runner.RunWith import org.mockito.ArgumentMatchers import org.mockito.ArgumentMatchers.eq import org.mockito.kotlin.any -import org.mockito.kotlin.doReturn -import org.mockito.kotlin.doThrow import org.mockito.kotlin.mock import org.mockito.kotlin.never import org.mockito.kotlin.verify @@ -80,17 +78,19 @@ class MultiInstanceHelperTest : ShellTestCase() { @Test fun supportsMultiInstanceSplit_inStaticAllowList() { val allowList = arrayOf(TEST_PACKAGE) - val helper = MultiInstanceHelper(mContext, context.packageManager, allowList, true) + val helper = MultiInstanceHelper(mContext, context.packageManager, allowList, + mock(), mock(), true) val component = ComponentName(TEST_PACKAGE, TEST_ACTIVITY) - assertEquals(true, helper.supportsMultiInstanceSplit(component)) + assertEquals(true, helper.supportsMultiInstanceSplit(component, TEST_OTHER_USER_ID)) } @Test fun supportsMultiInstanceSplit_notInStaticAllowList() { val allowList = arrayOf(TEST_PACKAGE) - val helper = MultiInstanceHelper(mContext, context.packageManager, allowList, true) + val helper = MultiInstanceHelper(mContext, context.packageManager, allowList, + mock(), mock(), true) val component = ComponentName(TEST_NOT_ALLOWED_PACKAGE, TEST_ACTIVITY) - assertEquals(false, helper.supportsMultiInstanceSplit(component)) + assertEquals(false, helper.supportsMultiInstanceSplit(component, TEST_OTHER_USER_ID)) } @Test @@ -99,17 +99,17 @@ class MultiInstanceHelperTest : ShellTestCase() { val component = ComponentName(TEST_PACKAGE, TEST_ACTIVITY) val pm = mock<PackageManager>() val activityProp = PackageManager.Property("", true, "", "") - whenever(pm.getProperty(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), - eq(component))) + whenever(pm.getPropertyAsUser(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), + eq(component.packageName), eq(component.className), eq(TEST_OTHER_USER_ID))) .thenReturn(activityProp) val appProp = PackageManager.Property("", false, "", "") - whenever(pm.getProperty(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), - eq(component.packageName))) + whenever(pm.getPropertyAsUser(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), + eq(component.packageName), eq(null), eq(TEST_OTHER_USER_ID))) .thenReturn(appProp) - val helper = MultiInstanceHelper(mContext, pm, emptyArray(), true) + val helper = MultiInstanceHelper(mContext, pm, emptyArray(), mock(), mock(), true) // Expect activity property to override application property - assertEquals(true, helper.supportsMultiInstanceSplit(component)) + assertEquals(true, helper.supportsMultiInstanceSplit(component, TEST_OTHER_USER_ID)) } @Test @@ -118,17 +118,17 @@ class MultiInstanceHelperTest : ShellTestCase() { val component = ComponentName(TEST_PACKAGE, TEST_ACTIVITY) val pm = mock<PackageManager>() val activityProp = PackageManager.Property("", false, "", "") - whenever(pm.getProperty(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), - eq(component))) + whenever(pm.getPropertyAsUser(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), + eq(component.packageName), eq(component.className), eq(TEST_OTHER_USER_ID))) .thenReturn(activityProp) val appProp = PackageManager.Property("", true, "", "") - whenever(pm.getProperty(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), - eq(component.packageName))) + whenever(pm.getPropertyAsUser(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), + eq(component.packageName), eq(null), eq(TEST_OTHER_USER_ID))) .thenReturn(appProp) - val helper = MultiInstanceHelper(mContext, pm, emptyArray(), true) + val helper = MultiInstanceHelper(mContext, pm, emptyArray(), mock(), mock(), true) // Expect activity property to override application property - assertEquals(false, helper.supportsMultiInstanceSplit(component)) + assertEquals(false, helper.supportsMultiInstanceSplit(component, TEST_OTHER_USER_ID)) } @Test @@ -136,17 +136,17 @@ class MultiInstanceHelperTest : ShellTestCase() { fun supportsMultiInstanceSplit_noActivityPropertyApplicationPropertyTrue() { val component = ComponentName(TEST_PACKAGE, TEST_ACTIVITY) val pm = mock<PackageManager>() - whenever(pm.getProperty(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), - eq(component))) + whenever(pm.getPropertyAsUser(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), + eq(component.packageName), eq(component.className), eq(TEST_OTHER_USER_ID))) .thenThrow(PackageManager.NameNotFoundException()) val appProp = PackageManager.Property("", true, "", "") - whenever(pm.getProperty(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), - eq(component.packageName))) + whenever(pm.getPropertyAsUser(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), + eq(component.packageName), eq(null), eq(TEST_OTHER_USER_ID))) .thenReturn(appProp) - val helper = MultiInstanceHelper(mContext, pm, emptyArray(), true) + val helper = MultiInstanceHelper(mContext, pm, emptyArray(), mock(), mock(), true) // Expect fall through to app property - assertEquals(true, helper.supportsMultiInstanceSplit(component)) + assertEquals(true, helper.supportsMultiInstanceSplit(component, TEST_OTHER_USER_ID)) } @Test @@ -154,15 +154,15 @@ class MultiInstanceHelperTest : ShellTestCase() { fun supportsMultiInstanceSplit_noActivityOrAppProperty() { val component = ComponentName(TEST_PACKAGE, TEST_ACTIVITY) val pm = mock<PackageManager>() - whenever(pm.getProperty(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), - eq(component))) + whenever(pm.getPropertyAsUser(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), + eq(component.packageName), eq(component.className), eq(TEST_OTHER_USER_ID))) .thenThrow(PackageManager.NameNotFoundException()) - whenever(pm.getProperty(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), - eq(component.packageName))) + whenever(pm.getPropertyAsUser(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), + eq(component.packageName), eq(null), eq(TEST_OTHER_USER_ID))) .thenThrow(PackageManager.NameNotFoundException()) - val helper = MultiInstanceHelper(mContext, pm, emptyArray(), true) - assertEquals(false, helper.supportsMultiInstanceSplit(component)) + val helper = MultiInstanceHelper(mContext, pm, emptyArray(), mock(), mock(), true) + assertEquals(false, helper.supportsMultiInstanceSplit(component, TEST_OTHER_USER_ID)) } @Test @@ -171,24 +171,25 @@ class MultiInstanceHelperTest : ShellTestCase() { val component = ComponentName(TEST_PACKAGE, TEST_ACTIVITY) val pm = mock<PackageManager>() val activityProp = PackageManager.Property("", true, "", "") - whenever(pm.getProperty(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), - eq(component))) + whenever(pm.getPropertyAsUser(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), + eq(component.packageName), eq(component.className), eq(TEST_OTHER_USER_ID))) .thenReturn(activityProp) val appProp = PackageManager.Property("", true, "", "") - whenever(pm.getProperty(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), - eq(component.packageName))) + whenever(pm.getPropertyAsUser(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), + eq(component.packageName), eq(null), eq(TEST_OTHER_USER_ID))) .thenReturn(appProp) - val helper = MultiInstanceHelper(mContext, pm, emptyArray(), false) + val helper = MultiInstanceHelper(mContext, pm, emptyArray(), mock(), mock(), false) // Expect we only check the static list and not the property - assertEquals(false, helper.supportsMultiInstanceSplit(component)) + assertEquals(false, helper.supportsMultiInstanceSplit(component, TEST_OTHER_USER_ID)) verify(pm, never()).getProperty(any(), any<ComponentName>()) } companion object { val TEST_PACKAGE = "com.android.wm.shell.common" - val TEST_NOT_ALLOWED_PACKAGE = "com.android.wm.shell.common.fake"; - val TEST_ACTIVITY = "TestActivity"; + val TEST_NOT_ALLOWED_PACKAGE = "com.android.wm.shell.common.fake" + val TEST_ACTIVITY = "TestActivity" val TEST_SHORTCUT_ID = "test_shortcut_1" + val TEST_OTHER_USER_ID = 1234 } }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/UserProfileContextsTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/UserProfileContextsTest.kt new file mode 100644 index 000000000000..ef0b8ab14c81 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/UserProfileContextsTest.kt @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.common + +import android.app.ActivityManager +import android.content.Context +import android.content.pm.UserInfo +import android.os.UserHandle +import android.os.UserManager +import android.testing.AndroidTestingRunner +import com.android.wm.shell.ShellTestCase +import com.android.wm.shell.TestShellExecutor +import com.android.wm.shell.sysui.ShellController +import com.android.wm.shell.sysui.ShellInit +import com.android.wm.shell.sysui.UserChangeListener +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.anyInt +import org.mockito.kotlin.any +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.doAnswer +import org.mockito.kotlin.doReturn +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 + +/** + * Tests for [UserProfileContexts]. + */ +@RunWith(AndroidTestingRunner::class) +class UserProfileContextsTest : ShellTestCase() { + + private val testExecutor = TestShellExecutor() + private val shellInit = ShellInit(testExecutor) + private val activityManager = mock<ActivityManager>() + private val userManager = mock<UserManager>() + private val shellController = mock<ShellController>() + private val baseContext = mock<Context>() + + private lateinit var userProfilesContexts: UserProfileContexts + + @Before + fun setUp() { + doReturn(activityManager) + .whenever(baseContext) + .getSystemService(eq(ActivityManager::class.java)) + doReturn(userManager).whenever(baseContext).getSystemService(eq(UserManager::class.java)) + doAnswer { invocation -> + val userHandle = invocation.getArgument<UserHandle>(0) + createContextForUser(userHandle.identifier) + } + .whenever(baseContext) + .createContextAsUser(any<UserHandle>(), anyInt()) + // Define users and profiles + val currentUser = ActivityManager.getCurrentUser() + whenever(userManager.getProfiles(eq(currentUser))) + .thenReturn( + listOf(UserInfo(currentUser, "Current", 0), UserInfo(MAIN_PROFILE, "Work", 0)) + ) + whenever(userManager.getProfiles(eq(SECOND_USER))).thenReturn(SECOND_PROFILES) + userProfilesContexts = UserProfileContexts(baseContext, shellController, shellInit) + shellInit.init() + } + + @Test + fun onInit_registerUserChangeAndInit() { + val currentUser = ActivityManager.getCurrentUser() + + verify(shellController, times(1)).addUserChangeListener(any()) + assertThat(userProfilesContexts.userContext.userId).isEqualTo(currentUser) + assertThat(userProfilesContexts[currentUser]?.userId).isEqualTo(currentUser) + assertThat(userProfilesContexts[MAIN_PROFILE]?.userId).isEqualTo(MAIN_PROFILE) + assertThat(userProfilesContexts[SECOND_USER]).isNull() + } + + @Test + fun onUserChanged_updateUserContext() { + val userChangeListener = retrieveUserChangeListener() + val newUserContext = createContextForUser(SECOND_USER) + + userChangeListener.onUserChanged(SECOND_USER, newUserContext) + + assertThat(userProfilesContexts.userContext).isEqualTo(newUserContext) + assertThat(userProfilesContexts[SECOND_USER]).isEqualTo(newUserContext) + } + + @Test + fun onUserProfilesChanged_updateAllContexts() { + val userChangeListener = retrieveUserChangeListener() + val newUserContext = createContextForUser(SECOND_USER) + userChangeListener.onUserChanged(SECOND_USER, newUserContext) + + userChangeListener.onUserProfilesChanged(SECOND_PROFILES) + + assertThat(userProfilesContexts.userContext).isEqualTo(newUserContext) + assertThat(userProfilesContexts[SECOND_USER]).isEqualTo(newUserContext) + assertThat(userProfilesContexts[SECOND_PROFILE]?.userId).isEqualTo(SECOND_PROFILE) + assertThat(userProfilesContexts[SECOND_PROFILE_2]?.userId).isEqualTo(SECOND_PROFILE_2) + } + + @Test + fun onUserProfilesChanged_keepOnlyNewProfiles() { + val userChangeListener = retrieveUserChangeListener() + val newUserContext = createContextForUser(SECOND_USER) + userChangeListener.onUserChanged(SECOND_USER, newUserContext) + userChangeListener.onUserProfilesChanged(SECOND_PROFILES) + val newProfiles = listOf( + UserInfo(SECOND_USER, "Second", 0), + UserInfo(SECOND_PROFILE, "Second Profile", 0), + UserInfo(MAIN_PROFILE, "Main profile", 0), + ) + + userChangeListener.onUserProfilesChanged(newProfiles) + + assertThat(userProfilesContexts[SECOND_PROFILE_2]).isNull() + assertThat(userProfilesContexts[MAIN_PROFILE]?.userId).isEqualTo(MAIN_PROFILE) + assertThat(userProfilesContexts[SECOND_USER]?.userId).isEqualTo(SECOND_USER) + assertThat(userProfilesContexts[SECOND_PROFILE]?.userId).isEqualTo(SECOND_PROFILE) + } + + private fun retrieveUserChangeListener(): UserChangeListener { + val captor = argumentCaptor<UserChangeListener>() + + verify(shellController, times(1)).addUserChangeListener(captor.capture()) + + return captor.firstValue + } + + private fun createContextForUser(userId: Int): Context { + val newContext = mock<Context>() + whenever(newContext.userId).thenReturn(userId) + return newContext + } + + private companion object { + const val SECOND_USER = 3 + const val MAIN_PROFILE = 11 + const val SECOND_PROFILE = 15 + const val SECOND_PROFILE_2 = 17 + + val SECOND_PROFILES = + listOf( + UserInfo(SECOND_USER, "Second", 0), + UserInfo(SECOND_PROFILE, "Second Profile", 0), + UserInfo(SECOND_PROFILE_2, "Second Profile 2", 0), + ) + } +} 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 ecad5217b87f..957fdf995776 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 @@ -183,6 +183,8 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() { @Test fun handleActivityOrientationChange_resizeable_doNothing() { + userRepositories.current.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0) + userRepositories.current.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0) val task = setUpFreeformTask() taskStackListener.onActivityRequestedOrientationChanged( @@ -195,6 +197,8 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() { @Test fun handleActivityOrientationChange_nonResizeableFullscreen_doNothing() { + userRepositories.current.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0) + userRepositories.current.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0) val task = createFullscreenTask() task.isResizeable = false val activityInfo = ActivityInfo() @@ -214,6 +218,8 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() { @Test fun handleActivityOrientationChange_nonResizeablePortrait_requestSameOrientation_doNothing() { + userRepositories.current.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0) + userRepositories.current.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0) val task = setUpFreeformTask(isResizeable = false) val newTask = setUpFreeformTask( @@ -228,6 +234,8 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() { @Test fun handleActivityOrientationChange_notInDesktopMode_doNothing() { + userRepositories.current.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0) + userRepositories.current.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0) val task = setUpFreeformTask(isResizeable = false) userRepositories.current.updateTask(task.displayId, task.taskId, isVisible = false) @@ -241,6 +249,8 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() { @Test fun handleActivityOrientationChange_nonResizeablePortrait_respectLandscapeRequest() { + userRepositories.current.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0) + userRepositories.current.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0) val task = setUpFreeformTask(isResizeable = false) val oldBounds = task.configuration.windowConfiguration.bounds val newTask = @@ -263,6 +273,8 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() { @Test fun handleActivityOrientationChange_nonResizeableLandscape_respectPortraitRequest() { + userRepositories.current.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0) + userRepositories.current.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0) val oldBounds = Rect(0, 0, 500, 200) val task = setUpFreeformTask( diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt index 6a3717427e93..fae7363e0676 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt @@ -16,10 +16,14 @@ package com.android.wm.shell.desktopmode +import android.app.ActivityManager.RunningTaskInfo import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN +import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED import android.content.ContentResolver import android.os.Binder +import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.SetFlagsRule import android.provider.Settings import android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS import android.testing.AndroidTestingRunner @@ -29,20 +33,27 @@ import android.view.WindowManager.TRANSIT_CHANGE import android.window.DisplayAreaInfo import android.window.WindowContainerTransaction import androidx.test.filters.SmallTest +import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession import com.android.dx.mockito.inline.extended.ExtendedMockito.never +import com.android.dx.mockito.inline.extended.StaticMockitoSession +import com.android.window.flags.Flags import com.android.wm.shell.MockToken import com.android.wm.shell.RootTaskDisplayAreaOrganizer +import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.ShellTestCase +import com.android.wm.shell.TestRunningTaskInfoBuilder import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.DisplayController.OnDisplaysChangedListener import com.android.wm.shell.common.ShellExecutor +import com.android.wm.shell.shared.desktopmode.DesktopModeStatus import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.Transitions import com.google.common.truth.Truth.assertThat +import org.junit.After import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatchers.isNull import org.mockito.Mock import org.mockito.Mockito.anyInt @@ -53,6 +64,7 @@ import org.mockito.kotlin.any import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.eq import org.mockito.kotlin.whenever +import org.mockito.quality.Strictness /** * Test class for [DesktopDisplayEventHandler] @@ -63,21 +75,44 @@ import org.mockito.kotlin.whenever @RunWith(AndroidTestingRunner::class) class DesktopDisplayEventHandlerTest : ShellTestCase() { + @JvmField @Rule val setFlagsRule = SetFlagsRule() + @Mock lateinit var testExecutor: ShellExecutor @Mock lateinit var transitions: Transitions @Mock lateinit var displayController: DisplayController @Mock lateinit var rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer @Mock private lateinit var mockWindowManager: IWindowManager + @Mock private lateinit var mockDesktopUserRepositories: DesktopUserRepositories + @Mock private lateinit var mockDesktopRepository: DesktopRepository + @Mock private lateinit var mockDesktopTasksController: DesktopTasksController + @Mock private lateinit var shellTaskOrganizer: ShellTaskOrganizer + private lateinit var mockitoSession: StaticMockitoSession private lateinit var shellInit: ShellInit private lateinit var handler: DesktopDisplayEventHandler + private val onDisplaysChangedListenerCaptor = argumentCaptor<OnDisplaysChangedListener>() + private val runningTasks = mutableListOf<RunningTaskInfo>() + private val externalDisplayId = 100 + private val freeformTask = + TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FREEFORM).build() + private val fullscreenTask = + TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FULLSCREEN).build() + private val defaultTDA = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0) + @Before fun setUp() { + mockitoSession = + mockitoSession() + .strictness(Strictness.LENIENT) + .spyStatic(DesktopModeStatus::class.java) + .startMocking() + shellInit = spy(ShellInit(testExecutor)) + whenever(mockDesktopUserRepositories.current).thenReturn(mockDesktopRepository) whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() } - val tda = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0) - whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)).thenReturn(tda) + whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)) + .thenReturn(defaultTDA) handler = DesktopDisplayEventHandler( context, @@ -86,8 +121,21 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() { displayController, rootTaskDisplayAreaOrganizer, mockWindowManager, + mockDesktopUserRepositories, + mockDesktopTasksController, + shellTaskOrganizer, ) + whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenAnswer { runningTasks } + runningTasks.add(freeformTask) + runningTasks.add(fullscreenTask) shellInit.init() + verify(displayController) + .addDisplayWindowListener(onDisplaysChangedListenerCaptor.capture()) + } + + @After + fun tearDown() { + mockitoSession.finishMocking() } private fun testDisplayWindowingModeSwitch( @@ -95,11 +143,7 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() { extendedDisplayEnabled: Boolean, expectTransition: Boolean, ) { - val externalDisplayId = 100 - val captor = ArgumentCaptor.forClass(OnDisplaysChangedListener::class.java) - verify(displayController).addDisplayWindowListener(captor.capture()) - val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!! - tda.configuration.windowConfiguration.windowingMode = defaultWindowingMode + defaultTDA.configuration.windowConfiguration.windowingMode = defaultWindowingMode whenever(mockWindowManager.getWindowingMode(anyInt())).thenAnswer { defaultWindowingMode } val settingsSession = ExtendedDisplaySettingsSession( @@ -108,23 +152,17 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() { ) settingsSession.use { - // The external display connected. - whenever(rootTaskDisplayAreaOrganizer.getDisplayIds()) - .thenReturn(intArrayOf(DEFAULT_DISPLAY, externalDisplayId)) - captor.value.onDisplayAdded(externalDisplayId) - tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM - // The external display disconnected. - whenever(rootTaskDisplayAreaOrganizer.getDisplayIds()) - .thenReturn(intArrayOf(DEFAULT_DISPLAY)) - captor.value.onDisplayRemoved(externalDisplayId) + connectExternalDisplay() + defaultTDA.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM + disconnectExternalDisplay() if (expectTransition) { val arg = argumentCaptor<WindowContainerTransaction>() verify(transitions, times(2)) .startTransition(eq(TRANSIT_CHANGE), arg.capture(), isNull()) - assertThat(arg.firstValue.changes[tda.token.asBinder()]?.windowingMode) + assertThat(arg.firstValue.changes[defaultTDA.token.asBinder()]?.windowingMode) .isEqualTo(WINDOWING_MODE_FREEFORM) - assertThat(arg.secondValue.changes[tda.token.asBinder()]?.windowingMode) + assertThat(arg.secondValue.changes[defaultTDA.token.asBinder()]?.windowingMode) .isEqualTo(defaultWindowingMode) } else { verify(transitions, never()).startTransition(eq(TRANSIT_CHANGE), any(), isNull()) @@ -159,6 +197,96 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() { ) } + @Test + fun testDisplayAdded_supportsDesks_createsDesk() { + whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(true) + + onDisplaysChangedListenerCaptor.lastValue.onDisplayAdded(DEFAULT_DISPLAY) + + verify(mockDesktopTasksController).createDesk(DEFAULT_DISPLAY) + } + + @Test + fun testDisplayAdded_cannotEnterDesktopMode_doesNotCreateDesk() { + whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(false) + + onDisplaysChangedListenerCaptor.lastValue.onDisplayAdded(DEFAULT_DISPLAY) + + verify(mockDesktopTasksController, never()).createDesk(DEFAULT_DISPLAY) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun testDeskRemoved_noDesksRemain_createsDesk() { + whenever(mockDesktopRepository.getNumberOfDesks(DEFAULT_DISPLAY)).thenReturn(0) + + handler.onDeskRemoved(DEFAULT_DISPLAY, deskId = 1) + + verify(mockDesktopTasksController).createDesk(DEFAULT_DISPLAY) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun testDeskRemoved_desksRemain_doesNotCreateDesk() { + whenever(mockDesktopRepository.getNumberOfDesks(DEFAULT_DISPLAY)).thenReturn(1) + + handler.onDeskRemoved(DEFAULT_DISPLAY, deskId = 1) + + verify(mockDesktopTasksController, never()).createDesk(DEFAULT_DISPLAY) + } + + @Test + fun displayWindowingModeSwitch_existingTasksOnConnected() { + defaultTDA.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN + whenever(mockWindowManager.getWindowingMode(anyInt())).thenAnswer { + WINDOWING_MODE_FULLSCREEN + } + + ExtendedDisplaySettingsSession(context.contentResolver, 1).use { + connectExternalDisplay() + + val arg = argumentCaptor<WindowContainerTransaction>() + verify(transitions, times(1)) + .startTransition(eq(TRANSIT_CHANGE), arg.capture(), isNull()) + assertThat(arg.firstValue.changes[freeformTask.token.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_UNDEFINED) + assertThat(arg.firstValue.changes[fullscreenTask.token.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_FULLSCREEN) + } + } + + @Test + fun displayWindowingModeSwitch_existingTasksOnDisconnected() { + defaultTDA.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM + whenever(mockWindowManager.getWindowingMode(anyInt())).thenAnswer { + WINDOWING_MODE_FULLSCREEN + } + + ExtendedDisplaySettingsSession(context.contentResolver, 1).use { + disconnectExternalDisplay() + + val arg = argumentCaptor<WindowContainerTransaction>() + verify(transitions, times(1)) + .startTransition(eq(TRANSIT_CHANGE), arg.capture(), isNull()) + assertThat(arg.firstValue.changes[freeformTask.token.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_FREEFORM) + assertThat(arg.firstValue.changes[fullscreenTask.token.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_UNDEFINED) + } + } + + private fun connectExternalDisplay() { + whenever(rootTaskDisplayAreaOrganizer.getDisplayIds()) + .thenReturn(intArrayOf(DEFAULT_DISPLAY, externalDisplayId)) + onDisplaysChangedListenerCaptor.lastValue.onDisplayAdded(externalDisplayId) + } + + private fun disconnectExternalDisplay() { + whenever(rootTaskDisplayAreaOrganizer.getDisplayIds()) + .thenReturn(intArrayOf(DEFAULT_DISPLAY)) + onDisplaysChangedListenerCaptor.lastValue.onDisplayRemoved(externalDisplayId) + } + private class ExtendedDisplaySettingsSession( private val contentResolver: ContentResolver, private val overrideValue: Int, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt index eb6f1d75e6ca..bddc06204a52 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt @@ -35,12 +35,14 @@ import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.DisplayLayout import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.EnterReason import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ExitReason +import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.FocusReason import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.NO_SESSION_ID import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.TaskSizeUpdate import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.TaskUpdate +import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UNSET_FOCUS_REASON import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UNSET_MINIMIZE_REASON import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UNSET_UNMINIMIZE_REASON import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UnminimizeReason @@ -188,6 +190,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() { UNSET_MINIMIZE_REASON, UNSET_UNMINIMIZE_REASON, TASK_COUNT, + UNSET_FOCUS_REASON, ) verify { EventLogTags.writeWmShellDesktopModeTaskUpdate( @@ -202,6 +205,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() { eq(UNSET_MINIMIZE_REASON), eq(UNSET_UNMINIMIZE_REASON), eq(TASK_COUNT), + eq(UNSET_FOCUS_REASON), ) } verifyZeroInteractions(staticMockMarker(EventLogTags::class.java)) @@ -233,6 +237,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() { UNSET_MINIMIZE_REASON, UNSET_UNMINIMIZE_REASON, TASK_COUNT, + UNSET_FOCUS_REASON, ) verify { EventLogTags.writeWmShellDesktopModeTaskUpdate( @@ -247,6 +252,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() { eq(UNSET_MINIMIZE_REASON), eq(UNSET_UNMINIMIZE_REASON), eq(TASK_COUNT), + eq(UNSET_FOCUS_REASON), ) } verifyZeroInteractions(staticMockMarker(EventLogTags::class.java)) @@ -278,6 +284,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() { UNSET_MINIMIZE_REASON, UNSET_UNMINIMIZE_REASON, TASK_COUNT, + UNSET_FOCUS_REASON, ) verify { EventLogTags.writeWmShellDesktopModeTaskUpdate( @@ -295,6 +302,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() { eq(UNSET_MINIMIZE_REASON), eq(UNSET_UNMINIMIZE_REASON), eq(TASK_COUNT), + eq(UNSET_FOCUS_REASON), ) } verifyZeroInteractions(staticMockMarker(EventLogTags::class.java)) @@ -320,6 +328,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() { MinimizeReason.TASK_LIMIT.reason, UNSET_UNMINIMIZE_REASON, TASK_COUNT, + UNSET_FOCUS_REASON, ) verify { EventLogTags.writeWmShellDesktopModeTaskUpdate( @@ -337,6 +346,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() { eq(MinimizeReason.TASK_LIMIT.reason), eq(UNSET_UNMINIMIZE_REASON), eq(TASK_COUNT), + eq(UNSET_FOCUS_REASON), ) } verifyZeroInteractions(staticMockMarker(EventLogTags::class.java)) @@ -362,6 +372,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() { UNSET_MINIMIZE_REASON, UnminimizeReason.TASKBAR_TAP.reason, TASK_COUNT, + UNSET_FOCUS_REASON, ) verify { EventLogTags.writeWmShellDesktopModeTaskUpdate( @@ -379,6 +390,51 @@ class DesktopModeEventLoggerTest : ShellTestCase() { eq(UNSET_MINIMIZE_REASON), eq(UnminimizeReason.TASKBAR_TAP.reason), eq(TASK_COUNT), + eq(UNSET_FOCUS_REASON), + ) + } + verifyZeroInteractions(staticMockMarker(EventLogTags::class.java)) + } + + @Test + fun logTaskInfoChanged_logsTaskUpdateWithFocusReason() { + val sessionId = startDesktopModeSession() + + desktopModeEventLogger.logTaskInfoChanged( + createTaskUpdate(focusChangesReason = FocusReason.UNKNOWN) + ) + + verifyOnlyOneTaskUpdateLogging( + FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED, + TASK_UPDATE.instanceId, + TASK_UPDATE.uid, + TASK_UPDATE.taskHeight, + TASK_UPDATE.taskWidth, + TASK_UPDATE.taskX, + TASK_UPDATE.taskY, + sessionId, + UNSET_MINIMIZE_REASON, + UNSET_UNMINIMIZE_REASON, + TASK_COUNT, + FocusReason.UNKNOWN.reason, + ) + verify { + EventLogTags.writeWmShellDesktopModeTaskUpdate( + eq( + FrameworkStatsLog + .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED + ), + eq(TASK_UPDATE.instanceId), + eq(TASK_UPDATE.uid), + eq(TASK_UPDATE.taskHeight), + eq(TASK_UPDATE.taskWidth), + eq(TASK_UPDATE.taskX), + eq(TASK_UPDATE.taskY), + eq(sessionId), + eq(UNSET_MINIMIZE_REASON), + eq(UNSET_UNMINIMIZE_REASON), + eq(TASK_COUNT), + eq(FocusReason.UNKNOWN.reason), ) } verifyZeroInteractions(staticMockMarker(EventLogTags::class.java)) @@ -497,6 +553,8 @@ class DesktopModeEventLoggerTest : ShellTestCase() { eq(UNSET_UNMINIMIZE_REASON), /* visible_task_count */ eq(0), + /* focus_reason */ + eq(UNSET_FOCUS_REASON), ) } } @@ -536,6 +594,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() { anyInt(), anyInt(), anyInt(), + anyInt(), ) }, never(), @@ -597,6 +656,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() { minimizeReason: Int, unminimizeReason: Int, visibleTaskCount: Int, + focusChangedReason: Int, ) { verify({ FrameworkStatsLog.write( @@ -612,6 +672,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() { eq(minimizeReason), eq(unminimizeReason), eq(visibleTaskCount), + eq(focusChangedReason), ) }) verify({ @@ -628,6 +689,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() { anyInt(), anyInt(), anyInt(), + anyInt(), ) }) } @@ -710,6 +772,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() { private fun createTaskUpdate( minimizeReason: MinimizeReason? = null, unminimizeReason: UnminimizeReason? = null, + focusChangesReason: FocusReason? = null, ) = TaskUpdate( TASK_ID, @@ -721,6 +784,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() { minimizeReason, unminimizeReason, TASK_COUNT, + focusChangesReason, ) } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt index 2e9d6d95eebb..b7d25b5255f8 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt @@ -38,6 +38,7 @@ import android.view.WindowManager.TRANSIT_WAKE import android.window.IWindowContainerToken import android.window.TransitionInfo import android.window.TransitionInfo.Change +import android.window.TransitionInfo.FLAG_MOVED_TO_TOP import android.window.WindowContainerToken import androidx.test.filters.SmallTest import com.android.dx.mockito.inline.extended.ExtendedMockito @@ -47,6 +48,7 @@ import com.android.wm.shell.ShellTestCase import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.EnterReason import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ExitReason +import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.FocusReason import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.TaskUpdate import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UnminimizeReason @@ -175,7 +177,7 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { verifyTaskAddedAndEnterLogging( EnterReason.APP_FREEFORM_INTENT, - DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1), + DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1, focusReason = FocusReason.UNKNOWN), ) } @@ -635,7 +637,15 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { callOnTransitionReady(transitionInfo) verify(desktopModeEventLogger, times(1)) - .logTaskAdded(eq(DEFAULT_TASK_UPDATE.copy(instanceId = 2, visibleTaskCount = 2))) + .logTaskAdded( + eq( + DEFAULT_TASK_UPDATE.copy( + instanceId = 2, + visibleTaskCount = 2, + focusReason = FocusReason.UNKNOWN, + ) + ) + ) verify(desktopModeEventLogger, never()).logSessionEnter(any()) } @@ -695,6 +705,34 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { } @Test + fun sessionAlreadyStarted_taskFocusChanged_logsTaskUpdate() { + val taskInfo1 = createTaskInfo(WINDOWING_MODE_FREEFORM, id = 1) + val taskInfo2 = createTaskInfo(WINDOWING_MODE_FREEFORM, id = 2) + transitionObserver.addTaskInfosToCachedMap(taskInfo1) + transitionObserver.addTaskInfosToCachedMap(taskInfo2) + transitionObserver.isSessionActive = true + transitionObserver.setFocusedTaskForTesting(taskInfo1) + + val task2FocusedChange = createChange(TRANSIT_CHANGE, taskInfo2) + task2FocusedChange.flags = FLAG_MOVED_TO_TOP + val transitionInfo = + TransitionInfoBuilder(TRANSIT_CHANGE, 0).addChange(task2FocusedChange).build() + callOnTransitionReady(transitionInfo) + + verify(desktopModeEventLogger, times(1)) + .logTaskInfoChanged( + eq( + DEFAULT_TASK_UPDATE.copy( + instanceId = 2, + visibleTaskCount = 2, + focusReason = FocusReason.UNKNOWN, + ) + ) + ) + verifyZeroInteractions(desktopModeEventLogger) + } + + @Test fun sessionAlreadyStarted_multipleTasksUpdated_logsTaskUpdateForCorrectTask() { // add 2 existing freeform task val taskInfo1 = createTaskInfo(WINDOWING_MODE_FREEFORM) 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 8d73f3f59afd..f5c93ee8ffe4 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 @@ -17,13 +17,15 @@ package com.android.wm.shell.desktopmode import android.graphics.Rect +import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.FlagsParameterization import android.platform.test.flag.junit.SetFlagsRule -import android.testing.AndroidTestingRunner import android.util.ArraySet import android.view.Display.DEFAULT_DISPLAY import android.view.Display.INVALID_DISPLAY import androidx.test.filters.SmallTest +import com.android.window.flags.Flags import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP import com.android.wm.shell.ShellTestCase @@ -56,6 +58,8 @@ import org.mockito.kotlin.never 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 /** * Tests for [@link DesktopRepository]. @@ -63,11 +67,11 @@ import org.mockito.kotlin.whenever * Build/Install/Run: atest WMShellUnitTests:DesktopRepositoryTest */ @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(ParameterizedAndroidJunit4::class) @ExperimentalCoroutinesApi -class DesktopRepositoryTest : ShellTestCase() { +class DesktopRepositoryTest(flags: FlagsParameterization) : ShellTestCase() { - @JvmField @Rule val setFlagsRule = SetFlagsRule() + @JvmField @Rule val setFlagsRule = SetFlagsRule(flags) private lateinit var repo: DesktopRepository private lateinit var shellInit: ShellInit @@ -86,6 +90,8 @@ class DesktopRepositoryTest : ShellTestCase() { whenever(runBlocking { persistentRepository.readDesktop(any(), any()) }) .thenReturn(Desktop.getDefaultInstance()) shellInit.init() + repo.addDesk(displayId = DEFAULT_DISPLAY, deskId = DEFAULT_DESKTOP_ID) + repo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = DEFAULT_DESKTOP_ID) } @After @@ -137,6 +143,7 @@ class DesktopRepositoryTest : ShellTestCase() { @Test fun addTask_multipleDisplays_notifiesCorrectListener() { + repo.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY) val listener = TestListener() repo.addActiveTaskListener(listener) @@ -150,6 +157,7 @@ class DesktopRepositoryTest : ShellTestCase() { @Test fun addTask_multipleDisplays_moveToAnotherDisplay() { + repo.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY) repo.addTask(DEFAULT_DISPLAY, taskId = 1, isVisible = true) repo.addTask(SECOND_DISPLAY, taskId = 1, isVisible = true) assertThat(repo.getFreeformTasksInZOrder(DEFAULT_DISPLAY)).isEmpty() @@ -310,19 +318,21 @@ class DesktopRepositoryTest : ShellTestCase() { @Test fun isOnlyVisibleNonClosingTask_multipleDisplays() { + repo.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY) + repo.setActiveDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY) repo.updateTask(DEFAULT_DISPLAY, taskId = 1, isVisible = true) repo.updateTask(DEFAULT_DISPLAY, taskId = 2, isVisible = true) repo.updateTask(SECOND_DISPLAY, taskId = 3, isVisible = true) // Not the only task on DEFAULT_DISPLAY assertThat(repo.isVisibleTask(1)).isTrue() - assertThat(repo.isOnlyVisibleNonClosingTask(1)).isFalse() + assertThat(repo.isOnlyVisibleNonClosingTask(1, DEFAULT_DISPLAY)).isFalse() // Not the only task on DEFAULT_DISPLAY assertThat(repo.isVisibleTask(2)).isTrue() - assertThat(repo.isOnlyVisibleNonClosingTask(2)).isFalse() + assertThat(repo.isOnlyVisibleNonClosingTask(2, DEFAULT_DISPLAY)).isFalse() // The only visible task on SECOND_DISPLAY assertThat(repo.isVisibleTask(3)).isTrue() - assertThat(repo.isOnlyVisibleNonClosingTask(3)).isTrue() + assertThat(repo.isOnlyVisibleNonClosingTask(3, SECOND_DISPLAY)).isTrue() // Not a visible task assertThat(repo.isVisibleTask(99)).isFalse() assertThat(repo.isOnlyVisibleNonClosingTask(99)).isFalse() @@ -343,6 +353,7 @@ class DesktopRepositoryTest : ShellTestCase() { @Test fun addListener_tasksOnDifferentDisplay_doesNotNotify() { + repo.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY) repo.updateTask(SECOND_DISPLAY, taskId = 1, isVisible = true) val listener = TestVisibilityListener() val executor = TestShellExecutor() @@ -351,7 +362,7 @@ class DesktopRepositoryTest : ShellTestCase() { assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(0) // One call as adding listener notifies it - assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(0) + assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(1) } @Test @@ -365,11 +376,14 @@ class DesktopRepositoryTest : ShellTestCase() { executor.flushAll() assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(2) - assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(2) + // 1 from registration, 2 for the updates. + assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(3) } @Test fun updateTask_visibleTask_addVisibleTaskNotifiesListenerForThatDisplay() { + repo.addDesk(displayId = 1, deskId = 1) + repo.setActiveDesk(displayId = 1, deskId = 1) val listener = TestVisibilityListener() val executor = TestShellExecutor() repo.addVisibleTasksListener(listener, executor) @@ -378,22 +392,27 @@ class DesktopRepositoryTest : ShellTestCase() { executor.flushAll() assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(1) - assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(1) + // 1 for the registration, 1 for the update. + assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(2) assertThat(listener.visibleTasksCountOnSecondaryDisplay).isEqualTo(0) - assertThat(listener.visibleChangesOnSecondaryDisplay).isEqualTo(0) + // 1 for the registration. + assertThat(listener.visibleChangesOnSecondaryDisplay).isEqualTo(1) repo.updateTask(displayId = 1, taskId = 2, isVisible = true) executor.flushAll() // Listener for secondary display is notified assertThat(listener.visibleTasksCountOnSecondaryDisplay).isEqualTo(1) - assertThat(listener.visibleChangesOnSecondaryDisplay).isEqualTo(1) + // 1 for the registration, 1 for the update. + assertThat(listener.visibleChangesOnSecondaryDisplay).isEqualTo(2) // No changes to listener for default display - assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(1) + assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(2) } @Test fun updateTask_taskOnDefaultBecomesVisibleOnSecondDisplay_listenersNotified() { + repo.addDesk(displayId = 1, deskId = 1) + repo.setActiveDesk(displayId = 1, deskId = 1) val listener = TestVisibilityListener() val executor = TestShellExecutor() repo.addVisibleTasksListener(listener, executor) @@ -406,14 +425,15 @@ class DesktopRepositoryTest : ShellTestCase() { repo.updateTask(displayId = 1, taskId = 1, isVisible = true) executor.flushAll() - // Default display should have 2 calls - // 1 - visible task added - // 2 - visible task removed - assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(2) + // Default display should have 3 calls + // 1 - listener registered + // 2 - visible task added + // 3 - visible task removed + assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(3) assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(0) - // Secondary display should have 1 call for visible task added - assertThat(listener.visibleChangesOnSecondaryDisplay).isEqualTo(1) + // Secondary display should have 2 calls for registration + visible task added + assertThat(listener.visibleChangesOnSecondaryDisplay).isEqualTo(2) assertThat(listener.visibleTasksCountOnSecondaryDisplay).isEqualTo(1) } @@ -431,13 +451,13 @@ class DesktopRepositoryTest : ShellTestCase() { repo.updateTask(DEFAULT_DISPLAY, taskId = 1, isVisible = false) executor.flushAll() - assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(3) + assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(4) repo.updateTask(DEFAULT_DISPLAY, taskId = 2, isVisible = false) executor.flushAll() assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(0) - assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(4) + assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(5) } /** @@ -458,7 +478,8 @@ class DesktopRepositoryTest : ShellTestCase() { repo.updateTask(INVALID_DISPLAY, taskId = 1, isVisible = false) executor.flushAll() - assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(3) + // 1 from registering, 1x3 for each update including the one to the invalid display. + assertThat(listener.visibleChangesOnDefaultDisplay).isEqualTo(4) assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(1) } @@ -497,6 +518,8 @@ class DesktopRepositoryTest : ShellTestCase() { @Test fun getVisibleTaskCount_multipleDisplays_returnsCorrectCount() { + repo.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY) + repo.setActiveDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY) assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0) assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(0) @@ -674,8 +697,6 @@ class DesktopRepositoryTest : ShellTestCase() { repo.removeTask(INVALID_DISPLAY, taskId = 1) - val invalidDisplayTasks = repo.getFreeformTasksInZOrder(INVALID_DISPLAY) - assertThat(invalidDisplayTasks).isEmpty() val validDisplayTasks = repo.getFreeformTasksInZOrder(DEFAULT_DISPLAY) assertThat(validDisplayTasks).isEmpty() } @@ -746,6 +767,7 @@ class DesktopRepositoryTest : ShellTestCase() { @Test fun removeTask_validDisplay_differentDisplay_doesNotRemovesTask() { + repo.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY) repo.addTask(DEFAULT_DISPLAY, taskId = 1, isVisible = true) repo.removeTask(SECOND_DISPLAY, taskId = 1) @@ -758,6 +780,7 @@ class DesktopRepositoryTest : ShellTestCase() { @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE) fun removeTask_validDisplayButDifferentDisplay_persistenceEnabled_doesNotRemoveTask() { runTest(StandardTestDispatcher()) { + repo.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY) repo.addTask(DEFAULT_DISPLAY, taskId = 1, isVisible = true) repo.removeTask(SECOND_DISPLAY, taskId = 1) @@ -784,10 +807,10 @@ class DesktopRepositoryTest : ShellTestCase() { @Test fun removeTask_removesTaskBoundsBeforeMaximize() { val taskId = 1 - repo.addTask(THIRD_DISPLAY, taskId, isVisible = true) + repo.addTask(DEFAULT_DISPLAY, taskId, isVisible = true) repo.saveBoundsBeforeMaximize(taskId, Rect(0, 0, 200, 200)) - repo.removeTask(THIRD_DISPLAY, taskId) + repo.removeTask(DEFAULT_DISPLAY, taskId) assertThat(repo.removeBoundsBeforeMaximize(taskId)).isNull() } @@ -795,16 +818,17 @@ class DesktopRepositoryTest : ShellTestCase() { @Test fun removeTask_removesTaskBoundsBeforeImmersive() { val taskId = 1 - repo.addTask(THIRD_DISPLAY, taskId, isVisible = true) + repo.addTask(DEFAULT_DISPLAY, taskId, isVisible = true) repo.saveBoundsBeforeFullImmersive(taskId, Rect(0, 0, 200, 200)) - repo.removeTask(THIRD_DISPLAY, taskId) + repo.removeTask(DEFAULT_DISPLAY, taskId) assertThat(repo.removeBoundsBeforeFullImmersive(taskId)).isNull() } @Test fun removeTask_removesActiveTask() { + repo.addDesk(THIRD_DISPLAY, THIRD_DISPLAY) val taskId = 1 val listener = TestListener() repo.addActiveTaskListener(listener) @@ -829,6 +853,7 @@ class DesktopRepositoryTest : ShellTestCase() { @Test fun removeTask_updatesTaskVisibility() { + repo.addDesk(displayId = THIRD_DISPLAY, deskId = THIRD_DISPLAY) val taskId = 1 repo.addTask(DEFAULT_DISPLAY, taskId, isVisible = true) @@ -930,8 +955,8 @@ class DesktopRepositoryTest : ShellTestCase() { @Test fun updateTask_minimizedTaskBecomesVisible_unminimizesTask() { - repo.minimizeTask(displayId = 10, taskId = 2) - repo.updateTask(displayId = 10, taskId = 2, isVisible = true) + repo.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = 2) + repo.updateTask(displayId = DEFAULT_DISPLAY, taskId = 2, isVisible = true) val isMinimizedTask = repo.isMinimizedTask(taskId = 2) @@ -1003,34 +1028,34 @@ class DesktopRepositoryTest : ShellTestCase() { fun setTaskInFullImmersiveState_savedAsInImmersiveState() { assertThat(repo.isTaskInFullImmersiveState(taskId = 1)).isFalse() - repo.setTaskInFullImmersiveState(DEFAULT_DESKTOP_ID, taskId = 1, immersive = true) + repo.setTaskInFullImmersiveState(DEFAULT_DISPLAY, taskId = 1, immersive = true) assertThat(repo.isTaskInFullImmersiveState(taskId = 1)).isTrue() } @Test fun removeTaskInFullImmersiveState_removedAsInImmersiveState() { - repo.setTaskInFullImmersiveState(DEFAULT_DESKTOP_ID, taskId = 1, immersive = true) + repo.setTaskInFullImmersiveState(DEFAULT_DISPLAY, taskId = 1, immersive = true) assertThat(repo.isTaskInFullImmersiveState(taskId = 1)).isTrue() - repo.setTaskInFullImmersiveState(DEFAULT_DESKTOP_ID, taskId = 1, immersive = false) + repo.setTaskInFullImmersiveState(DEFAULT_DISPLAY, taskId = 1, immersive = false) assertThat(repo.isTaskInFullImmersiveState(taskId = 1)).isFalse() } @Test fun removeTaskInFullImmersiveState_otherWasImmersive_otherRemainsImmersive() { - repo.setTaskInFullImmersiveState(DEFAULT_DESKTOP_ID, taskId = 1, immersive = true) + repo.setTaskInFullImmersiveState(DEFAULT_DISPLAY, taskId = 1, immersive = true) - repo.setTaskInFullImmersiveState(DEFAULT_DESKTOP_ID, taskId = 2, immersive = false) + repo.setTaskInFullImmersiveState(DEFAULT_DISPLAY, taskId = 2, immersive = false) assertThat(repo.isTaskInFullImmersiveState(taskId = 1)).isTrue() } @Test fun setTaskInFullImmersiveState_sameDisplay_overridesExistingFullImmersiveTask() { - repo.setTaskInFullImmersiveState(DEFAULT_DESKTOP_ID, taskId = 1, immersive = true) - repo.setTaskInFullImmersiveState(DEFAULT_DESKTOP_ID, taskId = 2, immersive = true) + repo.setTaskInFullImmersiveState(DEFAULT_DISPLAY, taskId = 1, immersive = true) + repo.setTaskInFullImmersiveState(DEFAULT_DISPLAY, taskId = 2, immersive = true) assertThat(repo.isTaskInFullImmersiveState(taskId = 1)).isFalse() assertThat(repo.isTaskInFullImmersiveState(taskId = 2)).isTrue() @@ -1038,8 +1063,10 @@ class DesktopRepositoryTest : ShellTestCase() { @Test fun setTaskInFullImmersiveState_differentDisplay_bothAreImmersive() { - repo.setTaskInFullImmersiveState(DEFAULT_DESKTOP_ID, taskId = 1, immersive = true) - repo.setTaskInFullImmersiveState(DEFAULT_DESKTOP_ID + 1, taskId = 2, immersive = true) + repo.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY) + repo.setActiveDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY) + repo.setTaskInFullImmersiveState(DEFAULT_DISPLAY, taskId = 1, immersive = true) + repo.setTaskInFullImmersiveState(SECOND_DISPLAY, taskId = 2, immersive = true) assertThat(repo.isTaskInFullImmersiveState(taskId = 1)).isTrue() assertThat(repo.isTaskInFullImmersiveState(taskId = 2)).isTrue() @@ -1061,11 +1088,13 @@ class DesktopRepositoryTest : ShellTestCase() { @Test fun getTaskInFullImmersiveState_byDisplay() { + repo.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY) + repo.setActiveDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY) repo.setTaskInFullImmersiveState(DEFAULT_DESKTOP_ID, taskId = 1, immersive = true) - repo.setTaskInFullImmersiveState(DEFAULT_DESKTOP_ID + 1, taskId = 2, immersive = true) + repo.setTaskInFullImmersiveState(SECOND_DISPLAY, taskId = 2, immersive = true) assertThat(repo.getTaskInFullImmersiveState(DEFAULT_DESKTOP_ID)).isEqualTo(1) - assertThat(repo.getTaskInFullImmersiveState(DEFAULT_DESKTOP_ID + 1)).isEqualTo(2) + assertThat(repo.getTaskInFullImmersiveState(SECOND_DISPLAY)).isEqualTo(2) } @Test @@ -1089,11 +1118,13 @@ class DesktopRepositoryTest : ShellTestCase() { @Test fun setTaskInPip_multipleDisplays_bothAreInPip() { + repo.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY) + repo.setActiveDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY) repo.setTaskInPip(DEFAULT_DESKTOP_ID, taskId = 1, enterPip = true) - repo.setTaskInPip(DEFAULT_DESKTOP_ID + 1, taskId = 2, enterPip = true) + repo.setTaskInPip(SECOND_DISPLAY, taskId = 2, enterPip = true) assertThat(repo.isTaskMinimizedPipInDisplay(DEFAULT_DESKTOP_ID, taskId = 1)).isTrue() - assertThat(repo.isTaskMinimizedPipInDisplay(DEFAULT_DESKTOP_ID + 1, taskId = 2)).isTrue() + assertThat(repo.isTaskMinimizedPipInDisplay(SECOND_DISPLAY, taskId = 2)).isTrue() } @Test @@ -1129,6 +1160,14 @@ class DesktopRepositoryTest : ShellTestCase() { assertThat(repo.shouldDesktopBeActiveForPip(DEFAULT_DESKTOP_ID)).isFalse() } + @Test + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun addTask_deskDoesNotExists_createsDesk() { + repo.addTask(displayId = 999, taskId = 6, isVisible = true) + + assertThat(repo.getActiveTaskIdsInDesk(999)).contains(6) + } + class TestListener : DesktopRepository.ActiveTasksListener { var activeChangesOnDefaultDisplay = 0 var activeChangesOnSecondaryDisplay = 0 @@ -1169,5 +1208,10 @@ class DesktopRepositoryTest : ShellTestCase() { const val THIRD_DISPLAY = 345 private const val DEFAULT_USER_ID = 1000 private const val DEFAULT_DESKTOP_ID = 0 + + @JvmStatic + @Parameters(name = "{0}") + fun getParams(): List<FlagsParameterization> = + FlagsParameterization.allCombinationsOf(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) } } 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 c7c0dfc5be6d..12c7ff61399f 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 @@ -144,6 +144,16 @@ class DesktopTaskChangeListenerTest : ShellTestCase() { } @Test + fun onTaskMovingToFront_freeformTaskOutsideDesktop_addsTaskToRepo() { + val task = createFullscreenTask().apply { isVisible = true } + whenever(desktopUserRepositories.current.isActiveTask(task.taskId)).thenReturn(true) + + desktopTaskChangeListener.onTaskMovingToFront(task) + + verify(desktopUserRepositories.current).addTask(task.displayId, task.taskId, task.isVisible) + } + + @Test @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) fun onTaskClosing_backNavEnabled_nonClosingTask_minimizesTaskInRepo() { val task = createFreeformTask().apply { isVisible = true } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt index db19017ef87a..40c0e3610da2 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 @@ -48,8 +48,8 @@ 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.FlagsParameterization import android.platform.test.flag.junit.SetFlagsRule -import android.testing.AndroidTestingRunner import android.view.Display.DEFAULT_DISPLAY import android.view.DragEvent import android.view.Gravity @@ -100,6 +100,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.common.UserProfileContexts 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.MinimizeReason @@ -117,6 +118,7 @@ import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler.FULLSCR import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider import com.android.wm.shell.desktopmode.minimize.DesktopWindowLimitRemoteHandler +import com.android.wm.shell.desktopmode.multidesks.DesksOrganizer import com.android.wm.shell.desktopmode.persistence.Desktop import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer @@ -127,6 +129,7 @@ import com.android.wm.shell.recents.RecentsTransitionHandler import com.android.wm.shell.recents.RecentsTransitionStateListener import com.android.wm.shell.recents.RecentsTransitionStateListener.TRANSITION_STATE_ANIMATING import com.android.wm.shell.recents.RecentsTransitionStateListener.TRANSITION_STATE_REQUESTED +import com.android.wm.shell.shared.desktopmode.DesktopModeCompatPolicy import com.android.wm.shell.shared.desktopmode.DesktopModeStatus import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.UNKNOWN import com.android.wm.shell.shared.split.SplitScreenConstants @@ -184,6 +187,8 @@ import org.mockito.kotlin.capture import org.mockito.kotlin.eq import org.mockito.kotlin.whenever import org.mockito.quality.Strictness +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters /** * Test class for {@link DesktopTasksController} @@ -191,12 +196,12 @@ import org.mockito.quality.Strictness * Usage: atest WMShellUnitTests:DesktopTasksControllerTest */ @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(ParameterizedAndroidJunit4::class) @ExperimentalCoroutinesApi @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) -class DesktopTasksControllerTest : ShellTestCase() { +class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() { - @JvmField @Rule val setFlagsRule = SetFlagsRule() + @JvmField @Rule val setFlagsRule = SetFlagsRule(flags) @Mock lateinit var testExecutor: ShellExecutor @Mock lateinit var shellCommandHandler: ShellCommandHandler @@ -245,6 +250,10 @@ class DesktopTasksControllerTest : ShellTestCase() { @Mock private lateinit var desktopWallpaperActivityTokenProvider: DesktopWallpaperActivityTokenProvider + @Mock + private lateinit var overviewToDesktopTransitionObserver: OverviewToDesktopTransitionObserver + @Mock private lateinit var desksOrganizer: DesksOrganizer + @Mock private lateinit var userProfileContexts: UserProfileContexts private lateinit var controller: DesktopTasksController private lateinit var shellInit: ShellInit @@ -253,6 +262,7 @@ class DesktopTasksControllerTest : ShellTestCase() { private lateinit var desktopTasksLimiter: DesktopTasksLimiter private lateinit var recentsTransitionStateListener: RecentsTransitionStateListener private lateinit var testScope: CoroutineScope + private lateinit var desktopModeCompatPolicy: DesktopModeCompatPolicy private val shellExecutor = TestShellExecutor() @@ -303,6 +313,7 @@ class DesktopTasksControllerTest : ShellTestCase() { mContext, mockHandler, ) + desktopModeCompatPolicy = DesktopModeCompatPolicy(context) whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenAnswer { runningTasks } whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() } @@ -339,6 +350,7 @@ class DesktopTasksControllerTest : ShellTestCase() { ) .thenReturn(ExitResult.NoExit) whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(wallpaperToken) + whenever(userProfileContexts[anyInt()]).thenReturn(context) controller = createController() controller.setSplitScreenController(splitScreenController) @@ -356,6 +368,8 @@ class DesktopTasksControllerTest : ShellTestCase() { assumeTrue(ENABLE_SHELL_TRANSITIONS) taskRepository = userRepositories.current + taskRepository.addDesk(displayId = DEFAULT_DISPLAY, deskId = DEFAULT_DISPLAY) + taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = DEFAULT_DISPLAY) } private fun createController() = @@ -392,6 +406,10 @@ class DesktopTasksControllerTest : ShellTestCase() { desktopTilingDecorViewModel, desktopWallpaperActivityTokenProvider, Optional.of(bubbleController), + overviewToDesktopTransitionObserver, + desksOrganizer, + userProfileContexts, + desktopModeCompatPolicy, ) @After @@ -610,7 +628,12 @@ class DesktopTasksControllerTest : ShellTestCase() { Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY, ) + @DisableFlags( + /** TODO: b/362720497 - re-enable when activation is implemented. */ + Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND + ) fun showDesktopApps_onSecondaryDisplay_desktopWallpaperEnabled_perDisplayWallpaperEnabled_shouldShowWallpaper() { + taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY) val homeTask = setUpHomeTask(SECOND_DISPLAY) val task1 = setUpFreeformTask(SECOND_DISPLAY) val task2 = setUpFreeformTask(SECOND_DISPLAY) @@ -631,8 +654,13 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - @DisableFlags(Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY) + @DisableFlags( + Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY, + /** TODO: b/362720497 - re-enable when activation is implemented. */ + Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND, + ) fun showDesktopApps_onSecondaryDisplay_desktopWallpaperEnabled_shouldNotShowWallpaper() { + taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY) val homeTask = setUpHomeTask(SECOND_DISPLAY) val task1 = setUpFreeformTask(SECOND_DISPLAY) val task2 = setUpFreeformTask(SECOND_DISPLAY) @@ -671,8 +699,13 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test - @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + @DisableFlags( + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, + /** TODO: b/362720497 - re-enable when activation is implemented. */ + Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND, + ) fun showDesktopApps_onSecondaryDisplay_desktopWallpaperDisabled_shouldNotMoveLauncher() { + taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY) val homeTask = setUpHomeTask(SECOND_DISPLAY) val task1 = setUpFreeformTask(SECOND_DISPLAY) val task2 = setUpFreeformTask(SECOND_DISPLAY) @@ -774,6 +807,7 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) fun showDesktopApps_twoDisplays_bringsToFrontOnlyOneDisplay_desktopWallpaperDisabled() { + taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY) val homeTaskDefaultDisplay = setUpHomeTask(DEFAULT_DISPLAY) val taskDefaultDisplay = setUpFreeformTask(DEFAULT_DISPLAY) setUpHomeTask(SECOND_DISPLAY) @@ -794,6 +828,7 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) fun showDesktopApps_twoDisplays_bringsToFrontOnlyOneDisplay_desktopWallpaperEnabled() { + taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY) val homeTaskDefaultDisplay = setUpHomeTask(DEFAULT_DISPLAY) val taskDefaultDisplay = setUpFreeformTask(DEFAULT_DISPLAY) setUpHomeTask(SECOND_DISPLAY) @@ -880,6 +915,8 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test fun visibleTaskCount_twoTasksVisibleOnDifferentDisplays_returnsOne() { + taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY) + taskRepository.setActiveDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY) setUpHomeTask() setUpFreeformTask(DEFAULT_DISPLAY).also(::markTaskVisible) setUpFreeformTask(SECOND_DISPLAY).also(::markTaskVisible) @@ -1473,6 +1510,7 @@ class DesktopTasksControllerTest : ShellTestCase() { val fullscreenTaskDefault = setUpFullscreenTask(displayId = DEFAULT_DISPLAY) markTaskHidden(freeformTaskDefault) + taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY) val homeTaskSecond = setUpHomeTask(displayId = SECOND_DISPLAY) val freeformTaskSecond = setUpFreeformTask(displayId = SECOND_DISPLAY) markTaskHidden(freeformTaskSecond) @@ -1670,6 +1708,7 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test fun moveToFullscreen_secondDisplayTaskHasFreeform_secondDisplayNotAffected() { val taskDefaultDisplay = setUpFreeformTask(displayId = DEFAULT_DISPLAY) + taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY) val taskSecondDisplay = setUpFreeformTask(displayId = SECOND_DISPLAY) controller.moveToFullscreen(taskDefaultDisplay.taskId, transitionSource = UNKNOWN) @@ -1850,6 +1889,7 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test fun moveToNextDisplay_moveFromFirstToSecondDisplay() { // Set up two display ids + taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY) whenever(rootTaskDisplayAreaOrganizer.displayIds) .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY)) // Create a mock for the target display area: second display @@ -1879,6 +1919,7 @@ class DesktopTasksControllerTest : ShellTestCase() { whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)) .thenReturn(defaultDisplayArea) + taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY) val task = setUpFreeformTask(displayId = SECOND_DISPLAY) controller.moveToNextDisplay(task.taskId) @@ -1898,6 +1939,7 @@ class DesktopTasksControllerTest : ShellTestCase() { ) fun moveToNextDisplay_wallpaperOnSystemUser_reorderWallpaperToBack() { // Set up two display ids + taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY) whenever(rootTaskDisplayAreaOrganizer.displayIds) .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY)) // Create a mock for the target display area: second display @@ -1922,6 +1964,7 @@ class DesktopTasksControllerTest : ShellTestCase() { @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER) fun moveToNextDisplay_wallpaperNotOnSystemUser_removeWallpaper() { // Set up two display ids + taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY) whenever(rootTaskDisplayAreaOrganizer.displayIds) .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY)) // Create a mock for the target display area: second display @@ -2046,6 +2089,7 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test @EnableFlags(FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT) fun moveToNextDisplay_defaultBoundsWhenDestinationTooSmall() { + taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY) // Set up two display ids whenever(rootTaskDisplayAreaOrganizer.displayIds) .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY)) @@ -2087,6 +2131,7 @@ class DesktopTasksControllerTest : ShellTestCase() { FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT, ) fun moveToNextDisplay_destinationGainGlobalFocus() { + taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY) // Set up two display ids whenever(rootTaskDisplayAreaOrganizer.displayIds) .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY)) @@ -2974,6 +3019,27 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test + fun handleRequest_systemUIActivityWithDisplay_returnSwitchToFullscreenWCT_enforcedDesktop() { + whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true) + val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!! + tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM + // Set task as systemUI package + val systemUIPackageName = + context.resources.getString(com.android.internal.R.string.config_systemUi) + val baseComponent = ComponentName(systemUIPackageName, /* cls= */ "") + val task = + createFreeformTask().apply { + baseActivity = baseComponent + isTopActivityNoDisplay = false + } + + assertThat(controller.isDesktopModeShowing(DEFAULT_DISPLAY)).isFalse() + val result = controller.handleRequest(Binder(), createTransition(task)) + assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode) + .isEqualTo(WINDOWING_MODE_FULLSCREEN) + } + + @Test @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) fun handleRequest_backTransition_singleTaskNoToken_noWallpaper_doesNotHandle() { val task = setUpFreeformTask() @@ -3155,6 +3221,8 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) fun handleRequest_closeTransition_singleTaskNoToken_secondaryDisplay_launchesHome() { + taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY) + taskRepository.setActiveDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY) val task = setUpFreeformTask(displayId = SECOND_DISPLAY) whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null) @@ -4930,7 +4998,7 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) fun shouldPlayDesktopAnimation_notShowingDesktop_doesNotPlay() { - val triggerTask = setUpFullscreenTask(displayId = 5) + val triggerTask = setUpFullscreenTask(displayId = DEFAULT_DISPLAY) taskRepository.setTaskInFullImmersiveState( displayId = triggerTask.displayId, taskId = triggerTask.taskId, @@ -4948,7 +5016,7 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) fun shouldPlayDesktopAnimation_notOpening_doesNotPlay() { - val triggerTask = setUpFreeformTask(displayId = 5) + val triggerTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY) taskRepository.setTaskInFullImmersiveState( displayId = triggerTask.displayId, taskId = triggerTask.taskId, @@ -4966,7 +5034,7 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) fun shouldPlayDesktopAnimation_notImmersive_doesNotPlay() { - val triggerTask = setUpFreeformTask(displayId = 5) + val triggerTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY) taskRepository.setTaskInFullImmersiveState( displayId = triggerTask.displayId, taskId = triggerTask.taskId, @@ -4985,8 +5053,8 @@ class DesktopTasksControllerTest : ShellTestCase() { @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) fun shouldPlayDesktopAnimation_fullscreenEntersDesktop_plays() { // At least one freeform task to be in a desktop. - val existingTask = setUpFreeformTask(displayId = 5) - val triggerTask = setUpFullscreenTask(displayId = 5) + val existingTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY) + val triggerTask = setUpFullscreenTask(displayId = DEFAULT_DISPLAY) assertThat(controller.isDesktopModeShowing(triggerTask.displayId)).isTrue() taskRepository.setTaskInFullImmersiveState( displayId = existingTask.displayId, @@ -5005,7 +5073,7 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) fun shouldPlayDesktopAnimation_fullscreenStaysFullscreen_doesNotPlay() { - val triggerTask = setUpFullscreenTask(displayId = 5) + val triggerTask = setUpFullscreenTask(displayId = DEFAULT_DISPLAY) assertThat(controller.isDesktopModeShowing(triggerTask.displayId)).isFalse() assertThat( @@ -5020,8 +5088,8 @@ class DesktopTasksControllerTest : ShellTestCase() { @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) fun shouldPlayDesktopAnimation_freeformStaysInDesktop_plays() { // At least one freeform task to be in a desktop. - val existingTask = setUpFreeformTask(displayId = 5) - val triggerTask = setUpFreeformTask(displayId = 5, active = false) + val existingTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY) + val triggerTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY, active = false) assertThat(controller.isDesktopModeShowing(triggerTask.displayId)).isTrue() taskRepository.setTaskInFullImmersiveState( displayId = existingTask.displayId, @@ -5040,7 +5108,7 @@ class DesktopTasksControllerTest : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) fun shouldPlayDesktopAnimation_freeformExitsDesktop_doesNotPlay() { - val triggerTask = setUpFreeformTask(displayId = 5, active = false) + val triggerTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY, active = false) assertThat(controller.isDesktopModeShowing(triggerTask.displayId)).isFalse() assertThat( @@ -5051,6 +5119,19 @@ class DesktopTasksControllerTest : ShellTestCase() { .isFalse() } + @Test + @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun testCreateDesk() { + val currentDeskCount = taskRepository.getNumberOfDesks(DEFAULT_DISPLAY) + whenever(desksOrganizer.createDesk(eq(DEFAULT_DISPLAY), any())).thenAnswer { invocation -> + (invocation.arguments[1] as DesksOrganizer.OnCreateCallback).onCreated(deskId = 5) + } + + controller.createDesk(DEFAULT_DISPLAY) + + assertThat(taskRepository.getNumberOfDesks(DEFAULT_DISPLAY)).isEqualTo(currentDeskCount + 1) + } + private class RunOnStartTransitionCallback : ((IBinder) -> Unit) { var invocations = 0 private set @@ -5095,7 +5176,8 @@ class DesktopTasksControllerTest : ShellTestCase() { whenever(mockDragEvent.dragSurface).thenReturn(dragSurface) whenever(mockDragEvent.x).thenReturn(inputCoordinate.x) whenever(mockDragEvent.y).thenReturn(inputCoordinate.y) - whenever(multiInstanceHelper.supportsMultiInstanceSplit(anyOrNull())).thenReturn(true) + whenever(multiInstanceHelper.supportsMultiInstanceSplit(anyOrNull(), anyInt())) + .thenReturn(true) whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) doReturn(indicatorType) .whenever(spyController) @@ -5109,6 +5191,7 @@ class DesktopTasksControllerTest : ShellTestCase() { spyController.onUnhandledDrag( mockPendingIntent, + context.userId, mockDragEvent, mockCallback as Consumer<Boolean>, ) @@ -5384,6 +5467,11 @@ class DesktopTasksControllerTest : ShellTestCase() { val STABLE_BOUNDS = Rect(0, 0, 1000, 1000) const val MAX_TASK_LIMIT = 6 private const val TASKBAR_FRAME_HEIGHT = 200 + + @JvmStatic + @Parameters(name = "{0}") + fun getParams(): List<FlagsParameterization> = + FlagsParameterization.allCombinationsOf(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) } } 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 e85901bbd9d4..554b09f130bd 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 @@ -180,6 +180,8 @@ class DesktopTasksLimiterTest : ShellTestCase() { @Test fun addPendingMinimizeTransition_taskIsNotMinimized() { + desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0) + desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0) val task = setUpFreeformTask() markTaskHidden(task) @@ -190,6 +192,8 @@ class DesktopTasksLimiterTest : ShellTestCase() { @Test fun onTransitionReady_noPendingTransition_taskIsNotMinimized() { + desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0) + desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0) val task = setUpFreeformTask() markTaskHidden(task) @@ -203,6 +207,8 @@ class DesktopTasksLimiterTest : ShellTestCase() { @Test fun onTransitionReady_differentPendingTransition_taskIsNotMinimized() { + desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0) + desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0) val pendingTransition = Binder() val taskTransition = Binder() val task = setUpFreeformTask() @@ -219,6 +225,8 @@ class DesktopTasksLimiterTest : ShellTestCase() { @Test fun onTransitionReady_pendingTransition_noTaskChange_taskVisible_taskIsNotMinimized() { + desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0) + desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0) val transition = Binder() val task = setUpFreeformTask() markTaskVisible(task) @@ -232,6 +240,8 @@ class DesktopTasksLimiterTest : ShellTestCase() { @Test fun onTransitionReady_pendingTransition_noTaskChange_taskInvisible_taskIsMinimized() { val transition = Binder() + desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0) + desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0) val task = setUpFreeformTask() markTaskHidden(task) addPendingMinimizeChange(transition, taskId = task.taskId) @@ -243,6 +253,8 @@ class DesktopTasksLimiterTest : ShellTestCase() { @Test fun onTransitionReady_pendingTransition_changeTaskToBack_taskIsMinimized() { + desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0) + desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0) val transition = Binder() val task = setUpFreeformTask() addPendingMinimizeChange(transition, taskId = task.taskId) @@ -257,6 +269,8 @@ class DesktopTasksLimiterTest : ShellTestCase() { @Test fun onTransitionReady_pendingTransition_changeTaskToBack_boundsSaved() { + desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0) + desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0) val bounds = Rect(0, 0, 200, 200) val transition = Binder() val task = setUpFreeformTask() @@ -280,6 +294,8 @@ class DesktopTasksLimiterTest : ShellTestCase() { @Test fun onTransitionReady_transitionMergedFromPending_taskIsMinimized() { + desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0) + desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0) val mergedTransition = Binder() val newTransition = Binder() val task = setUpFreeformTask() @@ -302,6 +318,8 @@ class DesktopTasksLimiterTest : ShellTestCase() { @Test @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) fun removeLeftoverMinimizedTasks_activeNonMinimizedTasksStillAround_doesNothing() { + desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0) + desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0) desktopTaskRepo.addTask(displayId = DEFAULT_DISPLAY, taskId = 1, isVisible = true) desktopTaskRepo.addTask(displayId = DEFAULT_DISPLAY, taskId = 2, isVisible = true) desktopTaskRepo.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = 2) @@ -318,6 +336,8 @@ class DesktopTasksLimiterTest : ShellTestCase() { @Test @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) fun removeLeftoverMinimizedTasks_noMinimizedTasks_doesNothing() { + desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0) + desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0) val wct = WindowContainerTransaction() desktopTasksLimiter.leftoverMinimizedTasksRemover.removeLeftoverMinimizedTasks( DEFAULT_DISPLAY, @@ -330,6 +350,8 @@ class DesktopTasksLimiterTest : ShellTestCase() { @Test @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) fun removeLeftoverMinimizedTasks_onlyMinimizedTasksLeft_removesAllMinimizedTasks() { + desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0) + desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0) val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) desktopTaskRepo.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task1.taskId) @@ -351,6 +373,8 @@ class DesktopTasksLimiterTest : ShellTestCase() { @Test @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) fun removeLeftoverMinimizedTasks_onlyMinimizedTasksLeft_backNavEnabled_doesNothing() { + desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0) + desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0) val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY) desktopTaskRepo.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task1.taskId) @@ -364,6 +388,8 @@ class DesktopTasksLimiterTest : ShellTestCase() { @Test fun addAndGetMinimizeTaskChanges_tasksWithinLimit_noTaskMinimized() { + desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0) + desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0) (1..<MAX_TASK_LIMIT).forEach { _ -> setUpFreeformTask() } val wct = WindowContainerTransaction() @@ -380,6 +406,8 @@ class DesktopTasksLimiterTest : ShellTestCase() { @Test fun addAndGetMinimizeTaskChanges_tasksAboveLimit_backTaskMinimized() { + desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0) + desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0) // The following list will be ordered bottom -> top, as the last task is moved to top last. val tasks = (1..MAX_TASK_LIMIT).map { setUpFreeformTask() } @@ -399,6 +427,8 @@ class DesktopTasksLimiterTest : ShellTestCase() { @Test fun addAndGetMinimizeTaskChanges_nonMinimizedTasksWithinLimit_noTaskMinimized() { + desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0) + desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0) val tasks = (1..MAX_TASK_LIMIT).map { setUpFreeformTask() } desktopTaskRepo.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = tasks[0].taskId) @@ -416,6 +446,8 @@ class DesktopTasksLimiterTest : ShellTestCase() { @Test fun getTaskToMinimize_tasksWithinLimit_returnsNull() { + desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0) + desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0) val tasks = (1..MAX_TASK_LIMIT).map { setUpFreeformTask() } val minimizedTask = @@ -426,6 +458,8 @@ class DesktopTasksLimiterTest : ShellTestCase() { @Test fun getTaskToMinimize_tasksAboveLimit_returnsBackTask() { + desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0) + desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0) val tasks = (1..MAX_TASK_LIMIT + 1).map { setUpFreeformTask() } val minimizedTask = @@ -437,6 +471,8 @@ class DesktopTasksLimiterTest : ShellTestCase() { @Test fun getTaskToMinimize_tasksAboveLimit_otherLimit_returnsBackTask() { + desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0) + desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0) desktopTasksLimiter = DesktopTasksLimiter( transitions, @@ -458,6 +494,8 @@ class DesktopTasksLimiterTest : ShellTestCase() { @Test fun getTaskToMinimize_withNewTask_tasksAboveLimit_returnsBackTask() { + desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0) + desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0) val tasks = (1..MAX_TASK_LIMIT).map { setUpFreeformTask() } val minimizedTask = @@ -472,6 +510,8 @@ class DesktopTasksLimiterTest : ShellTestCase() { @Test fun getTaskToMinimize_tasksAtLimit_newIntentReturnsBackTask() { + desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0) + desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0) val tasks = (1..MAX_TASK_LIMIT).map { setUpFreeformTask() } val minimizedTask = desktopTasksLimiter.getTaskIdToMinimize( @@ -486,6 +526,8 @@ class DesktopTasksLimiterTest : ShellTestCase() { @Test fun minimizeTransitionReadyAndFinished_logsJankInstrumentationBeginAndEnd() { + desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0) + desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0) (1..<MAX_TASK_LIMIT).forEach { _ -> setUpFreeformTask() } val transition = Binder() val task = setUpFreeformTask() @@ -510,6 +552,8 @@ class DesktopTasksLimiterTest : ShellTestCase() { @Test fun minimizeTransitionReadyAndAborted_logsJankInstrumentationBeginAndCancel() { + desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0) + desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0) (1..<MAX_TASK_LIMIT).forEach { _ -> setUpFreeformTask() } val transition = Binder() val task = setUpFreeformTask() @@ -534,6 +578,8 @@ class DesktopTasksLimiterTest : ShellTestCase() { @Test fun minimizeTransitionReadyAndMerged_logsJankInstrumentationBeginAndEnd() { + desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0) + desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0) (1..<MAX_TASK_LIMIT).forEach { _ -> setUpFreeformTask() } val mergedTransition = Binder() val newTransition = Binder() @@ -566,6 +612,8 @@ class DesktopTasksLimiterTest : ShellTestCase() { @Test fun getMinimizingTask_pendingTaskTransition_returnsTask() { + desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0) + desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0) val transition = Binder() val task = setUpFreeformTask() addPendingMinimizeChange( @@ -582,6 +630,8 @@ class DesktopTasksLimiterTest : ShellTestCase() { @Test fun getMinimizingTask_activeTaskTransition_returnsTask() { + desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0) + desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0) val transition = Binder() val task = setUpFreeformTask() addPendingMinimizeChange( @@ -613,6 +663,8 @@ class DesktopTasksLimiterTest : ShellTestCase() { @Test fun getUnminimizingTask_pendingTaskTransition_returnsTask() { + desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0) + desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0) val transition = Binder() val task = setUpFreeformTask() addPendingUnminimizeChange( @@ -632,6 +684,8 @@ class DesktopTasksLimiterTest : ShellTestCase() { @Test fun getUnminimizingTask_activeTaskTransition_returnsTask() { + desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0) + desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0) val transition = Binder() val task = setUpFreeformTask() addPendingMinimizeChange( 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 aee8821a63f6..8b6cafb10df4 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 @@ -35,6 +35,7 @@ object DesktopTestHelpers { ): RunningTaskInfo = TestRunningTaskInfoBuilder() .setDisplayId(displayId) + .setParentTaskId(displayId) .setToken(MockToken().token()) .setActivityType(ACTIVITY_TYPE_STANDARD) .setWindowingMode(WINDOWING_MODE_FREEFORM) @@ -73,10 +74,14 @@ object DesktopTestHelpers { .setLastActiveTime(100) .build() + /** + * Create a new System Modal task builder, i.e. a builder for a task with only transparent + * activities. + */ + fun createSystemModalTaskBuilder(displayId: Int = DEFAULT_DISPLAY): TestRunningTaskInfoBuilder = + createFullscreenTaskBuilder(displayId).setActivityStackTransparent(true).setNumActivities(1) + /** Create a new System Modal task, i.e. a task with only transparent activities. */ fun createSystemModalTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo = - createFullscreenTaskBuilder(displayId) - .setActivityStackTransparent(true) - .setNumActivities(1) - .build() + createSystemModalTaskBuilder(displayId).build() } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/OverviewToDesktopTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/OverviewToDesktopTransitionObserverTest.kt new file mode 100644 index 000000000000..490c42f980e3 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/OverviewToDesktopTransitionObserverTest.kt @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.desktopmode + +import android.os.Binder +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.wm.shell.ShellTestCase +import com.android.wm.shell.common.ShellExecutor +import com.android.wm.shell.sysui.ShellInit +import com.android.wm.shell.transition.Transitions +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.spy +import org.mockito.Mockito.verify +import org.mockito.kotlin.mock + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class OverviewToDesktopTransitionObserverTest : ShellTestCase() { + + @Mock private lateinit var transitions: Transitions + + @Mock private lateinit var moveToDesktopCallback: IMoveToDesktopCallback + + private val testExecutor = mock<ShellExecutor>() + private lateinit var shellInit: ShellInit + private lateinit var transitionObserver: OverviewToDesktopTransitionObserver + private val token = Binder() + + @Before + fun setup() { + shellInit = spy(ShellInit(testExecutor)) + transitionObserver = OverviewToDesktopTransitionObserver(transitions, shellInit) + } + + @Test + fun moveToDesktop_onTransitionEnd_invokesCallback() { + transitionObserver.addPendingOverviewTransition(token, moveToDesktopCallback) + + transitionObserver.onTransitionFinished(token, false) + + verify(moveToDesktopCallback).onTaskMovedToDesktop() + } +} 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 1569f9dc9b10..dfb1b0c8c642 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 @@ -16,6 +16,7 @@ package com.android.wm.shell.desktopmode.compatui +import android.content.Intent import android.os.Binder import android.testing.AndroidTestingRunner import android.view.SurfaceControl @@ -29,7 +30,10 @@ 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.DesktopTestHelpers.createSystemModalTaskBuilder import com.android.wm.shell.desktopmode.DesktopUserRepositories +import com.android.wm.shell.desktopmode.DesktopWallpaperActivity +import com.android.wm.shell.shared.desktopmode.DesktopModeCompatPolicy import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.TransitionInfoBuilder import com.android.wm.shell.transition.Transitions @@ -60,12 +64,14 @@ class SystemModalsTransitionHandlerTest : ShellTestCase() { private val finishT = mock<SurfaceControl.Transaction>() private lateinit var transitionHandler: SystemModalsTransitionHandler + private lateinit var desktopModeCompatPolicy: DesktopModeCompatPolicy @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) + desktopModeCompatPolicy = DesktopModeCompatPolicy(context) transitionHandler = createTransitionHandler() } @@ -77,6 +83,7 @@ class SystemModalsTransitionHandlerTest : ShellTestCase() { shellInit, transitions, desktopUserRepositories, + desktopModeCompatPolicy, ) @Test @@ -116,6 +123,19 @@ class SystemModalsTransitionHandlerTest : ShellTestCase() { } @Test + fun startAnimation_launchingWallpaperTask_doesNotAnimate() { + val wallpaperTask = + createSystemModalTaskBuilder().setBaseIntent(createWallpaperIntent()).build() + val info = + TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_OPEN, wallpaperTask).build() + + assertThat(transitionHandler.startAnimation(Binder(), info, startT, finishT) {}).isFalse() + } + + private fun createWallpaperIntent() = + Intent().apply { setComponent(DesktopWallpaperActivity.wallpaperActivityComponent) } + + @Test fun startAnimation_launchingFullscreenTask_doesNotAnimate() { val info = TransitionInfoBuilder(TRANSIT_OPEN) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt index 5475032f35a9..493a8c83c48e 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt @@ -29,7 +29,6 @@ import com.android.wm.shell.ShellTestCase import com.android.wm.shell.desktopmode.CaptionState import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository import com.android.wm.shell.desktopmode.education.AppHandleEducationController.Companion.APP_HANDLE_EDUCATION_DELAY_MILLIS -import com.android.wm.shell.desktopmode.education.AppHandleEducationController.Companion.APP_HANDLE_EDUCATION_TIMEOUT_MILLIS import com.android.wm.shell.desktopmode.education.data.AppHandleEducationDatastoreRepository import com.android.wm.shell.shared.desktopmode.DesktopModeStatus import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource @@ -47,7 +46,6 @@ import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.setMain import org.junit.Before -import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -113,10 +111,10 @@ class AppHandleEducationControllerTest : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) - fun init_appHandleVisible_shouldCallShowEducationTooltip() = + fun init_appHandleVisible_shouldCallShowEducationTooltipAndMarkAsViewed() = testScope.runTest { // App handle is visible. Should show education tooltip. - setShouldShowAppHandleEducation(true) + setShouldShowDesktopModeEducation(true) // Simulate app handle visible. testCaptionStateFlow.value = createAppHandleState() @@ -124,6 +122,38 @@ class AppHandleEducationControllerTest : ShellTestCase() { waitForBufferDelay() verify(mockTooltipController, times(1)).showEducationTooltip(any(), any()) + verify(mockDataStoreRepository, times(1)) + .updateAppHandleHintViewedTimestampMillis(eq(true)) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) + fun init_appHandleVisibleAndMenuExpanded_shouldCallShowEducationTooltipAndMarkAsViewed() = + testScope.runTest { + setShouldShowDesktopModeEducation(true) + + // Simulate app handle visible and handle menu is expanded. + testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = true) + waitForBufferDelay() + + verify(mockTooltipController, times(1)).showEducationTooltip(any(), any()) + verify(mockDataStoreRepository, times(1)) + .updateEnterDesktopModeHintViewedTimestampMillis(eq(true)) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) + fun init_appHeaderVisible_shouldCallShowEducationTooltipAndMarkAsViewed() = + testScope.runTest { + setShouldShowDesktopModeEducation(true) + + // Simulate app header visible. + testCaptionStateFlow.value = createAppHeaderState() + waitForBufferDelay() + + verify(mockTooltipController, times(1)).showEducationTooltip(any(), any()) + verify(mockDataStoreRepository, times(1)) + .updateExitDesktopModeHintViewedTimestampMillis(eq(true)) } @Test @@ -133,7 +163,7 @@ class AppHandleEducationControllerTest : ShellTestCase() { // App handle visible but education aconfig flag disabled, should not show education // tooltip. whenever(DesktopModeStatus.canEnterDesktopMode(any())).thenReturn(false) - setShouldShowAppHandleEducation(true) + setShouldShowDesktopModeEducation(true) // Simulate app handle visible. testCaptionStateFlow.value = createAppHandleState() @@ -145,12 +175,11 @@ class AppHandleEducationControllerTest : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) - fun init_shouldShowAppHandleEducationReturnsFalse_shouldNotCallShowEducationTooltip() = + fun init_shouldShowDesktopModeEducationReturnsFalse_shouldNotCallShowEducationTooltip() = testScope.runTest { - // App handle is visible but [shouldShowAppHandleEducation] api returns false, should - // not - // show education tooltip. - setShouldShowAppHandleEducation(false) + // App handle is visible but [shouldShowDesktopModeEducation] api returns false, should + // not show education tooltip. + setShouldShowDesktopModeEducation(false) // Simulate app handle visible. testCaptionStateFlow.value = createAppHandleState() @@ -165,7 +194,7 @@ class AppHandleEducationControllerTest : ShellTestCase() { fun init_appHandleNotVisible_shouldNotCallShowEducationTooltip() = testScope.runTest { // App handle is not visible, should not show education tooltip. - setShouldShowAppHandleEducation(true) + setShouldShowDesktopModeEducation(true) // Simulate app handle is not visible. testCaptionStateFlow.value = CaptionState.NoCaption @@ -184,7 +213,7 @@ class AppHandleEducationControllerTest : ShellTestCase() { // Mark app handle hint viewed. testDataStoreFlow.value = createWindowingEducationProto(appHandleHintViewedTimestampMillis = 123L) - setShouldShowAppHandleEducation(true) + setShouldShowDesktopModeEducation(true) // Simulate app handle visible. testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = false) @@ -196,231 +225,95 @@ class AppHandleEducationControllerTest : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) - fun overridePrerequisite_appHandleHintViewedAlready_shouldCallShowEducationTooltip() = + fun init_enterDesktopModeHintViewedAlready_shouldNotCallShowEducationTooltip() = testScope.runTest { - // App handle is visible but app handle hint has been viewed before. - // But as we are overriding prerequisite conditions, we should show app - // handle tooltip. + // App handle is visible but app handle hint has been viewed before, + // should not show education tooltip. // Mark app handle hint viewed. testDataStoreFlow.value = - createWindowingEducationProto(appHandleHintViewedTimestampMillis = 123L) - val systemPropertiesKey = - "persist.desktop_windowing_app_handle_education_override_conditions" - whenever(SystemProperties.getBoolean(eq(systemPropertiesKey), anyBoolean())) - .thenReturn(true) - setShouldShowAppHandleEducation(true) + createWindowingEducationProto(enterDesktopModeHintViewedTimestampMillis = 123L) + setShouldShowDesktopModeEducation(true) // Simulate app handle visible. - testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = false) - // Wait for first tooltip to showup. - waitForBufferDelay() - - verify(mockTooltipController, times(1)).showEducationTooltip(any(), any()) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) - fun init_appHandleExpanded_shouldMarkAppHandleHintUsed() = - testScope.runTest { - setShouldShowAppHandleEducation(false) - - // Simulate app handle visible and expanded. testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = true) - // Wait for some time before verifying + // Wait for first tooltip to showup. waitForBufferDelay() - verify(mockDataStoreRepository, times(1)) - .updateAppHandleHintUsedTimestampMillis(eq(true)) + verify(mockTooltipController, never()).showEducationTooltip(any(), any()) } @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) - fun init_showFirstTooltip_shouldMarkAppHandleHintViewed() = + fun init_exitDesktopModeHintViewedAlready_shouldNotCallShowEducationTooltip() = testScope.runTest { - // App handle is visible. Should show education tooltip. - setShouldShowAppHandleEducation(true) + // App handle is visible but app handle hint has been viewed before, + // should not show education tooltip. + // Mark app handle hint viewed. + testDataStoreFlow.value = + createWindowingEducationProto(exitDesktopModeHintViewedTimestampMillis = 123L) + setShouldShowDesktopModeEducation(true) // Simulate app handle visible. - testCaptionStateFlow.value = createAppHandleState() + testCaptionStateFlow.value = createAppHeaderState() // Wait for first tooltip to showup. waitForBufferDelay() - verify(mockDataStoreRepository, times(1)) - .updateAppHandleHintViewedTimestampMillis(eq(true)) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) - @Ignore("b/371527084: revisit testcase after refactoring original logic") - fun showWindowingImageButtonTooltip_appHandleExpanded_shouldCallShowEducationTooltipTwice() = - testScope.runTest { - // After first tooltip is dismissed, app handle is expanded. Should show second - // education - // tooltip. - showAndDismissFirstTooltip() - - // Simulate app handle expanded. - testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = true) - // Wait for next tooltip to showup. - waitForBufferDelay() - - // [showEducationTooltip] should be called twice, once for each tooltip. - verify(mockTooltipController, times(2)).showEducationTooltip(any(), any()) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) - @Ignore("b/371527084: revisit testcase after refactoring original logic") - fun showWindowingImageButtonTooltip_appHandleExpandedAfterTimeout_shouldCallShowEducationTooltipOnce() = - testScope.runTest { - // After first tooltip is dismissed, app handle is expanded after timeout. Should not - // show - // second education tooltip. - showAndDismissFirstTooltip() - - // Wait for timeout to occur, after this timeout we should not listen for further - // triggers - // anymore. - advanceTimeBy(APP_HANDLE_EDUCATION_TIMEOUT_BUFFER_MILLIS) - runCurrent() - // Simulate app handle expanded. - testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = true) - // Wait for next tooltip to showup. - waitForBufferDelay() - - // [showEducationTooltip] should be called once, just for the first tooltip. - verify(mockTooltipController, times(1)).showEducationTooltip(any(), any()) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) - @Ignore("b/371527084: revisit testcase after refactoring original logic") - fun showWindowingImageButtonTooltip_appHandleExpandedTwice_shouldCallShowEducationTooltipTwice() = - testScope.runTest { - // After first tooltip is dismissed, app handle is expanded twice. Should show second - // education tooltip only once. - showAndDismissFirstTooltip() - - // Simulate app handle expanded. - testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = true) - // Wait for next tooltip to showup. - waitForBufferDelay() - // Simulate app handle being expanded twice. - testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = true) - waitForBufferDelay() - - // [showEducationTooltip] should not be called thrice, even if app handle was expanded - // twice. Should be called twice, once for each tooltip. - verify(mockTooltipController, times(2)).showEducationTooltip(any(), any()) + verify(mockTooltipController, never()).showEducationTooltip(any(), any()) } @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) - @Ignore("b/371527084: revisit testcase after refactoring original logic") - fun showWindowingImageButtonTooltip_appHandleNotExpanded_shouldCallShowEducationTooltipOnce() = + fun overridePrerequisite_appHandleHintViewedAlready_shouldCallShowEducationTooltip() = testScope.runTest { - // After first tooltip is dismissed, app handle is not expanded. Should not show second - // education tooltip. - showAndDismissFirstTooltip() + // App handle is visible but app handle hint has been viewed before. + // But as we are overriding prerequisite conditions, we should show app + // handle tooltip. + // Mark app handle hint viewed. + testDataStoreFlow.value = + createWindowingEducationProto(appHandleHintViewedTimestampMillis = 123L) + val systemPropertiesKey = "persist.windowing_force_show_desktop_mode_education" + whenever(SystemProperties.getBoolean(eq(systemPropertiesKey), anyBoolean())) + .thenReturn(true) + setShouldShowDesktopModeEducation(true) - // Simulate app handle visible but not expanded. + // Simulate app handle visible. testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = false) - // Wait for next tooltip to showup. + // Wait for first tooltip to showup. waitForBufferDelay() - // [showEducationTooltip] should be called once, just for the first tooltip. verify(mockTooltipController, times(1)).showEducationTooltip(any(), any()) } @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) - @Ignore("b/371527084: revisit testcase after refactoring original logic") - fun showExitWindowingButtonTooltip_appHeaderVisible_shouldCallShowEducationTooltipThrice() = - testScope.runTest { - // After first two tooltips are dismissed, app header is visible. Should show third - // education tooltip. - showAndDismissFirstTooltip() - showAndDismissSecondTooltip() - - // Simulate app header visible. - testCaptionStateFlow.value = createAppHeaderState() - // Wait for next tooltip to showup. - waitForBufferDelay() - - verify(mockTooltipController, times(3)).showEducationTooltip(any(), any()) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) - @Ignore("b/371527084: revisit testcase after refactoring original logic") - fun showExitWindowingButtonTooltip_appHeaderVisibleAfterTimeout_shouldCallShowEducationTooltipTwice() = - testScope.runTest { - // After first two tooltips are dismissed, app header is visible after timeout. Should - // not - // show third education tooltip. - showAndDismissFirstTooltip() - showAndDismissSecondTooltip() - - // Wait for timeout to occur, after this timeout we should not listen for further - // triggers - // anymore. - advanceTimeBy(APP_HANDLE_EDUCATION_TIMEOUT_BUFFER_MILLIS) - runCurrent() - // Simulate app header visible. - testCaptionStateFlow.value = createAppHeaderState() - // Wait for next tooltip to showup. - waitForBufferDelay() - - verify(mockTooltipController, times(2)).showEducationTooltip(any(), any()) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) - @Ignore("b/371527084: revisit testcase after refactoring original logic") - fun showExitWindowingButtonTooltip_appHeaderVisibleTwice_shouldCallShowEducationTooltipThrice() = + fun clickAppHandleHint_openHandleMenuCallbackInvoked() = testScope.runTest { - // After first two tooltips are dismissed, app header is visible twice. Should show - // third - // education tooltip only once. - showAndDismissFirstTooltip() - showAndDismissSecondTooltip() - - // Simulate app header visible. - testCaptionStateFlow.value = createAppHeaderState() - // Wait for next tooltip to showup. - waitForBufferDelay() - testCaptionStateFlow.value = createAppHeaderState() - // Wait for next tooltip to showup. + // App handle is visible. Should show education tooltip. + setShouldShowDesktopModeEducation(true) + val mockOpenHandleMenuCallback: (Int) -> Unit = mock() + val mockToDesktopModeCallback: (Int, DesktopModeTransitionSource) -> Unit = mock() + educationController.setAppHandleEducationTooltipCallbacks( + mockOpenHandleMenuCallback, + mockToDesktopModeCallback, + ) + // Simulate app handle visible. + testCaptionStateFlow.value = createAppHandleState() + // Wait for first tooltip to showup. waitForBufferDelay() - verify(mockTooltipController, times(3)).showEducationTooltip(any(), any()) - } - - @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) - @Ignore("b/371527084: revisit testcase after refactoring original logic") - fun showExitWindowingButtonTooltip_appHeaderExpanded_shouldCallShowEducationTooltipTwice() = - testScope.runTest { - // After first two tooltips are dismissed, app header is visible but expanded. Should - // not - // show third education tooltip. - showAndDismissFirstTooltip() - showAndDismissSecondTooltip() - - // Simulate app header visible. - testCaptionStateFlow.value = createAppHeaderState(isHeaderMenuExpanded = true) - // Wait for next tooltip to showup. - waitForBufferDelay() + verify(mockTooltipController, atLeastOnce()) + .showEducationTooltip(educationConfigCaptor.capture(), any()) + educationConfigCaptor.lastValue.onEducationClickAction.invoke() - verify(mockTooltipController, times(2)).showEducationTooltip(any(), any()) + verify(mockOpenHandleMenuCallback, times(1)).invoke(any()) } @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) - fun setAppHandleEducationTooltipCallbacks_onAppHandleTooltipClicked_callbackInvoked() = + fun clickEnterDesktopModeHint_toDesktopModeCallbackInvoked() = testScope.runTest { // App handle is visible. Should show education tooltip. - setShouldShowAppHandleEducation(true) + setShouldShowDesktopModeEducation(true) val mockOpenHandleMenuCallback: (Int) -> Unit = mock() val mockToDesktopModeCallback: (Int, DesktopModeTransitionSource) -> Unit = mock() educationController.setAppHandleEducationTooltipCallbacks( @@ -428,7 +321,7 @@ class AppHandleEducationControllerTest : ShellTestCase() { mockToDesktopModeCallback, ) // Simulate app handle visible. - testCaptionStateFlow.value = createAppHandleState() + testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = true) // Wait for first tooltip to showup. waitForBufferDelay() @@ -436,68 +329,41 @@ class AppHandleEducationControllerTest : ShellTestCase() { .showEducationTooltip(educationConfigCaptor.capture(), any()) educationConfigCaptor.lastValue.onEducationClickAction.invoke() - verify(mockOpenHandleMenuCallback, times(1)).invoke(any()) + verify(mockToDesktopModeCallback, times(1)) + .invoke(any(), eq(DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON)) } @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION) - @Ignore("b/371527084: revisit testcase after refactoring original logic") - fun setAppHandleEducationTooltipCallbacks_onWindowingImageButtonTooltipClicked_callbackInvoked() = + fun clickExitDesktopModeHint_openHandleMenuCallbackInvoked() = testScope.runTest { - // After first tooltip is dismissed, app handle is expanded. Should show second - // education - // tooltip. - showAndDismissFirstTooltip() + // App handle is visible. Should show education tooltip. + setShouldShowDesktopModeEducation(true) val mockOpenHandleMenuCallback: (Int) -> Unit = mock() val mockToDesktopModeCallback: (Int, DesktopModeTransitionSource) -> Unit = mock() educationController.setAppHandleEducationTooltipCallbacks( mockOpenHandleMenuCallback, mockToDesktopModeCallback, ) - // Simulate app handle expanded. - testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = true) - // Wait for next tooltip to showup. + // Simulate app handle visible. + testCaptionStateFlow.value = createAppHeaderState() + // Wait for first tooltip to showup. waitForBufferDelay() verify(mockTooltipController, atLeastOnce()) .showEducationTooltip(educationConfigCaptor.capture(), any()) educationConfigCaptor.lastValue.onEducationClickAction.invoke() - verify(mockToDesktopModeCallback, times(1)).invoke(any(), any()) + verify(mockOpenHandleMenuCallback, times(1)).invoke(any()) } - private suspend fun TestScope.showAndDismissFirstTooltip() { - setShouldShowAppHandleEducation(true) - // Simulate app handle visible. - testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = false) - // Wait for first tooltip to showup. - waitForBufferDelay() - // [shouldShowAppHandleEducation] should return false as education has been viewed - // before. - setShouldShowAppHandleEducation(false) - // Dismiss previous tooltip, after this we should listen for next tooltip's trigger. - captureAndInvokeOnDismissAction() + private suspend fun setShouldShowDesktopModeEducation(shouldShowDesktopModeEducation: Boolean) { + whenever(mockEducationFilter.shouldShowDesktopModeEducation(any<CaptionState.AppHandle>())) + .thenReturn(shouldShowDesktopModeEducation) + whenever(mockEducationFilter.shouldShowDesktopModeEducation(any<CaptionState.AppHeader>())) + .thenReturn(shouldShowDesktopModeEducation) } - private fun TestScope.showAndDismissSecondTooltip() { - // Simulate app handle expanded. - testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = true) - // Wait for next tooltip to showup. - waitForBufferDelay() - // Dismiss previous tooltip, after this we should listen for next tooltip's trigger. - captureAndInvokeOnDismissAction() - } - - private fun captureAndInvokeOnDismissAction() { - verify(mockTooltipController, atLeastOnce()) - .showEducationTooltip(educationConfigCaptor.capture(), any()) - educationConfigCaptor.lastValue.onDismissAction.invoke() - } - - private suspend fun setShouldShowAppHandleEducation(shouldShowAppHandleEducation: Boolean) = - whenever(mockEducationFilter.shouldShowAppHandleEducation(any())) - .thenReturn(shouldShowAppHandleEducation) - /** * Class under test waits for some time before showing education, simulate advance time before * verifying or moving forward @@ -510,7 +376,5 @@ class AppHandleEducationControllerTest : ShellTestCase() { private companion object { val APP_HANDLE_EDUCATION_DELAY_BUFFER_MILLIS: Long = APP_HANDLE_EDUCATION_DELAY_MILLIS + 1000L - val APP_HANDLE_EDUCATION_TIMEOUT_BUFFER_MILLIS: Long = - APP_HANDLE_EDUCATION_TIMEOUT_MILLIS + 1000L } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationDatastoreRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationDatastoreRepositoryTest.kt index 4db883d13551..31dfc78902b2 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationDatastoreRepositoryTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationDatastoreRepositoryTest.kt @@ -123,6 +123,24 @@ class AppHandleEducationDatastoreRepositoryTest { } @Test + fun updateEnterDesktopModeHintViewedTimestampMillis_updatesDatastoreProto() = + runTest(StandardTestDispatcher()) { + datastoreRepository.updateEnterDesktopModeHintViewedTimestampMillis(true) + + val result = testDatastore.data.first().hasEnterDesktopModeHintViewedTimestampMillis() + assertThat(result).isEqualTo(true) + } + + @Test + fun updateExitDesktopModeHintViewedTimestampMillis_updatesDatastoreProto() = + runTest(StandardTestDispatcher()) { + datastoreRepository.updateExitDesktopModeHintViewedTimestampMillis(true) + + val result = testDatastore.data.first().hasExitDesktopModeHintViewedTimestampMillis() + assertThat(result).isEqualTo(true) + } + + @Test fun updateAppHandleHintUsedTimestampMillis_updatesDatastoreProto() = runTest(StandardTestDispatcher()) { datastoreRepository.updateAppHandleHintUsedTimestampMillis(true) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationFilterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationFilterTest.kt index 2fc36efb1a41..218226240c0f 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationFilterTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationFilterTest.kt @@ -89,9 +89,9 @@ class AppHandleEducationFilterTest : ShellTestCase() { } @Test - fun shouldShowAppHandleEducation_isTriggerValid_returnsTrue() = runTest { - // setup() makes sure that all of the conditions satisfy and #shouldShowAppHandleEducation - // should return true + fun shouldShowDesktopModeEducation_isTriggerValid_returnsTrue() = runTest { + // setup() makes sure that all of the conditions satisfy and + // [shouldShowDesktopModeEducation] should return true val windowingEducationProto = createWindowingEducationProto( appUsageStats = mapOf(GMAIL_PACKAGE_NAME to 4), @@ -99,16 +99,15 @@ class AppHandleEducationFilterTest : ShellTestCase() { ) `when`(datastoreRepository.windowingEducationProto()).thenReturn(windowingEducationProto) - val result = educationFilter.shouldShowAppHandleEducation(createAppHandleState()) + val result = educationFilter.shouldShowDesktopModeEducation(createAppHandleState()) assertThat(result).isTrue() } @Test - fun shouldShowAppHandleEducation_focusAppNotInAllowlist_returnsFalse() = runTest { + fun shouldShowDesktopModeEducation_focusAppNotInAllowlist_returnsFalse() = runTest { // Pass Youtube as current focus app, it is not in allowlist hence - // #shouldShowAppHandleEducation - // should return false + // [shouldShowDesktopModeEducation] should return false testableResources.addOverride( R.array.desktop_windowing_app_handle_education_allowlist_apps, arrayOf(GMAIL_PACKAGE_NAME), @@ -122,16 +121,15 @@ class AppHandleEducationFilterTest : ShellTestCase() { createAppHandleState(createTaskInfo(runningTaskPackageName = YOUTUBE_PACKAGE_NAME)) `when`(datastoreRepository.windowingEducationProto()).thenReturn(windowingEducationProto) - val result = educationFilter.shouldShowAppHandleEducation(captionState) + val result = educationFilter.shouldShowDesktopModeEducation(captionState) assertThat(result).isFalse() } @Test - fun shouldShowAppHandleEducation_timeSinceSetupIsNotSufficient_returnsFalse() = runTest { - // Time required to have passed setup is > 100 years, hence #shouldShowAppHandleEducation - // should - // return false + fun shouldShowDesktopModeEducation_timeSinceSetupIsNotSufficient_returnsFalse() = runTest { + // Time required to have passed setup is > 100 years, hence [shouldShowDesktopModeEducation] + // should return false testableResources.addOverride( R.integer.desktop_windowing_education_required_time_since_setup_seconds, MAX_VALUE, @@ -143,50 +141,15 @@ class AppHandleEducationFilterTest : ShellTestCase() { ) `when`(datastoreRepository.windowingEducationProto()).thenReturn(windowingEducationProto) - val result = educationFilter.shouldShowAppHandleEducation(createAppHandleState()) - - assertThat(result).isFalse() - } - - @Test - fun shouldShowAppHandleEducation_appHandleHintViewedBefore_returnsFalse() = runTest { - // App handle hint has been viewed before, hence #shouldShowAppHandleEducation should return - // false - val windowingEducationProto = - createWindowingEducationProto( - appUsageStats = mapOf(GMAIL_PACKAGE_NAME to 4), - appHandleHintViewedTimestampMillis = 123L, - appUsageStatsLastUpdateTimestampMillis = Long.MAX_VALUE, - ) - `when`(datastoreRepository.windowingEducationProto()).thenReturn(windowingEducationProto) - - val result = educationFilter.shouldShowAppHandleEducation(createAppHandleState()) - - assertThat(result).isFalse() - } - - @Test - fun shouldShowAppHandleEducation_appHandleHintUsedBefore_returnsFalse() = runTest { - // App handle hint has been used before, hence #shouldShowAppHandleEducation should return - // false - val windowingEducationProto = - createWindowingEducationProto( - appUsageStats = mapOf(GMAIL_PACKAGE_NAME to 4), - appHandleHintUsedTimestampMillis = 123L, - appUsageStatsLastUpdateTimestampMillis = Long.MAX_VALUE, - ) - `when`(datastoreRepository.windowingEducationProto()).thenReturn(windowingEducationProto) - - val result = educationFilter.shouldShowAppHandleEducation(createAppHandleState()) + val result = educationFilter.shouldShowDesktopModeEducation(createAppHandleState()) assertThat(result).isFalse() } @Test - fun shouldShowAppHandleEducation_doesNotHaveMinAppUsage_returnsFalse() = runTest { + fun shouldShowDesktopModeEducation_doesNotHaveMinAppUsage_returnsFalse() = runTest { // Simulate that gmail app has been launched twice before, minimum app launch count is 3, - // hence - // #shouldShowAppHandleEducation should return false + // hence [shouldShowDesktopModeEducation] should return false testableResources.addOverride(R.integer.desktop_windowing_education_min_app_launch_count, 3) val windowingEducationProto = createWindowingEducationProto( @@ -195,13 +158,13 @@ class AppHandleEducationFilterTest : ShellTestCase() { ) `when`(datastoreRepository.windowingEducationProto()).thenReturn(windowingEducationProto) - val result = educationFilter.shouldShowAppHandleEducation(createAppHandleState()) + val result = educationFilter.shouldShowDesktopModeEducation(createAppHandleState()) assertThat(result).isFalse() } @Test - fun shouldShowAppHandleEducation_appUsageStatsStale_queryAppUsageStats() = runTest { + fun shouldShowDesktopModeEducation_appUsageStatsStale_queryAppUsageStats() = runTest { // UsageStats caching interval is set to 0ms, that means caching should happen very // frequently testableResources.addOverride( @@ -209,8 +172,7 @@ class AppHandleEducationFilterTest : ShellTestCase() { 0, ) // The DataStore currently holds a proto object where Gmail's app launch count is recorded - // as 4. - // This value exceeds the minimum required count of 3. + // as 4. This value exceeds the minimum required count of 3. testableResources.addOverride(R.integer.desktop_windowing_education_min_app_launch_count, 3) val windowingEducationProto = createWindowingEducationProto( @@ -223,40 +185,20 @@ class AppHandleEducationFilterTest : ShellTestCase() { .thenReturn(mapOf(GMAIL_PACKAGE_NAME to UsageStats().apply { mAppLaunchCount = 2 })) `when`(datastoreRepository.windowingEducationProto()).thenReturn(windowingEducationProto) - val result = educationFilter.shouldShowAppHandleEducation(createAppHandleState()) + val result = educationFilter.shouldShowDesktopModeEducation(createAppHandleState()) // Result should be false as queried usage stats should be considered to determine the - // result - // instead of cached stats - assertThat(result).isFalse() - } - - @Test - fun shouldShowAppHandleEducation_appHandleMenuExpanded_returnsFalse() = runTest { - val windowingEducationProto = - createWindowingEducationProto( - appUsageStats = mapOf(GMAIL_PACKAGE_NAME to 4), - appUsageStatsLastUpdateTimestampMillis = Long.MAX_VALUE, - ) - // Simulate app handle menu is expanded - val captionState = createAppHandleState(isHandleMenuExpanded = true) - `when`(datastoreRepository.windowingEducationProto()).thenReturn(windowingEducationProto) - - val result = educationFilter.shouldShowAppHandleEducation(captionState) - - // We should not show app handle education if app menu is expanded + // result instead of cached stats assertThat(result).isFalse() } @Test - fun shouldShowAppHandleEducation_overridePrerequisite_returnsTrue() = runTest { + fun shouldShowDesktopModeEducation_overridePrerequisite_returnsTrue() = runTest { // Simulate that gmail app has been launched twice before, minimum app launch count is 3, - // hence - // #shouldShowAppHandleEducation should return false. But as we are overriding prerequisite - // conditions, #shouldShowAppHandleEducation should return true. + // hence [shouldShowDesktopModeEducation] should return false. But as we are overriding + // prerequisite conditions, [shouldShowDesktopModeEducation] should return true. testableResources.addOverride(R.integer.desktop_windowing_education_min_app_launch_count, 3) - val systemPropertiesKey = - "persist.desktop_windowing_app_handle_education_override_conditions" + val systemPropertiesKey = "persist.windowing_force_show_desktop_mode_education" whenever(SystemProperties.getBoolean(eq(systemPropertiesKey), anyBoolean())) .thenReturn(true) val windowingEducationProto = @@ -266,7 +208,7 @@ class AppHandleEducationFilterTest : ShellTestCase() { ) `when`(datastoreRepository.windowingEducationProto()).thenReturn(windowingEducationProto) - val result = educationFilter.shouldShowAppHandleEducation(createAppHandleState()) + val result = educationFilter.shouldShowDesktopModeEducation(createAppHandleState()) assertThat(result).isTrue() } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizerTest.kt new file mode 100644 index 000000000000..a07203d86b75 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizerTest.kt @@ -0,0 +1,256 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.multidesks + +import android.testing.AndroidTestingRunner +import android.view.Display +import android.view.SurfaceControl +import android.window.TransitionInfo +import android.window.WindowContainerTransaction +import android.window.WindowContainerTransaction.HierarchyOp +import androidx.test.filters.SmallTest +import com.android.wm.shell.ShellTaskOrganizer +import com.android.wm.shell.ShellTestCase +import com.android.wm.shell.TestShellExecutor +import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask +import com.android.wm.shell.sysui.ShellCommandHandler +import com.android.wm.shell.sysui.ShellInit +import com.google.common.truth.Truth.assertThat +import org.junit.Assert.assertEquals +import org.junit.Assert.assertThrows +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.mock + +/** + * Tests for [RootTaskDesksOrganizer]. + * + * Usage: atest WMShellUnitTests:RootTaskDesksOrganizerTest + */ +@SmallTest +@RunWith(AndroidTestingRunner::class) +class RootTaskDesksOrganizerTest : ShellTestCase() { + + private val testExecutor = TestShellExecutor() + private val testShellInit = ShellInit(testExecutor) + private val mockShellCommandHandler = mock<ShellCommandHandler>() + private val mockShellTaskOrganizer = mock<ShellTaskOrganizer>() + + private lateinit var organizer: RootTaskDesksOrganizer + + @Before + fun setUp() { + organizer = + RootTaskDesksOrganizer(testShellInit, mockShellCommandHandler, mockShellTaskOrganizer) + } + + @Test + fun testCreateDesk_callsBack() { + val callback = FakeOnCreateCallback() + organizer.createDesk(Display.DEFAULT_DISPLAY, callback) + + val freeformRoot = createFreeformTask().apply { parentTaskId = -1 } + organizer.onTaskAppeared(freeformRoot, SurfaceControl()) + + assertThat(callback.created).isTrue() + assertEquals(freeformRoot.taskId, callback.deskId) + } + + @Test + fun testOnTaskAppeared_withoutRequest_throws() { + val freeformRoot = createFreeformTask().apply { parentTaskId = -1 } + + assertThrows(Exception::class.java) { + organizer.onTaskAppeared(freeformRoot, SurfaceControl()) + } + } + + @Test + fun testOnTaskAppeared_withRequestOnlyInAnotherDisplay_throws() { + organizer.createDesk(displayId = 2, FakeOnCreateCallback()) + val freeformRoot = createFreeformTask(Display.DEFAULT_DISPLAY).apply { parentTaskId = -1 } + + assertThrows(Exception::class.java) { + organizer.onTaskAppeared(freeformRoot, SurfaceControl()) + } + } + + @Test + fun testOnTaskAppeared_duplicateRoot_throws() { + organizer.createDesk(Display.DEFAULT_DISPLAY, FakeOnCreateCallback()) + val freeformRoot = createFreeformTask().apply { parentTaskId = -1 } + organizer.onTaskAppeared(freeformRoot, SurfaceControl()) + + assertThrows(Exception::class.java) { + organizer.onTaskAppeared(freeformRoot, SurfaceControl()) + } + } + + @Test + fun testOnTaskVanished_removesRoot() { + val callback = FakeOnCreateCallback() + organizer.createDesk(Display.DEFAULT_DISPLAY, callback) + val freeformRoot = createFreeformTask().apply { parentTaskId = -1 } + organizer.onTaskAppeared(freeformRoot, SurfaceControl()) + + organizer.onTaskVanished(freeformRoot) + + assertThat(organizer.roots.contains(freeformRoot.taskId)).isFalse() + } + + @Test + fun testDesktopWindowAppearsInDesk() { + organizer.createDesk(Display.DEFAULT_DISPLAY, FakeOnCreateCallback()) + val freeformRoot = createFreeformTask().apply { parentTaskId = -1 } + organizer.onTaskAppeared(freeformRoot, SurfaceControl()) + val child = createFreeformTask().apply { parentTaskId = freeformRoot.taskId } + + organizer.onTaskAppeared(child, SurfaceControl()) + + assertThat(organizer.roots[freeformRoot.taskId].children).contains(child.taskId) + } + + @Test + fun testDesktopWindowDisappearsFromDesk() { + organizer.createDesk(Display.DEFAULT_DISPLAY, FakeOnCreateCallback()) + val freeformRoot = createFreeformTask().apply { parentTaskId = -1 } + organizer.onTaskAppeared(freeformRoot, SurfaceControl()) + val child = createFreeformTask().apply { parentTaskId = freeformRoot.taskId } + + organizer.onTaskAppeared(child, SurfaceControl()) + organizer.onTaskVanished(child) + + assertThat(organizer.roots[freeformRoot.taskId].children).doesNotContain(child.taskId) + } + + @Test + fun testRemoveDesk() { + organizer.createDesk(Display.DEFAULT_DISPLAY, FakeOnCreateCallback()) + val freeformRoot = createFreeformTask().apply { parentTaskId = -1 } + organizer.onTaskAppeared(freeformRoot, SurfaceControl()) + + val wct = WindowContainerTransaction() + organizer.removeDesk(wct, freeformRoot.taskId) + + assertThat( + wct.hierarchyOps.any { hop -> + hop.type == HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_ROOT_TASK && + hop.container == freeformRoot.token.asBinder() + } + ) + .isTrue() + } + + @Test + fun testRemoveDesk_didNotExist_throws() { + val freeformRoot = createFreeformTask().apply { parentTaskId = -1 } + + val wct = WindowContainerTransaction() + assertThrows(Exception::class.java) { organizer.removeDesk(wct, freeformRoot.taskId) } + } + + @Test + fun testActivateDesk() { + organizer.createDesk(Display.DEFAULT_DISPLAY, FakeOnCreateCallback()) + val freeformRoot = createFreeformTask().apply { parentTaskId = -1 } + organizer.onTaskAppeared(freeformRoot, SurfaceControl()) + + val wct = WindowContainerTransaction() + organizer.activateDesk(wct, freeformRoot.taskId) + + assertThat( + wct.hierarchyOps.any { hop -> + hop.type == HierarchyOp.HIERARCHY_OP_TYPE_REORDER && + hop.toTop && + hop.container == freeformRoot.token.asBinder() + } + ) + .isTrue() + assertThat( + wct.hierarchyOps.any { hop -> + hop.type == HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT && + hop.container == freeformRoot.token.asBinder() + } + ) + .isTrue() + } + + @Test + fun testActivateDesk_didNotExist_throws() { + val freeformRoot = createFreeformTask().apply { parentTaskId = -1 } + + val wct = WindowContainerTransaction() + assertThrows(Exception::class.java) { organizer.activateDesk(wct, freeformRoot.taskId) } + } + + @Test + fun testMoveTaskToDesk() { + organizer.createDesk(Display.DEFAULT_DISPLAY, FakeOnCreateCallback()) + val freeformRoot = createFreeformTask().apply { parentTaskId = -1 } + organizer.onTaskAppeared(freeformRoot, SurfaceControl()) + + val desktopTask = createFreeformTask().apply { parentTaskId = -1 } + val wct = WindowContainerTransaction() + organizer.moveTaskToDesk(wct, freeformRoot.taskId, desktopTask) + + assertThat( + wct.hierarchyOps.any { hop -> + hop.isReparent && + hop.toTop && + hop.container == desktopTask.token.asBinder() && + hop.newParent == freeformRoot.token.asBinder() + } + ) + .isTrue() + } + + @Test + fun testMoveTaskToDesk_didNotExist_throws() { + val freeformRoot = createFreeformTask().apply { parentTaskId = -1 } + + val desktopTask = createFreeformTask().apply { parentTaskId = -1 } + val wct = WindowContainerTransaction() + assertThrows(Exception::class.java) { + organizer.moveTaskToDesk(wct, freeformRoot.taskId, desktopTask) + } + } + + @Test + fun testGetDeskAtEnd() { + organizer.createDesk(Display.DEFAULT_DISPLAY, FakeOnCreateCallback()) + val freeformRoot = createFreeformTask().apply { parentTaskId = -1 } + organizer.onTaskAppeared(freeformRoot, SurfaceControl()) + + val task = createFreeformTask().apply { parentTaskId = freeformRoot.taskId } + val endDesk = + organizer.getDeskAtEnd( + TransitionInfo.Change(task.token, SurfaceControl()).apply { taskInfo = task } + ) + + assertThat(endDesk).isEqualTo(freeformRoot.taskId) + } + + private class FakeOnCreateCallback : DesksOrganizer.OnCreateCallback { + var deskId: Int? = null + val created: Boolean + get() = deskId != null + + override fun onCreated(deskId: Int) { + this.deskId = deskId + } + } +} 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 a3c441698905..9a8f264e98a4 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerTest.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.desktopmode.persistence import android.os.UserManager +import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.SetFlagsRule import android.testing.AndroidTestingRunner @@ -24,6 +25,7 @@ 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.window.flags.Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND import com.android.wm.shell.ShellTestCase import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.desktopmode.DesktopUserRepositories @@ -85,7 +87,9 @@ class DesktopRepositoryInitializerTest : ShellTestCase() { @Test @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE, FLAG_ENABLE_DESKTOP_WINDOWING_HSUM) - fun initWithPersistence_multipleUsers_addedCorrectly() = + /** TODO: b/362720497 - add multi-desk version when implemented. */ + @DisableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun initWithPersistence_multipleUsers_addedCorrectly_multiDesksDisabled() = runTest(StandardTestDispatcher()) { whenever(persistentRepository.getUserDesktopRepositoryMap()) .thenReturn( @@ -145,7 +149,9 @@ class DesktopRepositoryInitializerTest : ShellTestCase() { @Test @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE) - fun initWithPersistence_singleUser_addedCorrectly() = + /** TODO: b/362720497 - add multi-desk version when implemented. */ + @DisableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun initWithPersistence_singleUser_addedCorrectly_multiDesksDisabled() = runTest(StandardTestDispatcher()) { whenever(persistentRepository.getUserDesktopRepositoryMap()) .thenReturn(mapOf(USER_ID_1 to desktopRepositoryState1)) @@ -156,24 +162,24 @@ class DesktopRepositoryInitializerTest : ShellTestCase() { repositoryInitializer.initialize(desktopUserRepositories) - // Desktop Repository currently returns all tasks across desktops for a specific user - // since the repository currently doesn't handle desktops. This test logic should be - // updated - // once the repository handles multiple desktops. assertThat( - desktopUserRepositories.getProfile(USER_ID_1).getActiveTasks(DEFAULT_DISPLAY) + desktopUserRepositories + .getProfile(USER_ID_1) + .getActiveTaskIdsInDesk(deskId = DEFAULT_DISPLAY) ) .containsExactly(1, 3, 4, 5) .inOrder() assertThat( desktopUserRepositories .getProfile(USER_ID_1) - .getExpandedTasksOrdered(DEFAULT_DISPLAY) + .getExpandedTasksIdsInDeskOrdered(deskId = DEFAULT_DISPLAY) ) .containsExactly(5, 1) .inOrder() assertThat( - desktopUserRepositories.getProfile(USER_ID_1).getMinimizedTasks(DEFAULT_DISPLAY) + desktopUserRepositories + .getProfile(USER_ID_1) + .getMinimizedTaskIdsInDesk(deskId = DEFAULT_DISPLAY) ) .containsExactly(3, 4) .inOrder() @@ -195,6 +201,7 @@ class DesktopRepositoryInitializerTest : ShellTestCase() { val desktop1: Desktop = Desktop.newBuilder() .setDesktopId(DESKTOP_ID_1) + .setDisplayId(DEFAULT_DISPLAY) .addAllZOrderedTasks(freeformTasksInZOrder1) .putTasksByTaskId( 1, @@ -216,6 +223,7 @@ class DesktopRepositoryInitializerTest : ShellTestCase() { val desktop2: Desktop = Desktop.newBuilder() .setDesktopId(DESKTOP_ID_2) + .setDisplayId(DEFAULT_DISPLAY) .addAllZOrderedTasks(freeformTasksInZOrder2) .putTasksByTaskId( 4, @@ -237,6 +245,7 @@ class DesktopRepositoryInitializerTest : ShellTestCase() { val desktop3: Desktop = Desktop.newBuilder() .setDesktopId(DESKTOP_ID_3) + .setDisplayId(DEFAULT_DISPLAY) .addAllZOrderedTasks(freeformTasksInZOrder3) .putTasksByTaskId( 7, 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 fa5989a3ca99..4174bbd89b76 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 @@ -46,6 +46,7 @@ import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestRunningTaskInfoBuilder; import com.android.wm.shell.common.LaunchAdjacentController; +import com.android.wm.shell.desktopmode.DesktopModeLoggerTransitionObserver; import com.android.wm.shell.desktopmode.DesktopRepository; import com.android.wm.shell.desktopmode.DesktopTasksController; import com.android.wm.shell.desktopmode.DesktopUserRepositories; @@ -89,6 +90,8 @@ public final class FreeformTaskListenerTests extends ShellTestCase { @Mock private DesktopTasksController mDesktopTasksController; @Mock + private DesktopModeLoggerTransitionObserver mDesktopModeLoggerTransitionObserver; + @Mock private LaunchAdjacentController mLaunchAdjacentController; @Mock private TaskChangeListener mTaskChangeListener; @@ -114,6 +117,7 @@ public final class FreeformTaskListenerTests extends ShellTestCase { mTaskOrganizer, Optional.of(mDesktopUserRepositories), Optional.of(mDesktopTasksController), + mDesktopModeLoggerTransitionObserver, mLaunchAdjacentController, mWindowDecorViewModel, Optional.of(mTaskChangeListener)); @@ -159,7 +163,8 @@ public final class FreeformTaskListenerTests extends ShellTestCase { } @Test - public void focusTaskChanged_addsFreeformTaskToRepo() { + @DisableFlags(FLAG_ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS) + public void focusTaskChanged_noTransitionObserversFlag_addsFreeformTaskToRepo() { ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FREEFORM).build(); task.isFocused = true; @@ -171,6 +176,19 @@ public final class FreeformTaskListenerTests extends ShellTestCase { } @Test + @EnableFlags(FLAG_ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS) + public void focusTaskChanged_enableTransitionObservers_freeformTaskNotAddedToRepo() { + ActivityManager.RunningTaskInfo task = + new TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FREEFORM).build(); + task.isFocused = true; + + mFreeformTaskListener.onFocusTaskChanged(task); + + verify(mDesktopUserRepositories.getCurrent(), never()) + .addTask(task.displayId, task.taskId, task.isVisible); + } + + @Test public void focusTaskChanged_fullscreenTaskNotAddedToRepo() { ActivityManager.RunningTaskInfo fullscreenTask = new TestRunningTaskInfoBuilder() @@ -269,6 +287,19 @@ public final class FreeformTaskListenerTests extends ShellTestCase { } @Test + public void onTaskVanished_withDesktopModeLogger_forwards() { + ActivityManager.RunningTaskInfo task = + new TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FREEFORM).build(); + task.isVisible = true; + mFreeformTaskListener.onTaskAppeared(task, mMockSurfaceControl); + + mFreeformTaskListener.onTaskVanished(task); + + verify(mDesktopModeLoggerTransitionObserver).onTaskVanished(task); + } + + + @Test public void onTaskInfoChanged_withDesktopController_forwards() { ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FREEFORM).build(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java index 53f6cda62f55..7e68b68e469a 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java @@ -87,7 +87,7 @@ public class PipAnimationControllerTest extends ShellTestCase { .getAnimator(mTaskInfo, mLeash, new Rect(), 0f, 1f); assertEquals("Expect ANIM_TYPE_ALPHA animation", - animator.getAnimationType(), PipAnimationController.ANIM_TYPE_ALPHA); + animator.getAnimationType(), PipTransitionController.ANIM_TYPE_ALPHA); } @Test @@ -101,7 +101,7 @@ public class PipAnimationControllerTest extends ShellTestCase { false /* alwaysAnimateTaskBounds */); assertEquals("Expect ANIM_TYPE_BOUNDS animation", - animator.getAnimationType(), PipAnimationController.ANIM_TYPE_BOUNDS); + animator.getAnimationType(), PipTransitionController.ANIM_TYPE_BOUNDS); } @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 2eb2c3b8e2f7..836f4c24a979 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 @@ -289,7 +289,7 @@ public class PipTaskOrganizerTest extends ShellTestCase { DisplayLayout layout = new DisplayLayout(info, mContext.getResources(), true, true); mPipDisplayLayoutState.setDisplayLayout(layout); - doReturn(PipAnimationController.ANIM_TYPE_ALPHA).when(mMockPipAnimationController) + doReturn(PipTransitionController.ANIM_TYPE_ALPHA).when(mMockPipAnimationController) .takeOneShotEnterAnimationType(); mPipTaskOrganizer.setSurfaceControlTransactionFactory( MockSurfaceControlHelper::createMockSurfaceControlTransaction); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java index 6d18e3696f84..b11715b669f4 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java @@ -16,12 +16,16 @@ package com.android.wm.shell.pip.phone; +import static com.android.wm.shell.Flags.FLAG_ENABLE_PIP2; + import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import android.graphics.Rect; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.flag.junit.SetFlagsRule; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.util.Size; @@ -45,6 +49,7 @@ import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.sysui.ShellInit; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -63,6 +68,8 @@ import java.util.Optional; @SmallTest @TestableLooper.RunWithLooper(setAsMainLooper = true) public class PipTouchHandlerTest extends ShellTestCase { + @Rule + public SetFlagsRule setFlagsRule = new SetFlagsRule(); private static final int INSET = 10; private static final int PIP_LENGTH = 100; @@ -150,6 +157,7 @@ public class PipTouchHandlerTest extends ShellTestCase { } @Test + @DisableFlags(FLAG_ENABLE_PIP2) public void instantiate_addInitCallback() { verify(mShellInit, times(1)).addInitCallback(any(), any()); } 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 065fa219e8d0..542289db6cfc 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 @@ -211,6 +211,7 @@ public class RecentTasksControllerTest extends ShellTestCase { @Test public void testAddRemoveSplitNotifyChange() { + reset(mRecentTasksController); RecentTaskInfo t1 = makeTaskInfo(1); RecentTaskInfo t2 = makeTaskInfo(2); setRawList(t1, t2); @@ -225,6 +226,7 @@ public class RecentTasksControllerTest extends ShellTestCase { @Test public void testAddSameSplitBoundsInfoSkipNotifyChange() { + reset(mRecentTasksController); RecentTaskInfo t1 = makeTaskInfo(1); RecentTaskInfo t2 = makeTaskInfo(2); setRawList(t1, t2); @@ -535,6 +537,7 @@ public class RecentTasksControllerTest extends ShellTestCase { @Test public void testTaskWindowingModeChangedNotifiesChange() { + reset(mRecentTasksController); RecentTaskInfo t1 = makeTaskInfo(1); setRawList(t1); @@ -551,7 +554,8 @@ public class RecentTasksControllerTest extends ShellTestCase { WINDOWING_MODE_MULTI_WINDOW); mShellTaskOrganizer.onTaskInfoChanged(rt2MultiWIndow); - verify(mRecentTasksController).notifyRecentTasksChanged(); + // One for onTaskAppeared and one for onTaskInfoChanged + verify(mRecentTasksController, times(2)).notifyRecentTasksChanged(); } @Test 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/shared/desktopmode/DesktopModeCompatPolicyTest.kt index 7157a7f0b38f..8c78debdc19f 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/shared/desktopmode/DesktopModeCompatPolicyTest.kt @@ -14,29 +14,38 @@ * limitations under the License. */ -package com.android.wm.shell.compatui +package com.android.wm.shell.shared.desktopmode import android.content.ComponentName import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.internal.R +import com.android.wm.shell.compatui.CompatUIShellTestCase import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith /** - * Tests for [@link AppCompatUtils]. + * Tests for [@link DesktopModeCompatPolicy]. * - * Build/Install/Run: atest WMShellUnitTests:AppCompatUtilsTest + * Build/Install/Run: atest WMShellUnitTests:DesktopModeCompatPolicyTest */ @RunWith(AndroidTestingRunner::class) @SmallTest -class AppCompatUtilsTest : CompatUIShellTestCase() { +class DesktopModeCompatPolicyTest : CompatUIShellTestCase() { + private lateinit var desktopModeCompatPolicy: DesktopModeCompatPolicy + + @Before + fun setUp() { + desktopModeCompatPolicy = DesktopModeCompatPolicy(mContext) + } + @Test fun testIsTopActivityExemptFromDesktopWindowing_onlyTransparentActivitiesInStack() { - assertTrue(isTopActivityExemptFromDesktopWindowing(mContext, + assertTrue(desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing( createFreeformTask(/* displayId */ 0) .apply { isActivityStackTransparent = true @@ -47,7 +56,7 @@ class AppCompatUtilsTest : CompatUIShellTestCase() { @Test fun testIsTopActivityExemptFromDesktopWindowing_noActivitiesInStack() { - assertFalse(isTopActivityExemptFromDesktopWindowing(mContext, + assertFalse(desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing( createFreeformTask(/* displayId */ 0) .apply { isActivityStackTransparent = true @@ -58,7 +67,7 @@ class AppCompatUtilsTest : CompatUIShellTestCase() { @Test fun testIsTopActivityExemptFromDesktopWindowing_nonTransparentActivitiesInStack() { - assertFalse(isTopActivityExemptFromDesktopWindowing(mContext, + assertFalse(desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing( createFreeformTask(/* displayId */ 0) .apply { isActivityStackTransparent = false @@ -69,7 +78,7 @@ class AppCompatUtilsTest : CompatUIShellTestCase() { @Test fun testIsTopActivityExemptFromDesktopWindowing_transparentActivityStack_notDisplayed() { - assertFalse(isTopActivityExemptFromDesktopWindowing(mContext, + assertFalse(desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing( createFreeformTask(/* displayId */ 0) .apply { isActivityStackTransparent = true @@ -82,7 +91,7 @@ class AppCompatUtilsTest : CompatUIShellTestCase() { fun testIsTopActivityExemptFromDesktopWindowing_systemUiTask() { val systemUIPackageName = context.resources.getString(R.string.config_systemUi) val baseComponent = ComponentName(systemUIPackageName, /* class */ "") - assertTrue(isTopActivityExemptFromDesktopWindowing(mContext, + assertTrue(desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing( createFreeformTask(/* displayId */ 0) .apply { baseActivity = baseComponent @@ -94,7 +103,7 @@ class AppCompatUtilsTest : CompatUIShellTestCase() { fun testIsTopActivityExemptFromDesktopWindowing_systemUiTask_notDisplayed() { val systemUIPackageName = context.resources.getString(R.string.config_systemUi) val baseComponent = ComponentName(systemUIPackageName, /* class */ "") - assertFalse(isTopActivityExemptFromDesktopWindowing(mContext, + assertFalse(desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing( createFreeformTask(/* displayId */ 0) .apply { baseActivity = baseComponent 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 bb9703fce2e3..7f6b06d46abf 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 @@ -213,7 +213,7 @@ public class SplitScreenControllerTests extends ShellTestCase { @Test public void startIntent_multiInstancesSupported_appendsMultipleTaskFag() { - doReturn(true).when(mMultiInstanceHelper).supportsMultiInstanceSplit(any()); + doReturn(true).when(mMultiInstanceHelper).supportsMultiInstanceSplit(any(), anyInt()); Intent startIntent = createStartIntent("startActivity"); PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE); @@ -252,13 +252,13 @@ public class SplitScreenControllerTests extends ShellTestCase { verify(mStageCoordinator).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT), isNull(), isNull(), eq(SPLIT_INDEX_0)); - verify(mMultiInstanceHelper, never()).supportsMultiInstanceSplit(any()); + verify(mMultiInstanceHelper, never()).supportsMultiInstanceSplit(any(), anyInt()); verify(mStageCoordinator, never()).switchSplitPosition(any()); } @Test public void startIntent_multiInstancesSupported_startTaskInBackgroundAfterSplitActivated() { - doReturn(true).when(mMultiInstanceHelper).supportsMultiInstanceSplit(any()); + doReturn(true).when(mMultiInstanceHelper).supportsMultiInstanceSplit(any(), anyInt()); doNothing().when(mSplitScreenController).startTask(anyInt(), anyInt(), any(), any()); Intent startIntent = createStartIntent("startActivity"); PendingIntent pendingIntent = @@ -276,14 +276,14 @@ public class SplitScreenControllerTests extends ShellTestCase { mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null, SPLIT_POSITION_TOP_OR_LEFT, null /* options */, null /* hideTaskToken */, SPLIT_INDEX_0); - verify(mMultiInstanceHelper, never()).supportsMultiInstanceSplit(any()); + verify(mMultiInstanceHelper, never()).supportsMultiInstanceSplit(any(), anyInt()); verify(mStageCoordinator).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT), isNull(), isNull(), eq(SPLIT_INDEX_0)); } @Test public void startIntent_multiInstancesNotSupported_switchesPositionAfterSplitActivated() { - doReturn(false).when(mMultiInstanceHelper).supportsMultiInstanceSplit(any()); + doReturn(false).when(mMultiInstanceHelper).supportsMultiInstanceSplit(any(), anyInt()); Intent startIntent = createStartIntent("startActivity"); PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE); 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 ae0c9d6cbf7c..414c0147660c 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 @@ -26,6 +26,7 @@ import android.os.Handler; import android.view.SurfaceControl; import com.android.dx.mockito.inline.extended.ExtendedMockito; +import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.TestRunningTaskInfoBuilder; import com.android.wm.shell.common.DisplayController; @@ -83,12 +84,13 @@ public class SplitTestUtils { Optional<RecentTasksController> recentTasks, LaunchAdjacentController launchAdjacentController, Optional<WindowDecorViewModel> windowDecorViewModel, SplitState splitState, - Optional<DesktopTasksController> desktopTasksController) { + Optional<DesktopTasksController> desktopTasksController, + RootTaskDisplayAreaOrganizer rootTDAOrganizer) { super(context, displayId, syncQueue, taskOrganizer, mainStage, sideStage, displayController, imeController, insetsController, splitLayout, transitions, transactionPool, mainExecutor, mainHandler, recentTasks, launchAdjacentController, windowDecorViewModel, splitState, - desktopTasksController); + desktopTasksController, rootTDAOrganizer); // 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 b0fdfacd7d83..b9d6a454694d 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 @@ -47,6 +47,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.annotation.NonNull; import android.app.ActivityManager; @@ -54,6 +55,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; import android.view.SurfaceControl; +import android.window.DisplayAreaInfo; import android.window.IRemoteTransition; import android.window.RemoteTransition; import android.window.TransitionInfo; @@ -65,6 +67,8 @@ import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.launcher3.icons.IconProvider; +import com.android.wm.shell.Flags; +import com.android.wm.shell.MockToken; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; @@ -121,6 +125,8 @@ public class SplitTransitionTests extends ShellTestCase { private StageTaskListener mSideStage; private StageCoordinator mStageCoordinator; private SplitScreenTransitions mSplitScreenTransitions; + private final DisplayAreaInfo mDisplayAreaInfo = new DisplayAreaInfo(new MockToken().token(), + DEFAULT_DISPLAY, 0); private ActivityManager.RunningTaskInfo mMainChild; private ActivityManager.RunningTaskInfo mSideChild; @@ -146,7 +152,9 @@ public class SplitTransitionTests extends ShellTestCase { mSyncQueue, mTaskOrganizer, mMainStage, mSideStage, mDisplayController, mDisplayImeController, mDisplayInsetsController, mSplitLayout, mTransitions, mTransactionPool, mMainExecutor, mMainHandler, Optional.empty(), - mLaunchAdjacentController, Optional.empty(), mSplitState, Optional.empty()); + mLaunchAdjacentController, Optional.empty(), mSplitState, Optional.empty(), + mRootTDAOrganizer); + when(mRootTDAOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)).thenReturn(mDisplayAreaInfo); mStageCoordinator.setMixedHandler(mMixedHandler); mSplitScreenTransitions = mStageCoordinator.getSplitTransitions(); @@ -348,8 +356,13 @@ public class SplitTransitionTests extends ShellTestCase { // Make sure it cleans-up if recents doesn't restore WindowContainerTransaction commitWCT = new WindowContainerTransaction(); - mStageCoordinator.onRecentsInSplitAnimationFinish(commitWCT, - mock(SurfaceControl.Transaction.class)); + if (Flags.enableRecentsBookendTransition()) { + mStageCoordinator.onRecentsInSplitAnimationFinishing(false /* returnToApp */, commitWCT, + mock(SurfaceControl.Transaction.class)); + } else { + mStageCoordinator.onRecentsInSplitAnimationFinish(commitWCT, + mock(SurfaceControl.Transaction.class)); + } assertFalse(mStageCoordinator.isSplitScreenVisible()); } @@ -413,8 +426,13 @@ public class SplitTransitionTests extends ShellTestCase { // simulate the restoreWCT being applied: mMainStage.onTaskAppeared(mMainChild, mock(SurfaceControl.class)); mSideStage.onTaskAppeared(mSideChild, mock(SurfaceControl.class)); - mStageCoordinator.onRecentsInSplitAnimationFinish(restoreWCT, - mock(SurfaceControl.Transaction.class)); + if (Flags.enableRecentsBookendTransition()) { + mStageCoordinator.onRecentsInSplitAnimationFinishing(true /* returnToApp */, restoreWCT, + mock(SurfaceControl.Transaction.class)); + } else { + mStageCoordinator.onRecentsInSplitAnimationFinish(restoreWCT, + mock(SurfaceControl.Transaction.class)); + } assertTrue(mStageCoordinator.isSplitScreenVisible()); } 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 7a88ace3f85f..5851cbf9b933 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 @@ -17,6 +17,8 @@ package com.android.wm.shell.splitscreen; import static android.app.ActivityTaskManager.INVALID_TASK_ID; +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.view.Display.DEFAULT_DISPLAY; @@ -27,11 +29,14 @@ import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSIT import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED; +import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER; +import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DESKTOP_MODE; import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_DISMISS; 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.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; @@ -39,6 +44,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.notNull; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; @@ -56,6 +62,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.view.SurfaceControl; +import android.window.DisplayAreaInfo; import android.window.RemoteTransition; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; @@ -64,6 +71,8 @@ import androidx.test.annotation.UiThreadTest; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; +import com.android.wm.shell.MockToken; +import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestRunningTaskInfoBuilder; @@ -94,6 +103,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.Optional; +import java.util.function.Consumer; /** * Tests for {@link StageCoordinator} @@ -125,6 +135,8 @@ public class StageCoordinatorTests extends ShellTestCase { private DefaultMixedHandler mDefaultMixedHandler; @Mock private SplitState mSplitState; + @Mock + private RootTaskDisplayAreaOrganizer mRootTDAOrganizer; private final Rect mBounds1 = new Rect(10, 20, 30, 40); private final Rect mBounds2 = new Rect(5, 10, 15, 20); @@ -138,6 +150,10 @@ public class StageCoordinatorTests extends ShellTestCase { private final TestShellExecutor mMainExecutor = new TestShellExecutor(); private final ShellExecutor mAnimExecutor = new TestShellExecutor(); private final Handler mMainHandler = new Handler(Looper.getMainLooper()); + private final DisplayAreaInfo mDisplayAreaInfo = new DisplayAreaInfo(new MockToken().token(), + DEFAULT_DISPLAY, 0); + private final ActivityManager.RunningTaskInfo mMainChildTaskInfo = + new TestRunningTaskInfoBuilder().setVisible(true).build(); @Before @UiThreadTest @@ -148,9 +164,10 @@ public class StageCoordinatorTests extends ShellTestCase { mTaskOrganizer, mMainStage, mSideStage, mDisplayController, mDisplayImeController, mDisplayInsetsController, mSplitLayout, mTransitions, mTransactionPool, mMainExecutor, mMainHandler, Optional.empty(), mLaunchAdjacentController, - Optional.empty(), mSplitState, Optional.empty())); + Optional.empty(), mSplitState, Optional.empty(), mRootTDAOrganizer)); mDividerLeash = new SurfaceControl.Builder().setName("fakeDivider").build(); + when(mRootTDAOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)).thenReturn(mDisplayAreaInfo); when(mSplitLayout.getTopLeftBounds()).thenReturn(mBounds1); when(mSplitLayout.getBottomRightBounds()).thenReturn(mBounds2); @@ -167,6 +184,12 @@ public class StageCoordinatorTests extends ShellTestCase { mMainStage.mRootTaskInfo = new TestRunningTaskInfoBuilder().build(); doReturn(mock(SplitDecorManager.class)).when(mMainStage).getSplitDecorManager(); doReturn(mock(SplitDecorManager.class)).when(mSideStage).getSplitDecorManager(); + + doAnswer(invocation -> { + Consumer<ActivityManager.RunningTaskInfo> consumer = invocation.getArgument(0); + consumer.accept(mMainChildTaskInfo); + return null; + }).when(mMainStage).doForAllChildTaskInfos(any()); } @Test @@ -454,6 +477,55 @@ public class StageCoordinatorTests extends ShellTestCase { int windowingMode = wctCaptor.getValue().getChanges().get(binder).getWindowingMode(); assertEquals(windowingMode, WINDOWING_MODE_UNDEFINED); } + @Test + public void testDismiss_freeformDisplay() { + mDisplayAreaInfo.configuration.windowConfiguration.setWindowingMode( + WINDOWING_MODE_FREEFORM); + when(mStageCoordinator.isSplitActive()).thenReturn(true); + + WindowContainerTransaction wct = new WindowContainerTransaction(); + mStageCoordinator.prepareExitSplitScreen(STAGE_TYPE_MAIN, wct, EXIT_REASON_DRAG_DIVIDER); + + assertEquals(wct.getChanges().get(mMainChildTaskInfo.token.asBinder()).getWindowingMode(), + WINDOWING_MODE_FULLSCREEN); + } + + @Test + public void testDismiss_freeformDisplayToDesktop() { + mDisplayAreaInfo.configuration.windowConfiguration.setWindowingMode( + WINDOWING_MODE_FREEFORM); + when(mStageCoordinator.isSplitActive()).thenReturn(true); + + WindowContainerTransaction wct = new WindowContainerTransaction(); + mStageCoordinator.prepareExitSplitScreen(STAGE_TYPE_MAIN, wct, EXIT_REASON_DESKTOP_MODE); + + WindowContainerTransaction.Change c = + wct.getChanges().get(mMainChildTaskInfo.token.asBinder()); + assertFalse(c != null && c.getWindowingMode() == WINDOWING_MODE_FULLSCREEN); + } + + @Test + public void testDismiss_fullscreenDisplay() { + when(mStageCoordinator.isSplitActive()).thenReturn(true); + + WindowContainerTransaction wct = new WindowContainerTransaction(); + mStageCoordinator.prepareExitSplitScreen(STAGE_TYPE_MAIN, wct, EXIT_REASON_DRAG_DIVIDER); + + assertEquals(wct.getChanges().get(mMainChildTaskInfo.token.asBinder()).getWindowingMode(), + WINDOWING_MODE_UNDEFINED); + } + + @Test + public void testDismiss_fullscreenDisplayToDesktop() { + when(mStageCoordinator.isSplitActive()).thenReturn(true); + + WindowContainerTransaction wct = new WindowContainerTransaction(); + mStageCoordinator.prepareExitSplitScreen(STAGE_TYPE_MAIN, wct, EXIT_REASON_DESKTOP_MODE); + + WindowContainerTransaction.Change c = + wct.getChanges().get(mMainChildTaskInfo.token.asBinder()); + assertFalse(c != null && c.getWindowingMode() == WINDOWING_MODE_FULLSCREEN); + } private Transitions createTestTransitions() { ShellInit shellInit = new ShellInit(mMainExecutor); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/util/WindowingEducationTestUtils.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/util/WindowingEducationTestUtils.kt index b9d91e7895db..546848421302 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/util/WindowingEducationTestUtils.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/util/WindowingEducationTestUtils.kt @@ -81,7 +81,9 @@ fun createWindowingEducationProto( appHandleHintViewedTimestampMillis: Long? = null, appHandleHintUsedTimestampMillis: Long? = null, appUsageStats: Map<String, Int>? = null, - appUsageStatsLastUpdateTimestampMillis: Long? = null + appUsageStatsLastUpdateTimestampMillis: Long? = null, + enterDesktopModeHintViewedTimestampMillis: Long? = null, + exitDesktopModeHintViewedTimestampMillis: Long? = null, ): WindowingEducationProto = WindowingEducationProto.newBuilder() .apply { @@ -91,6 +93,12 @@ fun createWindowingEducationProto( if (appHandleHintUsedTimestampMillis != null) { setAppHandleHintUsedTimestampMillis(appHandleHintUsedTimestampMillis) } + if (enterDesktopModeHintViewedTimestampMillis != null) { + setEnterDesktopModeHintViewedTimestampMillis(enterDesktopModeHintViewedTimestampMillis) + } + if (exitDesktopModeHintViewedTimestampMillis != null) { + setExitDesktopModeHintViewedTimestampMillis(exitDesktopModeHintViewedTimestampMillis) + } setAppHandleEducation( createAppHandleEducationProto(appUsageStats, appUsageStatsLastUpdateTimestampMillis)) } 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 b4791642663a..baccbee0893d 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 @@ -642,6 +642,7 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest eq(decor.mTaskInfo.taskId), any(), eq(DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON), + anyOrNull(), anyOrNull() ) } @@ -875,7 +876,7 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest ) verify(mockDesktopTasksController, times(1)) - .moveTaskToDesktop(any(), any(), any(), anyOrNull()) + .moveTaskToDesktop(any(), any(), any(), anyOrNull(), anyOrNull()) } @Test 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 908bc9952e99..c5c827467c75 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 @@ -69,6 +69,7 @@ import com.android.wm.shell.desktopmode.education.AppToWebEducationController import com.android.wm.shell.freeform.FreeformTaskTransitionStarter import com.android.wm.shell.recents.RecentsTransitionHandler import com.android.wm.shell.recents.RecentsTransitionStateListener +import com.android.wm.shell.shared.desktopmode.DesktopModeCompatPolicy import com.android.wm.shell.splitscreen.SplitScreenController import com.android.wm.shell.sysui.ShellCommandHandler import com.android.wm.shell.sysui.ShellController @@ -161,6 +162,7 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() { val display = mock<Display>() protected lateinit var spyContext: TestableContext private lateinit var desktopModeEventLogger: DesktopModeEventLogger + private lateinit var desktopModeCompatPolicy: DesktopModeCompatPolicy private val transactionFactory = Supplier<SurfaceControl.Transaction> { SurfaceControl.Transaction() @@ -188,6 +190,7 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() { whenever(mockDisplayController.getDisplay(any())).thenReturn(display) whenever(mockDesktopUserRepositories.getProfile(anyInt())) .thenReturn(mockDesktopRepository) + desktopModeCompatPolicy = DesktopModeCompatPolicy(context) desktopModeWindowDecorViewModel = DesktopModeWindowDecorViewModel( spyContext, testShellExecutor, @@ -230,6 +233,7 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() { mock<DesktopModeUiEventLogger>(), mock<WindowDecorTaskResourceLoader>(), mockRecentsTransitionHandler, + desktopModeCompatPolicy, ) desktopModeWindowDecorViewModel.setSplitScreenController(mockSplitScreenController) whenever(mockDisplayController.getDisplayLayout(any())).thenReturn(mockDisplayLayout) 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 9ea5fd6e1abe..87198d14c839 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 @@ -280,7 +280,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { mTestableContext = new TestableContext(mContext); mTestableContext.ensureTestableResources(); mContext.setMockPackageManager(mMockPackageManager); - when(mMockMultiInstanceHelper.supportsMultiInstanceSplit(any())) + when(mMockMultiInstanceHelper.supportsMultiInstanceSplit(any(), anyInt())) .thenReturn(false); when(mMockPackageManager.getApplicationLabel(any())).thenReturn("applicationLabel"); final ActivityInfo activityInfo = createActivityInfo(); @@ -295,7 +295,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean(), any(), anyInt(), anyInt(), anyInt(), anyInt())) .thenReturn(mMockHandleMenu); - when(mMockMultiInstanceHelper.supportsMultiInstanceSplit(any())).thenReturn(false); + when(mMockMultiInstanceHelper.supportsMultiInstanceSplit(any(), anyInt())) + .thenReturn(false); when(mMockAppHeaderViewHolderFactory.create(any(), any(), any(), any(), any(), any())) .thenReturn(mMockAppHeaderViewHolder); when(mMockDesktopUserRepositories.getCurrent()).thenReturn(mDesktopRepository); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt index 2207c705d7dc..0615c1d677ba 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt @@ -51,6 +51,7 @@ import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_UNDEFI import java.util.function.Supplier import junit.framework.Assert import org.junit.Before +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock @@ -206,6 +207,7 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() { } @Test + @Ignore("Causing presubmit failure b/391717499") fun testDragResize_movesTask_doesNotShowResizeVeil() = runOnUiThread { taskPositioner.onDragPositioningStart( CTRL_TYPE_UNDEFINED, @@ -245,6 +247,7 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() { } @Test + @Ignore("Causing presubmit failure b/391717499") fun testDragResize_movesTaskToNewDisplay() = runOnUiThread { taskPositioner.onDragPositioningStart( CTRL_TYPE_UNDEFINED, @@ -370,6 +373,7 @@ class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() { } @Test + @Ignore("Causing presubmit failure b/391717499") fun testDragResize_drag_setBoundsNotRunIfDragEndsInDisallowedEndArea() = runOnUiThread { taskPositioner.onDragPositioningStart( CTRL_TYPE_UNDEFINED, // drag diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoaderTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoaderTest.kt index 1ec0fe794d0a..431de896f433 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoaderTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoaderTest.kt @@ -33,6 +33,7 @@ import com.android.launcher3.icons.IconProvider import com.android.wm.shell.ShellTestCase import com.android.wm.shell.TestRunningTaskInfoBuilder import com.android.wm.shell.TestShellExecutor +import com.android.wm.shell.common.UserProfileContexts import com.android.wm.shell.sysui.ShellController import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.sysui.UserChangeListener @@ -69,6 +70,7 @@ class WindowDecorTaskResourceLoaderTest : ShellTestCase() { private val mockIconProvider = mock<IconProvider>() private val mockHeaderIconFactory = mock<BaseIconFactory>() private val mockVeilIconFactory = mock<BaseIconFactory>() + private val mMockUserProfileContexts = mock<UserProfileContexts>() private lateinit var spyContext: TestableContext private lateinit var loader: WindowDecorTaskResourceLoader @@ -83,12 +85,13 @@ class WindowDecorTaskResourceLoaderTest : ShellTestCase() { spyContext = spy(mContext) spyContext.setMockPackageManager(mockPackageManager) doReturn(spyContext).whenever(spyContext).createContextAsUser(any(), anyInt()) + doReturn(spyContext).whenever(mMockUserProfileContexts)[anyInt()] loader = WindowDecorTaskResourceLoader( - context = spyContext, shellInit = shellInit, shellController = mockShellController, shellCommandHandler = mock(), + userProfilesContexts = mMockUserProfileContexts, iconProvider = mockIconProvider, headerIconFactory = mockHeaderIconFactory, veilIconFactory = mockVeilIconFactory, @@ -170,16 +173,6 @@ class WindowDecorTaskResourceLoaderTest : ShellTestCase() { } @Test - fun testUserChange_updatesContext() { - val newUser = 5000 - val newContext = mock<Context>() - - userChangeListener.onUserChanged(newUser, newContext) - - assertThat(loader.currentUserContext).isEqualTo(newContext) - } - - @Test fun testUserChange_clearsCache() { val newUser = 5000 val newContext = mock<Context>() 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 997ece6ecadc..2cabb9a33b86 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 @@ -23,6 +23,7 @@ import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.ShellTestCase import com.android.wm.shell.common.DisplayController +import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.common.SyncTransactionQueue import com.android.wm.shell.desktopmode.DesktopModeEventLogger import com.android.wm.shell.desktopmode.DesktopUserRepositories @@ -30,6 +31,7 @@ import com.android.wm.shell.desktopmode.DesktopTasksController import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator import com.android.wm.shell.desktopmode.ToggleResizeDesktopTaskTransitionHandler +import com.android.wm.shell.transition.FocusTransitionObserver import com.android.wm.shell.transition.Transitions import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration import com.android.wm.shell.windowdecor.common.WindowDecorTaskResourceLoader @@ -67,6 +69,8 @@ class DesktopTilingDecorViewModelTest : ShellTestCase() { private val desktopModeWindowDecorationMock: DesktopModeWindowDecoration = mock() private val desktopTilingDecoration: DesktopTilingWindowDecoration = mock() private val taskResourceLoader: WindowDecorTaskResourceLoader = mock() + private val focusTransitionObserver: FocusTransitionObserver = mock() + private val mainExecutor: ShellExecutor = mock() private lateinit var desktopTilingDecorViewModel: DesktopTilingDecorViewModel @Before @@ -86,6 +90,8 @@ class DesktopTilingDecorViewModelTest : ShellTestCase() { userRepositories, desktopModeEventLogger, taskResourceLoader, + focusTransitionObserver, + mainExecutor ) whenever(contextMock.createContextAsUser(any(), any())).thenReturn(contextMock) } @@ -140,7 +146,7 @@ class DesktopTilingDecorViewModelTest : ShellTestCase() { desktopTilingDecorViewModel.moveTaskToFrontIfTiled(task1) verify(desktopTilingDecoration, times(1)) - .moveTiledPairToFront(any(), isTaskFocused = eq(true)) + .moveTiledPairToFront(any(), isFocusedOnDisplay = eq(true)) } @Test 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 2f15c2e38855..399a51e1ed08 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 @@ -33,6 +33,7 @@ import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.ShellTestCase import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.DisplayLayout +import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.common.SyncTransactionQueue import com.android.wm.shell.desktopmode.DesktopModeEventLogger import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger @@ -42,6 +43,7 @@ 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.FocusTransitionObserver import com.android.wm.shell.transition.Transitions import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration import com.android.wm.shell.windowdecor.DragResizeWindowGeometry @@ -105,6 +107,8 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() { private val mainDispatcher: MainCoroutineDispatcher = mock() private val bgScope: CoroutineScope = mock() private val taskResourceLoader: WindowDecorTaskResourceLoader = mock() + private val focusTransitionObserver: FocusTransitionObserver = mock() + private val mainExecutor: ShellExecutor = mock() private lateinit var tilingDecoration: DesktopTilingWindowDecoration private val split_divider_width = 10 @@ -129,6 +133,8 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() { returnToDragStartAnimator, userRepositories, desktopModeEventLogger, + focusTransitionObserver, + mainExecutor ) whenever(context.createContextAsUser(any(), any())).thenReturn(context) whenever(userRepositories.current).thenReturn(desktopRepository) @@ -242,7 +248,7 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() { BOUNDS, ) - assertThat(tilingDecoration.moveTiledPairToFront(task2)).isFalse() + assertThat(tilingDecoration.moveTiledPairToFront(task2.taskId, false)).isFalse() verify(transitions, never()).startTransition(any(), any(), any()) } @@ -272,7 +278,7 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() { BOUNDS, ) - assertThat(tilingDecoration.moveTiledPairToFront(task3)).isFalse() + assertThat(tilingDecoration.moveTiledPairToFront(task3.taskId, false)).isFalse() verify(transitions, never()).startTransition(any(), any(), any()) } @@ -304,7 +310,7 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() { ) task1.isFocused = true - assertThat(tilingDecoration.moveTiledPairToFront(task1, isTaskFocused = true)).isTrue() + assertThat(tilingDecoration.moveTiledPairToFront(task1.taskId, isFocusedOnDisplay = true)).isTrue() verify(transitions, times(1)).startTransition(eq(TRANSIT_TO_FRONT), any(), eq(null)) } @@ -336,8 +342,8 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() { task1.isFocused = true task3.isFocused = true - assertThat(tilingDecoration.moveTiledPairToFront(task3)).isFalse() - assertThat(tilingDecoration.moveTiledPairToFront(task1)).isTrue() + assertThat(tilingDecoration.moveTiledPairToFront(task3.taskId, true)).isFalse() + assertThat(tilingDecoration.moveTiledPairToFront(task1.taskId, true)).isTrue() verify(transitions, times(1)).startTransition(eq(TRANSIT_TO_FRONT), any(), eq(null)) } @@ -367,8 +373,8 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() { BOUNDS, ) - assertThat(tilingDecoration.moveTiledPairToFront(task3, isTaskFocused = true)).isFalse() - assertThat(tilingDecoration.moveTiledPairToFront(task1, isTaskFocused = true)).isTrue() + assertThat(tilingDecoration.moveTiledPairToFront(task3.taskId, isFocusedOnDisplay = true)).isFalse() + assertThat(tilingDecoration.moveTiledPairToFront(task1.taskId, isFocusedOnDisplay = true)).isTrue() verify(transitions, times(1)).startTransition(eq(TRANSIT_TO_FRONT), any(), eq(null)) } diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index 677fd86aca9c..53d3b77f1ba2 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -206,6 +206,9 @@ java_sdk_library { visibility: [ "//frameworks/base", // Framework ], + impl_library_visibility: [ + "//frameworks/base/ravenwood", + ], srcs: [ ":framework-graphics-srcs", diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 52eae43f7db9..71013f7f4e34 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -21,6 +21,8 @@ import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO; import static android.content.Context.DEVICE_ID_DEFAULT; import static android.media.audio.Flags.autoPublicVolumeApiHardening; import static android.media.audio.Flags.automaticBtDeviceType; +import static android.media.audio.Flags.cacheGetStreamMinMaxVolume; +import static android.media.audio.Flags.cacheGetStreamVolume; import static android.media.audio.Flags.FLAG_DEPRECATE_STREAM_BT_SCO; import static android.media.audio.Flags.FLAG_FOCUS_EXCLUSIVE_WITH_RECORDING; import static android.media.audio.Flags.FLAG_FOCUS_FREEZE_TEST_API; @@ -58,7 +60,6 @@ import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.media.AudioAttributes.AttributeSystemUsage; -import android.media.AudioDeviceInfo; import android.media.CallbackUtil.ListenerInfo; import android.media.audiopolicy.AudioPolicy; import android.media.audiopolicy.AudioPolicy.AudioPolicyFocusListener; @@ -75,6 +76,7 @@ import android.os.Binder; import android.os.Build; import android.os.Handler; import android.os.IBinder; +import android.os.IpcDataCache; import android.os.Looper; import android.os.Message; import android.os.RemoteException; @@ -1231,6 +1233,102 @@ public class AudioManager { } /** + * API string for caching the min volume for each stream + * @hide + **/ + public static final String VOLUME_MIN_CACHING_API = "getStreamMinVolume"; + /** + * API string for caching the max volume for each stream + * @hide + **/ + public static final String VOLUME_MAX_CACHING_API = "getStreamMaxVolume"; + /** + * API string for caching the volume for each stream + * @hide + **/ + public static final String VOLUME_CACHING_API = "getStreamVolume"; + private static final int VOLUME_CACHING_SIZE = 16; + + private final IpcDataCache.QueryHandler<VolumeCacheQuery, Integer> mVolQuery = + new IpcDataCache.QueryHandler<>() { + @Override + public Integer apply(VolumeCacheQuery query) { + final IAudioService service = getService(); + try { + return switch (query.queryCommand) { + case QUERY_VOL_MIN -> service.getStreamMinVolume(query.stream); + case QUERY_VOL_MAX -> service.getStreamMaxVolume(query.stream); + case QUERY_VOL -> service.getStreamVolume(query.stream); + default -> { + Log.w(TAG, "Not a valid volume cache query: " + query); + yield null; + } + }; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + }; + + private final IpcDataCache<VolumeCacheQuery, Integer> mVolMinCache = + new IpcDataCache<>(VOLUME_CACHING_SIZE, IpcDataCache.MODULE_SYSTEM, + VOLUME_MIN_CACHING_API, VOLUME_MIN_CACHING_API, mVolQuery); + + private final IpcDataCache<VolumeCacheQuery, Integer> mVolMaxCache = + new IpcDataCache<>(VOLUME_CACHING_SIZE, IpcDataCache.MODULE_SYSTEM, + VOLUME_MAX_CACHING_API, VOLUME_MAX_CACHING_API, mVolQuery); + + private final IpcDataCache<VolumeCacheQuery, Integer> mVolCache = + new IpcDataCache<>(VOLUME_CACHING_SIZE, IpcDataCache.MODULE_SYSTEM, + VOLUME_CACHING_API, VOLUME_CACHING_API, mVolQuery); + + /** + * Used to invalidate the cache for the given API + * @hide + **/ + public static void clearVolumeCache(String api) { + if (cacheGetStreamMinMaxVolume() && (VOLUME_MAX_CACHING_API.equals(api) + || VOLUME_MIN_CACHING_API.equals(api))) { + IpcDataCache.invalidateCache(IpcDataCache.MODULE_SYSTEM, api); + } else if (cacheGetStreamVolume() && VOLUME_CACHING_API.equals(api)) { + IpcDataCache.invalidateCache(IpcDataCache.MODULE_SYSTEM, api); + } else { + Log.w(TAG, "invalid clearVolumeCache for api " + api); + } + } + + private static final int QUERY_VOL_MIN = 1; + private static final int QUERY_VOL_MAX = 2; + private static final int QUERY_VOL = 3; + /** @hide */ + @IntDef(prefix = "QUERY_VOL", value = { + QUERY_VOL_MIN, + QUERY_VOL_MAX, + QUERY_VOL} + ) + @Retention(RetentionPolicy.SOURCE) + private @interface QueryVolCommand {} + + private record VolumeCacheQuery(int stream, @QueryVolCommand int queryCommand) { + private String queryVolCommandToString() { + return switch (queryCommand) { + case QUERY_VOL_MIN -> "getStreamMinVolume"; + case QUERY_VOL_MAX -> "getStreamMaxVolume"; + case QUERY_VOL -> "getStreamVolume"; + default -> "invalid command"; + }; + } + + @NonNull + @Override + public String toString() { + return TextUtils.formatSimple("VolumeCacheQuery(stream=%d, queryCommand=%s)", stream, + queryVolCommandToString()); + } + } + + /** * Returns the maximum volume index for a particular stream. * * @param streamType The stream type whose maximum volume index is returned. @@ -1238,6 +1336,9 @@ public class AudioManager { * @see #getStreamVolume(int) */ public int getStreamMaxVolume(int streamType) { + if (cacheGetStreamMinMaxVolume()) { + return mVolMaxCache.query(new VolumeCacheQuery(streamType, QUERY_VOL_MAX)); + } final IAudioService service = getService(); try { return service.getStreamMaxVolume(streamType); @@ -1271,6 +1372,9 @@ public class AudioManager { */ @TestApi public int getStreamMinVolumeInt(int streamType) { + if (cacheGetStreamMinMaxVolume()) { + return mVolMinCache.query(new VolumeCacheQuery(streamType, QUERY_VOL_MIN)); + } final IAudioService service = getService(); try { return service.getStreamMinVolume(streamType); @@ -1288,6 +1392,9 @@ public class AudioManager { * @see #setStreamVolume(int, int, int) */ public int getStreamVolume(int streamType) { + if (cacheGetStreamVolume()) { + return mVolCache.query(new VolumeCacheQuery(streamType, QUERY_VOL)); + } final IAudioService service = getService(); try { return service.getStreamVolume(streamType); diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java index c9625c405faa..c4886836f451 100644 --- a/media/java/android/media/MediaCodec.java +++ b/media/java/android/media/MediaCodec.java @@ -20,6 +20,8 @@ import static android.media.codec.Flags.FLAG_CODEC_AVAILABILITY; import static android.media.codec.Flags.FLAG_NULL_OUTPUT_SURFACE; import static android.media.codec.Flags.FLAG_REGION_OF_INTEREST; import static android.media.codec.Flags.FLAG_SUBSESSION_METRICS; +import static android.media.tv.flags.Flags.applyPictureProfiles; +import static android.media.tv.flags.Flags.mediaQualityFw; import static com.android.media.codec.flags.Flags.FLAG_LARGE_AUDIO_FRAME; @@ -37,6 +39,8 @@ import android.graphics.Rect; import android.graphics.SurfaceTexture; import android.hardware.HardwareBuffer; import android.media.MediaCodecInfo.CodecCapabilities; +import android.media.quality.PictureProfile; +import android.media.quality.PictureProfileHandle; import android.os.Build; import android.os.Bundle; import android.os.Handler; @@ -5370,6 +5374,9 @@ final public class MediaCodec { * @param params The bundle of parameters to set. * @throws IllegalStateException if in the Released state. */ + + private static final String PARAMETER_KEY_PICTURE_PROFILE_HANDLE = "picture-profile-handle"; + public final void setParameters(@Nullable Bundle params) { if (params == null) { return; @@ -5383,19 +5390,41 @@ final public class MediaCodec { if (key.equals(MediaFormat.KEY_AUDIO_SESSION_ID)) { int sessionId = 0; try { - sessionId = (Integer)params.get(key); + sessionId = (Integer) params.get(key); } catch (Exception e) { throw new IllegalArgumentException("Wrong Session ID Parameter!"); } keys[i] = "audio-hw-sync"; values[i] = AudioSystem.getAudioHwSyncForSession(sessionId); + } else if (applyPictureProfiles() && mediaQualityFw() + && key.equals(MediaFormat.KEY_PICTURE_PROFILE_INSTANCE)) { + PictureProfile pictureProfile = null; + try { + pictureProfile = (PictureProfile) params.get(key); + } catch (ClassCastException e) { + throw new IllegalArgumentException( + "Cannot cast the instance parameter to PictureProfile!"); + } catch (Exception e) { + android.util.Log.getStackTraceString(e); + throw new IllegalArgumentException("Unexpected exception when casting the " + + "instance parameter to PictureProfile!"); + } + if (pictureProfile == null) { + throw new IllegalArgumentException( + "Picture profile instance parameter is null!"); + } + PictureProfileHandle handle = pictureProfile.getHandle(); + if (handle != PictureProfileHandle.NONE) { + keys[i] = PARAMETER_KEY_PICTURE_PROFILE_HANDLE; + values[i] = Long.valueOf(handle.getId()); + } } else { keys[i] = key; Object value = params.get(key); // Bundle's byte array is a byte[], JNI layer only takes ByteBuffer if (value instanceof byte[]) { - values[i] = ByteBuffer.wrap((byte[])value); + values[i] = ByteBuffer.wrap((byte[]) value); } else { values[i] = value; } diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java index e57148fe5a6a..3af36a404c30 100644 --- a/media/java/android/media/MediaRouter2.java +++ b/media/java/android/media/MediaRouter2.java @@ -281,7 +281,7 @@ public final class MediaRouter2 { /* executor */ null, /* onInstanceInvalidatedListener */ null); } catch (IllegalArgumentException ex) { - Log.e(TAG, "Package " + clientPackageName + " not found. Ignoring."); + Log.e(TAG, "Failed to create proxy router for package '" + clientPackageName + "'", ex); return null; } } diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig index 312f78e2b24e..94454cf9ab9b 100644 --- a/media/java/android/media/flags/media_better_together.aconfig +++ b/media/java/android/media/flags/media_better_together.aconfig @@ -173,11 +173,10 @@ flag { bug: "281072508" } - flag { - name: "enable_singleton_audio_manager_route_controller" + name: "enable_use_of_singleton_audio_manager_route_controller" is_exported: true - namespace: "media_solutions" + namespace: "media_better_together" description: "Use singleton AudioManagerRouteController shared across all users." bug: "372868909" metadata { diff --git a/media/java/android/media/flags/projection.aconfig b/media/java/android/media/flags/projection.aconfig index fa1349c61c4c..6d4f0b4f47d5 100644 --- a/media/java/android/media/flags/projection.aconfig +++ b/media/java/android/media/flags/projection.aconfig @@ -29,3 +29,13 @@ flag { is_exported: true } +flag { + namespace: "media_projection" + name: "show_stop_dialog_post_call_end" + description: "Shows a stop dialog for MediaProjection sessions that started during call and remain active after a call ends" + bug: "390343524" + metadata { + purpose: PURPOSE_BUGFIX + } + is_exported: true +} diff --git a/media/java/android/media/projection/IMediaProjectionWatcherCallback.aidl b/media/java/android/media/projection/IMediaProjectionWatcherCallback.aidl index e46d34e81483..3baf4d7efd65 100644 --- a/media/java/android/media/projection/IMediaProjectionWatcherCallback.aidl +++ b/media/java/android/media/projection/IMediaProjectionWatcherCallback.aidl @@ -18,6 +18,7 @@ package android.media.projection; import android.media.projection.MediaProjectionInfo; import android.view.ContentRecordingSession; +import android.media.projection.MediaProjectionEvent; /** {@hide} */ oneway interface IMediaProjectionWatcherCallback { @@ -35,4 +36,19 @@ oneway interface IMediaProjectionWatcherCallback { in MediaProjectionInfo info, in @nullable ContentRecordingSession session ); + + /** + * Called when a specific {@link MediaProjectionEvent} occurs during the media projection session. + * + * @param event contains the event type, which describes the nature/context of the event. + * @param info optional {@link MediaProjectionInfo} containing details about the media + projection host. + * @param session the recording session for the current media projection. Can be + * {@code null} when the recording will stop. + */ + void onMediaProjectionEvent( + in MediaProjectionEvent event, + in @nullable MediaProjectionInfo info, + in @nullable ContentRecordingSession session + ); } diff --git a/media/java/android/media/projection/MediaProjectionEvent.aidl b/media/java/android/media/projection/MediaProjectionEvent.aidl new file mode 100644 index 000000000000..34359900ce81 --- /dev/null +++ b/media/java/android/media/projection/MediaProjectionEvent.aidl @@ -0,0 +1,3 @@ +package android.media.projection; + +parcelable MediaProjectionEvent;
\ No newline at end of file diff --git a/media/java/android/media/projection/MediaProjectionEvent.java b/media/java/android/media/projection/MediaProjectionEvent.java new file mode 100644 index 000000000000..6922560c8abe --- /dev/null +++ b/media/java/android/media/projection/MediaProjectionEvent.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.projection; + +import android.annotation.IntDef; +import android.os.Parcel; +import android.os.Parcelable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Objects; + +/** @hide */ +public final class MediaProjectionEvent implements Parcelable { + + /** + * Represents various media projection events. + */ + @IntDef({PROJECTION_STARTED_DURING_CALL_AND_ACTIVE_POST_CALL}) + @Retention(RetentionPolicy.SOURCE) + public @interface EventType {} + + /** Event type for when a call ends but the session is still active. */ + public static final int PROJECTION_STARTED_DURING_CALL_AND_ACTIVE_POST_CALL = 0; + + private final @EventType int mEventType; + private final long mTimestampMillis; + + public MediaProjectionEvent(@EventType int eventType, long timestampMillis) { + mEventType = eventType; + mTimestampMillis = timestampMillis; + } + + private MediaProjectionEvent(Parcel in) { + mEventType = in.readInt(); + mTimestampMillis = in.readLong(); + } + + public @EventType int getEventType() { + return mEventType; + } + + public long getTimestampMillis() { + return mTimestampMillis; + } + + @Override + public boolean equals(Object o) { + if (o instanceof MediaProjectionEvent other) { + return mEventType == other.mEventType && mTimestampMillis == other.mTimestampMillis; + } + return false; + } + + @Override + public int hashCode() { + return Objects.hash(mEventType, mTimestampMillis); + } + + @Override + public String toString() { + return "MediaProjectionEvent{mEventType=" + mEventType + ", mTimestampMillis=" + + mTimestampMillis + "}"; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(mEventType); + out.writeLong(mTimestampMillis); + } + + public static final Parcelable.Creator<MediaProjectionEvent> CREATOR = + new Parcelable.Creator<>() { + @Override + public MediaProjectionEvent createFromParcel(Parcel in) { + return new MediaProjectionEvent(in); + } + + @Override + public MediaProjectionEvent[] newArray(int size) { + return new MediaProjectionEvent[size]; + } + }; +} diff --git a/media/java/android/media/projection/MediaProjectionManager.java b/media/java/android/media/projection/MediaProjectionManager.java index 9cc2cca441a4..9036bf385d96 100644 --- a/media/java/android/media/projection/MediaProjectionManager.java +++ b/media/java/android/media/projection/MediaProjectionManager.java @@ -363,6 +363,19 @@ public final class MediaProjectionManager { @Nullable ContentRecordingSession session ) { } + + /** + * Called when a specific {@link MediaProjectionEvent} occurs during the media projection + * session. + * + * @param event the media projection event details. + * @param info optional details about the media projection host. + * @param session optional associated recording session details. + */ + public void onMediaProjectionEvent( + final MediaProjectionEvent event, + @Nullable MediaProjectionInfo info, + @Nullable final ContentRecordingSession session) {} } /** @hide */ @@ -405,5 +418,13 @@ public final class MediaProjectionManager { ) { mHandler.post(() -> mCallback.onRecordingSessionSet(info, session)); } + + @Override + public void onMediaProjectionEvent( + final MediaProjectionEvent event, + @Nullable MediaProjectionInfo info, + @Nullable final ContentRecordingSession session) { + mHandler.post(() -> mCallback.onMediaProjectionEvent(event, info, session)); + } } } diff --git a/media/java/android/media/quality/MediaQualityContract.java b/media/java/android/media/quality/MediaQualityContract.java index d1f63404dbff..e558209420e0 100644 --- a/media/java/android/media/quality/MediaQualityContract.java +++ b/media/java/android/media/quality/MediaQualityContract.java @@ -385,6 +385,332 @@ public class MediaQualityContract { public static final String PARAMETER_AUTO_SUPER_RESOLUTION_ENABLED = "auto_super_resolution_enabled"; + /** + * @hide + * + */ + public static final String PARAMETER_LEVEL_RANGE = "level_range"; + + /** + * @hide + * + */ + public static final String PARAMETER_GAMUT_MAPPING = "gamut_mapping"; + + /** + * @hide + * + */ + public static final String PARAMETER_PC_MODE = "pc_mode"; + + /** + * @hide + * + */ + public static final String PARAMETER_LOW_LATENCY = "low_latency"; + + /** + * @hide + * + */ + public static final String PARAMETER_VRR = "vrr"; + + /** + * @hide + * + */ + public static final String PARAMETER_CVRR = "cvrr"; + + /** + * @hide + * + */ + public static final String PARAMETER_HDMI_RGB_RANGE = "hdmi_rgb_range"; + + /** + * @hide + * + */ + public static final String PARAMETER_COLOR_SPACE = "color_space"; + + /** + * @hide + * + */ + public static final String PARAMETER_PANEL_INIT_MAX_LUMINCE_NITS = + "panel_init_max_lumince_nits"; + + /** + * @hide + * + */ + public static final String PARAMETER_PANEL_INIT_MAX_LUMINCE_VALID = + "panel_init_max_lumince_valid"; + + /** + * @hide + * + */ + public static final String PARAMETER_GAMMA = "gamma"; + + /** + * @hide + * + */ + public static final String PARAMETER_COLOR_TEMPERATURE_RED_OFFSET = + "color_temperature_red_offset"; + + /** + * @hide + * + */ + public static final String PARAMETER_COLOR_TEMPERATURE_GREEN_OFFSET = + "color_temperature_green_offset"; + + /** + * @hide + * + */ + public static final String PARAMETER_COLOR_TEMPERATURE_BLUE_OFFSET = + "color_temperature_blue_offset"; + + /** + * @hide + * + */ + public static final String PARAMETER_ELEVEN_POINT_RED = "eleven_point_red"; + + /** + * @hide + * + */ + public static final String PARAMETER_ELEVEN_POINT_GREEN = "eleven_point_green"; + + /** + * @hide + * + */ + public static final String PARAMETER_ELEVEN_POINT_BLUE = "eleven_point_blue"; + + /** + * @hide + * + */ + public static final String PARAMETER_LOW_BLUE_LIGHT = "low_blue_light"; + + /** + * @hide + * + */ + public static final String PARAMETER_LD_MODE = "ld_mode"; + + /** + * @hide + * + */ + public static final String PARAMETER_OSD_RED_GAIN = "osd_red_gain"; + + /** + * @hide + * + */ + public static final String PARAMETER_OSD_GREEN_GAIN = "osd_green_gain"; + + /** + * @hide + * + */ + public static final String PARAMETER_OSD_BLUE_GAIN = "osd_blue_gain"; + + /** + * @hide + * + */ + public static final String PARAMETER_OSD_RED_OFFSET = "osd_red_offset"; + + /** + * @hide + * + */ + public static final String PARAMETER_OSD_GREEN_OFFSET = "osd_green_offset"; + + /** + * @hide + * + */ + public static final String PARAMETER_OSD_BLUE_OFFSET = "osd_blue_offset"; + + /** + * @hide + * + */ + public static final String PARAMETER_OSD_HUE = "osd_hue"; + + /** + * @hide + * + */ + public static final String PARAMETER_OSD_SATURATION = "osd_saturation"; + + /** + * @hide + * + */ + public static final String PARAMETER_OSD_CONTRAST = "osd_contrast"; + + /** + * @hide + * + */ + public static final String PARAMETER_COLOR_TUNER_SWITCH = "color_tuner_switch"; + + /** + * @hide + * + */ + public static final String PARAMETER_COLOR_TUNER_HUE_RED = "color_tuner_hue_red"; + + /** + * @hide + * + */ + public static final String PARAMETER_COLOR_TUNER_HUE_GREEN = "color_tuner_hue_green"; + + /** + * @hide + * + */ + public static final String PARAMETER_COLOR_TUNER_HUE_BLUE = "color_tuner_hue_blue"; + + /** + * @hide + * + */ + public static final String PARAMETER_COLOR_TUNER_HUE_CYAN = "color_tuner_hue_cyan"; + + /** + * @hide + * + */ + public static final String PARAMETER_COLOR_TUNER_HUE_MAGENTA = "color_tuner_hue_magenta"; + + /** + * @hide + * + */ + public static final String PARAMETER_COLOR_TUNER_HUE_YELLOW = "color_tuner_hue_yellow"; + + /** + * @hide + * + */ + public static final String PARAMETER_COLOR_TUNER_HUE_FLESH = "color_tuner_hue_flesh"; + + /** + * @hide + * + */ + public static final String PARAMETER_COLOR_TUNER_SATURATION_RED = + "color_tuner_saturation_red"; + + /** + * @hide + * + */ + public static final String PARAMETER_COLOR_TUNER_SATURATION_GREEN = + "color_tuner_saturation_green"; + + /** + * @hide + * + */ + public static final String PARAMETER_COLOR_TUNER_SATURATION_BLUE = + "color_tuner_saturation_blue"; + + /** + * @hide + * + */ + public static final String PARAMETER_COLOR_TUNER_SATURATION_CYAN = + "color_tuner_saturation_cyan"; + + /** + * @hide + * + */ + public static final String PARAMETER_COLOR_TUNER_SATURATION_MAGENTA = + "color_tuner_saturation_magenta"; + + /** + * @hide + * + */ + public static final String PARAMETER_COLOR_TUNER_SATURATION_YELLOW = + "color_tuner_saturation_yellow"; + + /** + * @hide + * + */ + public static final String PARAMETER_COLOR_TUNER_SATURATION_FLESH = + "color_tuner_saturation_flesh"; + + /** + * @hide + * + */ + public static final String PARAMETER_COLOR_TUNER_LUMINANCE_RED = + "color_tuner_luminance_red"; + + /** + * @hide + * + */ + public static final String PARAMETER_COLOR_TUNER_LUMINANCE_GREEN = + "color_tuner_luminance_green"; + + /** + * @hide + * + */ + public static final String PARAMETER_COLOR_TUNER_LUMINANCE_BLUE = + "color_tuner_luminance_blue"; + + /** + * @hide + * + */ + public static final String PARAMETER_COLOR_TUNER_LUMINANCE_CYAN = + "color_tuner_luminance_cyan"; + + /** + * @hide + * + */ + public static final String PARAMETER_COLOR_TUNER_LUMINANCE_MAGENTA = + "color_tuner_luminance_magenta"; + + /** + * @hide + * + */ + public static final String PARAMETER_COLOR_TUNER_LUMINANCE_YELLOW = + "color_tuner_luminance_yellow"; + + /** + * @hide + * + */ + public static final String PARAMETER_COLOR_TUNER_LUMINANCE_FLESH = + "color_tuner_luminance_flesh"; + + /** + * @hide + * + */ + public static final String PARAMETER_PICTURE_QUALITY_EVENT_TYPE = + "picture_quality_event_type"; + private PictureQuality() { } } @@ -641,6 +967,12 @@ public class MediaQualityContract { */ public static final String PARAMETER_DIGITAL_OUTPUT_MODE = "digital_output_mode"; + /** + * @hide + */ + public static final String PARAMETER_SOUND_STYLE = "sound_style"; + + private SoundQuality() { } diff --git a/media/java/android/media/quality/MediaQualityManager.java b/media/java/android/media/quality/MediaQualityManager.java index b7269256a449..0d6d32a22dae 100644 --- a/media/java/android/media/quality/MediaQualityManager.java +++ b/media/java/android/media/quality/MediaQualityManager.java @@ -51,7 +51,6 @@ import java.util.function.Consumer; @FlaggedApi(Flags.FLAG_MEDIA_QUALITY_FW) @SystemService(Context.MEDIA_QUALITY_SERVICE) public final class MediaQualityManager { - // TODO: unhide the APIs for api review private static final String TAG = "MediaQualityManager"; private final IMediaQualityManager mService; @@ -123,7 +122,6 @@ public final class MediaQualityManager { public void onPictureProfileAdded(String profileId, PictureProfile profile) { synchronized (mPpLock) { for (PictureProfileCallbackRecord record : mPpCallbackRecords) { - // TODO: filter callback record record.postPictureProfileAdded(profileId, profile); } } @@ -132,7 +130,6 @@ public final class MediaQualityManager { public void onPictureProfileUpdated(String profileId, PictureProfile profile) { synchronized (mPpLock) { for (PictureProfileCallbackRecord record : mPpCallbackRecords) { - // TODO: filter callback record record.postPictureProfileUpdated(profileId, profile); } } @@ -141,7 +138,6 @@ public final class MediaQualityManager { public void onPictureProfileRemoved(String profileId, PictureProfile profile) { synchronized (mPpLock) { for (PictureProfileCallbackRecord record : mPpCallbackRecords) { - // TODO: filter callback record record.postPictureProfileRemoved(profileId, profile); } } @@ -151,7 +147,6 @@ public final class MediaQualityManager { String profileId, List<ParameterCapability> caps) { synchronized (mPpLock) { for (PictureProfileCallbackRecord record : mPpCallbackRecords) { - // TODO: filter callback record record.postParameterCapabilitiesChanged(profileId, caps); } } @@ -160,7 +155,6 @@ public final class MediaQualityManager { public void onError(String profileId, int err) { synchronized (mPpLock) { for (PictureProfileCallbackRecord record : mPpCallbackRecords) { - // TODO: filter callback record record.postError(profileId, err); } } @@ -171,7 +165,6 @@ public final class MediaQualityManager { public void onSoundProfileAdded(String profileId, SoundProfile profile) { synchronized (mSpLock) { for (SoundProfileCallbackRecord record : mSpCallbackRecords) { - // TODO: filter callback record record.postSoundProfileAdded(profileId, profile); } } @@ -180,7 +173,6 @@ public final class MediaQualityManager { public void onSoundProfileUpdated(String profileId, SoundProfile profile) { synchronized (mSpLock) { for (SoundProfileCallbackRecord record : mSpCallbackRecords) { - // TODO: filter callback record record.postSoundProfileUpdated(profileId, profile); } } @@ -189,7 +181,6 @@ public final class MediaQualityManager { public void onSoundProfileRemoved(String profileId, SoundProfile profile) { synchronized (mSpLock) { for (SoundProfileCallbackRecord record : mSpCallbackRecords) { - // TODO: filter callback record record.postSoundProfileRemoved(profileId, profile); } } @@ -199,7 +190,6 @@ public final class MediaQualityManager { String profileId, List<ParameterCapability> caps) { synchronized (mSpLock) { for (SoundProfileCallbackRecord record : mSpCallbackRecords) { - // TODO: filter callback record record.postParameterCapabilitiesChanged(profileId, caps); } } @@ -208,7 +198,6 @@ public final class MediaQualityManager { public void onError(String profileId, int err) { synchronized (mSpLock) { for (SoundProfileCallbackRecord record : mSpCallbackRecords) { - // TODO: filter callback record record.postError(profileId, err); } } diff --git a/media/java/android/mtp/OWNERS b/media/java/android/mtp/OWNERS index 77ed08b1f9a5..c57265a91eff 100644 --- a/media/java/android/mtp/OWNERS +++ b/media/java/android/mtp/OWNERS @@ -1,9 +1,9 @@ set noparent -anothermark@google.com +vmartensson@google.com +nkapron@google.com febinthattil@google.com -aprasath@google.com +shubhankarm@google.com jsharkey@android.com jameswei@google.com rmojumder@google.com -kumarashishg@google.com diff --git a/media/jni/android_mtp_MtpDatabase.cpp b/media/jni/android_mtp_MtpDatabase.cpp index a77bc9fe0570..a1ce495fe33d 100644 --- a/media/jni/android_mtp_MtpDatabase.cpp +++ b/media/jni/android_mtp_MtpDatabase.cpp @@ -910,7 +910,7 @@ MtpResponseCode MtpDatabase::getObjectInfo(MtpObjectHandle handle, case MTP_FORMAT_TIFF: case MTP_FORMAT_TIFF_EP: case MTP_FORMAT_DEFINED: { - String8 temp(path); + String8 temp {static_cast<std::string_view>(path)}; std::unique_ptr<FileStream> stream(new FileStream(temp)); piex::PreviewImageData image_data; if (!GetExifFromRawImage(stream.get(), temp, image_data)) { @@ -967,7 +967,7 @@ void* MtpDatabase::getThumbnail(MtpObjectHandle handle, size_t& outThumbSize) { case MTP_FORMAT_TIFF: case MTP_FORMAT_TIFF_EP: case MTP_FORMAT_DEFINED: { - String8 temp(path); + String8 temp {static_cast<std::string_view>(path)}; std::unique_ptr<FileStream> stream(new FileStream(temp)); piex::PreviewImageData image_data; if (!GetExifFromRawImage(stream.get(), temp, image_data)) { diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioManagerTest.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioManagerTest.java index e9a0d3eceba3..209734ca4a53 100644 --- a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioManagerTest.java +++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioManagerTest.java @@ -16,6 +16,15 @@ package com.android.audiopolicytest; +import static android.media.AudioManager.STREAM_ACCESSIBILITY; +import static android.media.AudioManager.STREAM_ALARM; +import static android.media.AudioManager.STREAM_DTMF; +import static android.media.AudioManager.STREAM_MUSIC; +import static android.media.AudioManager.STREAM_NOTIFICATION; +import static android.media.AudioManager.STREAM_RING; +import static android.media.AudioManager.STREAM_SYSTEM; +import static android.media.AudioManager.STREAM_VOICE_CALL; + import static androidx.test.core.app.ApplicationProvider.getApplicationContext; import static com.android.audiopolicytest.AudioVolumeTestUtil.DEFAULT_ATTRIBUTES; @@ -28,11 +37,15 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; +import android.content.Context; import android.media.AudioAttributes; import android.media.AudioManager; import android.media.AudioSystem; +import android.media.IAudioService; import android.media.audiopolicy.AudioProductStrategy; import android.media.audiopolicy.AudioVolumeGroup; +import android.os.IBinder; +import android.os.ServiceManager; import android.platform.test.annotations.Presubmit; import android.util.Log; @@ -54,6 +67,17 @@ public class AudioManagerTest { private AudioManager mAudioManager; + private static final int[] PUBLIC_STREAM_TYPES = { + STREAM_VOICE_CALL, + STREAM_SYSTEM, + STREAM_RING, + STREAM_MUSIC, + STREAM_ALARM, + STREAM_NOTIFICATION, + STREAM_DTMF, + STREAM_ACCESSIBILITY, + }; + @Rule public final AudioVolumesTestRule rule = new AudioVolumesTestRule(); @@ -207,6 +231,33 @@ public class AudioManagerTest { } //----------------------------------------------------------------- + // Test getStreamVolume consistency with AudioService + //----------------------------------------------------------------- + @Test + public void getStreamMinMaxVolume_consistentWithAs() throws Exception { + IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE); + IAudioService service = IAudioService.Stub.asInterface(b); + + for (int streamType : PUBLIC_STREAM_TYPES) { + assertEquals(service.getStreamMinVolume(streamType), + mAudioManager.getStreamMinVolume(streamType)); + assertEquals(service.getStreamMaxVolume(streamType), + mAudioManager.getStreamMaxVolume(streamType)); + } + } + + @Test + public void getStreamVolume_consistentWithAs() throws Exception { + IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE); + IAudioService service = IAudioService.Stub.asInterface(b); + + for (int streamType : PUBLIC_STREAM_TYPES) { + assertEquals(service.getStreamVolume(streamType), + mAudioManager.getStreamVolume(streamType)); + } + } + + //----------------------------------------------------------------- // Test Volume per Attributes setter/getters //----------------------------------------------------------------- @Test diff --git a/media/tests/MtpTests/OWNERS b/media/tests/MtpTests/OWNERS index bdb6cdbea332..c57265a91eff 100644 --- a/media/tests/MtpTests/OWNERS +++ b/media/tests/MtpTests/OWNERS @@ -1,9 +1,9 @@ set noparent -anothermark@google.com +vmartensson@google.com +nkapron@google.com febinthattil@google.com -aprasath@google.com +shubhankarm@google.com jsharkey@android.com jameswei@google.com rmojumder@google.com -kumarashishg@google.com
\ No newline at end of file diff --git a/packages/InputDevices/res/values-ar/strings.xml b/packages/InputDevices/res/values-ar/strings.xml index 65f3edb2604c..6d917da5b814 100644 --- a/packages/InputDevices/res/values-ar/strings.xml +++ b/packages/InputDevices/res/values-ar/strings.xml @@ -56,6 +56,5 @@ <string name="keyboard_layout_montenegrin_latin" msgid="1467832503378949945">"لغة الجبل الأسود (اللاتينية)"</string> <string name="keyboard_layout_serbian_cyrillic" msgid="7013541044323542196">"الصربية (السيريلية)"</string> <string name="keyboard_layout_montenegrin_cyrillic" msgid="2391253952894077421">"لغة الجبل الأسود (السيريلية)"</string> - <!-- no translation found for keyboard_layout_romanian (8698989892731726903) --> - <skip /> + <string name="keyboard_layout_romanian" msgid="8698989892731726903">"الرومانية"</string> </resources> diff --git a/packages/InputDevices/res/values-cs/strings.xml b/packages/InputDevices/res/values-cs/strings.xml index 6a46b1473089..c7a489672bb0 100644 --- a/packages/InputDevices/res/values-cs/strings.xml +++ b/packages/InputDevices/res/values-cs/strings.xml @@ -56,6 +56,5 @@ <string name="keyboard_layout_montenegrin_latin" msgid="1467832503378949945">"černohorština (latinka)"</string> <string name="keyboard_layout_serbian_cyrillic" msgid="7013541044323542196">"srbština (cyrilice)"</string> <string name="keyboard_layout_montenegrin_cyrillic" msgid="2391253952894077421">"černohorština (cyrilice)"</string> - <!-- no translation found for keyboard_layout_romanian (8698989892731726903) --> - <skip /> + <string name="keyboard_layout_romanian" msgid="8698989892731726903">"rumunština"</string> </resources> diff --git a/packages/InputDevices/res/values-iw/strings.xml b/packages/InputDevices/res/values-iw/strings.xml index 4cf8098fded7..85f9656dedde 100644 --- a/packages/InputDevices/res/values-iw/strings.xml +++ b/packages/InputDevices/res/values-iw/strings.xml @@ -56,6 +56,5 @@ <string name="keyboard_layout_montenegrin_latin" msgid="1467832503378949945">"מונטנגרית (לטינית)"</string> <string name="keyboard_layout_serbian_cyrillic" msgid="7013541044323542196">"סרבית (אותיות קיריליות)"</string> <string name="keyboard_layout_montenegrin_cyrillic" msgid="2391253952894077421">"מונטנגרית (אותיות קיריליות)"</string> - <!-- no translation found for keyboard_layout_romanian (8698989892731726903) --> - <skip /> + <string name="keyboard_layout_romanian" msgid="8698989892731726903">"רומנית"</string> </resources> diff --git a/packages/InputDevices/res/values-si/strings.xml b/packages/InputDevices/res/values-si/strings.xml index 373dfda87874..78d10101358e 100644 --- a/packages/InputDevices/res/values-si/strings.xml +++ b/packages/InputDevices/res/values-si/strings.xml @@ -56,6 +56,5 @@ <string name="keyboard_layout_montenegrin_latin" msgid="1467832503378949945">"මොන්ටෙනේග්රීන් (ලතින්)"</string> <string name="keyboard_layout_serbian_cyrillic" msgid="7013541044323542196">"සර්බියානු (සිරිලික්)"</string> <string name="keyboard_layout_montenegrin_cyrillic" msgid="2391253952894077421">"මොන්ටෙනේග්රීන් (සිරිලික්)"</string> - <!-- no translation found for keyboard_layout_romanian (8698989892731726903) --> - <skip /> + <string name="keyboard_layout_romanian" msgid="8698989892731726903">"රුමේනියානු"</string> </resources> diff --git a/packages/InputDevices/res/values-sq/strings.xml b/packages/InputDevices/res/values-sq/strings.xml index 36882cbf7986..066129e87769 100644 --- a/packages/InputDevices/res/values-sq/strings.xml +++ b/packages/InputDevices/res/values-sq/strings.xml @@ -56,6 +56,5 @@ <string name="keyboard_layout_montenegrin_latin" msgid="1467832503378949945">"Malazisht (latine)"</string> <string name="keyboard_layout_serbian_cyrillic" msgid="7013541044323542196">"Serbisht (cirilike)"</string> <string name="keyboard_layout_montenegrin_cyrillic" msgid="2391253952894077421">"Malazisht (cirilike)"</string> - <!-- no translation found for keyboard_layout_romanian (8698989892731726903) --> - <skip /> + <string name="keyboard_layout_romanian" msgid="8698989892731726903">"Rumanisht"</string> </resources> diff --git a/packages/InputDevices/res/values-uk/strings.xml b/packages/InputDevices/res/values-uk/strings.xml index 1010177539b1..01d34e362c7f 100644 --- a/packages/InputDevices/res/values-uk/strings.xml +++ b/packages/InputDevices/res/values-uk/strings.xml @@ -56,6 +56,5 @@ <string name="keyboard_layout_montenegrin_latin" msgid="1467832503378949945">"Чорногорська (латиниця)"</string> <string name="keyboard_layout_serbian_cyrillic" msgid="7013541044323542196">"Сербська (кирилиця)"</string> <string name="keyboard_layout_montenegrin_cyrillic" msgid="2391253952894077421">"Чорногорська (кирилиця)"</string> - <!-- no translation found for keyboard_layout_romanian (8698989892731726903) --> - <skip /> + <string name="keyboard_layout_romanian" msgid="8698989892731726903">"Румунська"</string> </resources> diff --git a/packages/InputDevices/res/values-uz/strings.xml b/packages/InputDevices/res/values-uz/strings.xml index 92e00d81c622..e63fc09b8d00 100644 --- a/packages/InputDevices/res/values-uz/strings.xml +++ b/packages/InputDevices/res/values-uz/strings.xml @@ -56,6 +56,5 @@ <string name="keyboard_layout_montenegrin_latin" msgid="1467832503378949945">"Chernogor (lotin)"</string> <string name="keyboard_layout_serbian_cyrillic" msgid="7013541044323542196">"Serb (kirill)"</string> <string name="keyboard_layout_montenegrin_cyrillic" msgid="2391253952894077421">"Chernogor (kirill)"</string> - <!-- no translation found for keyboard_layout_romanian (8698989892731726903) --> - <skip /> + <string name="keyboard_layout_romanian" msgid="8698989892731726903">"Rumin"</string> </resources> diff --git a/packages/PackageInstaller/res/values-ko/strings.xml b/packages/PackageInstaller/res/values-ko/strings.xml index 21da3e913df3..0d9920e4cdfe 100644 --- a/packages/PackageInstaller/res/values-ko/strings.xml +++ b/packages/PackageInstaller/res/values-ko/strings.xml @@ -78,7 +78,7 @@ <string name="uninstalling" msgid="8709566347688966845">"제거 중..."</string> <string name="uninstalling_app" msgid="8866082646836981397">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> 제거 중…"</string> <string name="uninstall_done" msgid="439354138387969269">"제거를 완료했습니다."</string> - <string name="uninstall_done_app" msgid="4588850984473605768">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>를 제거했습니다."</string> + <string name="uninstall_done_app" msgid="4588850984473605768">"앱(<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>)을 삭제했습니다."</string> <string name="uninstall_done_clone_app" msgid="5578308154544195413">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> 복제 삭제됨"</string> <string name="uninstall_failed" msgid="1847750968168364332">"제거하지 못했습니다."</string> <string name="uninstall_failed_app" msgid="5506028705017601412">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>을(를) 제거하지 못했습니다."</string> diff --git a/packages/PrintSpooler/src/com/android/printspooler/model/PageContentRepository.java b/packages/PrintSpooler/src/com/android/printspooler/model/PageContentRepository.java index fe27cee7ba2d..acead8e2a0eb 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/model/PageContentRepository.java +++ b/packages/PrintSpooler/src/com/android/printspooler/model/PageContentRepository.java @@ -510,7 +510,10 @@ public final class PageContentRepository { protected Void doInBackground(Void... params) { synchronized (mLock) { try { - if (mRenderer != null) { + // A page count < 0 indicates there was an error + // opening the document, in which case it doesn't + // need to be closed. + if (mRenderer != null && mPageCount >= 0) { mRenderer.closeDocument(); } } catch (RemoteException re) { diff --git a/packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java b/packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java index b48c55ddfef0..a9d00e9a77eb 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java +++ b/packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java @@ -70,6 +70,7 @@ public final class RemotePrintDocument { private static final int STATE_CANCELING = 6; private static final int STATE_CANCELED = 7; private static final int STATE_DESTROYED = 8; + private static final int STATE_INVALID = 9; private final Context mContext; @@ -287,7 +288,8 @@ public final class RemotePrintDocument { } if (mState != STATE_STARTED && mState != STATE_UPDATED && mState != STATE_FAILED && mState != STATE_CANCELING - && mState != STATE_CANCELED && mState != STATE_DESTROYED) { + && mState != STATE_CANCELED && mState != STATE_DESTROYED + && mState != STATE_INVALID) { throw new IllegalStateException("Cannot finish in state:" + stateToString(mState)); } @@ -300,6 +302,16 @@ public final class RemotePrintDocument { } } + /** + * Mark this document as invalid. + */ + public void invalid() { + if (DEBUG) { + Log.i(LOG_TAG, "[CALLED] invalid()"); + } + mState = STATE_INVALID; + } + public void cancel(boolean force) { if (DEBUG) { Log.i(LOG_TAG, "[CALLED] cancel(" + force + ")"); @@ -491,6 +503,9 @@ public final class RemotePrintDocument { case STATE_DESTROYED: { return "STATE_DESTROYED"; } + case STATE_INVALID: { + return "STATE_INVALID"; + } default: { return "STATE_UNKNOWN"; } diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java index 4a3a6d248254..2e3234e6e622 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java +++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java @@ -167,6 +167,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat private static final int STATE_PRINTER_UNAVAILABLE = 6; private static final int STATE_UPDATE_SLOW = 7; private static final int STATE_PRINT_COMPLETED = 8; + private static final int STATE_FILE_INVALID = 9; private static final int UI_STATE_PREVIEW = 0; private static final int UI_STATE_ERROR = 1; @@ -404,6 +405,11 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat public void onPause() { PrintSpoolerService spooler = mSpoolerProvider.getSpooler(); + if (isInvalid()) { + super.onPause(); + return; + } + if (mState == STATE_INITIALIZING) { if (isFinishing()) { if (spooler != null) { @@ -478,7 +484,8 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat } if (mState == STATE_PRINT_CANCELED || mState == STATE_PRINT_CONFIRMED - || mState == STATE_PRINT_COMPLETED) { + || mState == STATE_PRINT_COMPLETED + || mState == STATE_FILE_INVALID) { return true; } @@ -509,23 +516,32 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat @Override public void onMalformedPdfFile() { onPrintDocumentError("Cannot print a malformed PDF file"); + mPrintedDocument.invalid(); + setState(STATE_FILE_INVALID); } @Override public void onSecurePdfFile() { onPrintDocumentError("Cannot print a password protected PDF file"); + mPrintedDocument.invalid(); + setState(STATE_FILE_INVALID); } private void onPrintDocumentError(String message) { setState(mProgressMessageController.cancel()); - ensureErrorUiShown(null, PrintErrorFragment.ACTION_RETRY); + ensureErrorUiShown( + getString(R.string.print_cannot_load_page), PrintErrorFragment.ACTION_NONE); setState(STATE_UPDATE_FAILED); if (DEBUG) { Log.i(LOG_TAG, "PrintJob state[" + PrintJobInfo.STATE_FAILED + "] reason: " + message); } PrintSpoolerService spooler = mSpoolerProvider.getSpooler(); - spooler.setPrintJobState(mPrintJob.getId(), PrintJobInfo.STATE_FAILED, message); + // Use a cancel state for the spooler. This will prevent the notification from getting + // displayed and will remove the job. The notification (which displays the cancel and + // restart options) doesn't make sense for an invalid document since it will just fail + // again. + spooler.setPrintJobState(mPrintJob.getId(), PrintJobInfo.STATE_CANCELED, message); mPrintedDocument.finish(); } @@ -995,6 +1011,9 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat } private void setState(int state) { + if (isInvalid()) { + return; + } if (isFinalState(mState)) { if (isFinalState(state)) { if (DEBUG) { @@ -1015,7 +1034,12 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat private static boolean isFinalState(int state) { return state == STATE_PRINT_CANCELED || state == STATE_PRINT_COMPLETED - || state == STATE_CREATE_FILE_FAILED; + || state == STATE_CREATE_FILE_FAILED + || state == STATE_FILE_INVALID; + } + + private boolean isInvalid() { + return mState == STATE_FILE_INVALID; } private void updateSelectedPagesFromPreview() { @@ -1100,7 +1124,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat } private void ensurePreviewUiShown() { - if (isFinishing() || isDestroyed()) { + if (isFinishing() || isDestroyed() || isInvalid()) { return; } if (mUiState != UI_STATE_PREVIEW) { @@ -1257,6 +1281,9 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat } private boolean updateDocument(boolean clearLastError) { + if (isInvalid()) { + return false; + } if (!clearLastError && mPrintedDocument.hasUpdateError()) { return false; } @@ -1676,7 +1703,8 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat || mState == STATE_UPDATE_FAILED || mState == STATE_CREATE_FILE_FAILED || mState == STATE_PRINTER_UNAVAILABLE - || mState == STATE_UPDATE_SLOW) { + || mState == STATE_UPDATE_SLOW + || mState == STATE_FILE_INVALID) { disableOptionsUi(isFinalState(mState)); return; } @@ -2100,6 +2128,9 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat } private boolean canUpdateDocument() { + if (isInvalid()) { + return false; + } if (mPrintedDocument.isDestroyed()) { return false; } diff --git a/packages/SettingsLib/BannerMessagePreference/Android.bp b/packages/SettingsLib/BannerMessagePreference/Android.bp index 77e2cc735895..182daeb45d7c 100644 --- a/packages/SettingsLib/BannerMessagePreference/Android.bp +++ b/packages/SettingsLib/BannerMessagePreference/Android.bp @@ -28,4 +28,8 @@ android_library { sdk_version: "system_current", min_sdk_version: "28", + apex_available: [ + "//apex_available:platform", + "com.android.healthfitness", + ], } diff --git a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled.xml b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled.xml index f55b320269a8..ff22b2e7f86f 100644 --- a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled.xml +++ b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled.xml @@ -20,7 +20,8 @@ android:layout_height="wrap_content" android:orientation="vertical" android:paddingStart="?android:attr/listPreferredItemPaddingStart" - android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"> + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" + android:filterTouchesWhenObscured="true"> <com.google.android.material.button.MaterialButton android:id="@+id/settingslib_button" diff --git a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled_extra.xml b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled_extra.xml index b663b6ccc5bf..d878ba0d310b 100644 --- a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled_extra.xml +++ b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled_extra.xml @@ -20,7 +20,8 @@ android:layout_height="wrap_content" android:orientation="vertical" android:paddingStart="?android:attr/listPreferredItemPaddingStart" - android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"> + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" + android:filterTouchesWhenObscured="true"> <com.google.android.material.button.MaterialButton android:id="@+id/settingslib_button" diff --git a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled_large.xml b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled_large.xml index 784e6ad6a9f8..8f0a158c55eb 100644 --- a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled_large.xml +++ b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled_large.xml @@ -20,7 +20,8 @@ android:layout_height="wrap_content" android:orientation="vertical" android:paddingStart="?android:attr/listPreferredItemPaddingStart" - android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"> + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" + android:filterTouchesWhenObscured="true"> <com.google.android.material.button.MaterialButton android:id="@+id/settingslib_button" diff --git a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline.xml b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline.xml index 8b44a6539801..0c8996063e9f 100644 --- a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline.xml +++ b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline.xml @@ -20,7 +20,8 @@ android:layout_height="wrap_content" android:orientation="vertical" android:paddingStart="?android:attr/listPreferredItemPaddingStart" - android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"> + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" + android:filterTouchesWhenObscured="true"> <com.google.android.material.button.MaterialButton android:id="@+id/settingslib_button" diff --git a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline_extra.xml b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline_extra.xml index f8a2d8fbd975..41d8490feeb3 100644 --- a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline_extra.xml +++ b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline_extra.xml @@ -20,7 +20,8 @@ android:layout_height="wrap_content" android:orientation="vertical" android:paddingStart="?android:attr/listPreferredItemPaddingStart" - android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"> + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" + android:filterTouchesWhenObscured="true"> <com.google.android.material.button.MaterialButton android:id="@+id/settingslib_button" diff --git a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline_large.xml b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline_large.xml index 781a5a136164..958552064c98 100644 --- a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline_large.xml +++ b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline_large.xml @@ -20,7 +20,8 @@ android:layout_height="wrap_content" android:orientation="vertical" android:paddingStart="?android:attr/listPreferredItemPaddingStart" - android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"> + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" + android:filterTouchesWhenObscured="true"> <com.google.android.material.button.MaterialButton android:id="@+id/settingslib_button" diff --git a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal.xml b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal.xml index 5b568f870ea4..03ca1f0a1033 100644 --- a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal.xml +++ b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal.xml @@ -20,7 +20,8 @@ android:layout_height="wrap_content" android:orientation="vertical" android:paddingStart="?android:attr/listPreferredItemPaddingStart" - android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"> + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" + android:filterTouchesWhenObscured="true"> <com.google.android.material.button.MaterialButton android:id="@+id/settingslib_button" diff --git a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal_extra.xml b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal_extra.xml index 1e7a08b714f1..030ee66fef3f 100644 --- a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal_extra.xml +++ b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal_extra.xml @@ -20,7 +20,8 @@ android:layout_height="wrap_content" android:orientation="vertical" android:paddingStart="?android:attr/listPreferredItemPaddingStart" - android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"> + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" + android:filterTouchesWhenObscured="true"> <com.google.android.material.button.MaterialButton android:id="@+id/settingslib_button" diff --git a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal_large.xml b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal_large.xml index 42116be07041..5c16723f7a63 100644 --- a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal_large.xml +++ b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal_large.xml @@ -20,7 +20,8 @@ android:layout_height="wrap_content" android:orientation="vertical" android:paddingStart="?android:attr/listPreferredItemPaddingStart" - android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"> + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" + android:filterTouchesWhenObscured="true"> <com.google.android.material.button.MaterialButton android:id="@+id/settingslib_button" diff --git a/packages/SettingsLib/ButtonPreference/res/layout/settingslib_button_layout.xml b/packages/SettingsLib/ButtonPreference/res/layout/settingslib_button_layout.xml index 1ff09901ffaf..5405045a013d 100644 --- a/packages/SettingsLib/ButtonPreference/res/layout/settingslib_button_layout.xml +++ b/packages/SettingsLib/ButtonPreference/res/layout/settingslib_button_layout.xml @@ -20,7 +20,8 @@ android:layout_height="wrap_content" android:orientation="vertical" android:paddingStart="?android:attr/listPreferredItemPaddingStart" - android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"> + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" + android:filterTouchesWhenObscured="true"> <Button android:id="@+id/settingslib_button" diff --git a/packages/SettingsLib/ButtonPreference/res/layout/settingslib_number_button.xml b/packages/SettingsLib/ButtonPreference/res/layout/settingslib_number_button.xml index fa13b4125065..b23c5a510745 100644 --- a/packages/SettingsLib/ButtonPreference/res/layout/settingslib_number_button.xml +++ b/packages/SettingsLib/ButtonPreference/res/layout/settingslib_number_button.xml @@ -29,7 +29,8 @@ android:layout_height="wrap_content" android:paddingVertical="@dimen/settingslib_expressive_space_small1" android:paddingHorizontal="@dimen/settingslib_expressive_space_small4" - android:background="@drawable/settingslib_number_button_background"> + android:background="@drawable/settingslib_number_button_background" + android:filterTouchesWhenObscured="true"> <TextView android:id="@+id/settingslib_number_count" android:layout_width="wrap_content" diff --git a/packages/SettingsLib/ButtonPreference/res/layout/settingslib_section_button.xml b/packages/SettingsLib/ButtonPreference/res/layout/settingslib_section_button.xml index e7fb572d06dc..66a4c2e25c77 100644 --- a/packages/SettingsLib/ButtonPreference/res/layout/settingslib_section_button.xml +++ b/packages/SettingsLib/ButtonPreference/res/layout/settingslib_section_button.xml @@ -21,7 +21,8 @@ android:orientation="vertical" android:paddingStart="?android:attr/listPreferredItemPaddingStart" android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" - android:paddingVertical="@dimen/settingslib_expressive_space_extrasmall4"> + android:paddingVertical="@dimen/settingslib_expressive_space_extrasmall4" + android:filterTouchesWhenObscured="true"> <com.google.android.material.button.MaterialButton android:id="@+id/settingslib_section_button" diff --git a/packages/SettingsLib/DisplayUtils/Android.bp b/packages/SettingsLib/DisplayUtils/Android.bp index 279bb70d81bf..62630b5a9331 100644 --- a/packages/SettingsLib/DisplayUtils/Android.bp +++ b/packages/SettingsLib/DisplayUtils/Android.bp @@ -15,6 +15,4 @@ android_library { ], srcs: ["src/**/*.java"], - - min_sdk_version: "21", } diff --git a/packages/SettingsLib/DisplayUtils/src/com/android/settingslib/display/DisplayDensityConfiguration.java b/packages/SettingsLib/DisplayUtils/src/com/android/settingslib/display/DisplayDensityConfiguration.java index 284a9025de64..127e628fbd2f 100644 --- a/packages/SettingsLib/DisplayUtils/src/com/android/settingslib/display/DisplayDensityConfiguration.java +++ b/packages/SettingsLib/DisplayUtils/src/com/android/settingslib/display/DisplayDensityConfiguration.java @@ -16,13 +16,20 @@ package com.android.settingslib.display; +import android.annotation.NonNull; +import android.content.Context; +import android.hardware.display.DisplayManager; import android.os.AsyncTask; import android.os.RemoteException; import android.os.UserHandle; import android.util.Log; +import android.view.Display; +import android.view.DisplayInfo; import android.view.IWindowManager; import android.view.WindowManagerGlobal; +import java.util.function.Predicate; + /** Utility methods for controlling the display density. */ public class DisplayDensityConfiguration { private static final String LOG_TAG = "DisplayDensityConfig"; @@ -85,4 +92,42 @@ public class DisplayDensityConfiguration { } }); } + + /** + * Asynchronously applies display density changes to all displays that satisfy the predicate. + * + * <p>The change will be applied to the user specified by the value of + * {@link UserHandle#myUserId()} at the time the method is called. + * + * @param context The context + * @param predicate Determines which displays to set the density to + * @param density The density to force + */ + public static void setForcedDisplayDensity(@NonNull Context context, + @NonNull Predicate<DisplayInfo> predicate, final int density) { + final int userId = UserHandle.myUserId(); + DisplayManager dm = context.getSystemService(DisplayManager.class); + AsyncTask.execute(() -> { + try { + for (Display display : dm.getDisplays( + DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED)) { + int displayId = display.getDisplayId(); + DisplayInfo info = new DisplayInfo(); + if (!display.getDisplayInfo(info)) { + Log.w(LOG_TAG, "Unable to save forced display density setting " + + "for display " + displayId); + continue; + } + if (!predicate.test(info)) { + continue; + } + + final IWindowManager wm = WindowManagerGlobal.getWindowManagerService(); + wm.setForcedDisplayDensityForUser(displayId, density, userId); + } + } catch (RemoteException exc) { + Log.w(LOG_TAG, "Unable to save forced display density setting"); + } + }); + } } diff --git a/packages/SettingsLib/Graph/graph.proto b/packages/SettingsLib/Graph/graph.proto index 33a7df4c6ba8..a834947144a0 100644 --- a/packages/SettingsLib/Graph/graph.proto +++ b/packages/SettingsLib/Graph/graph.proto @@ -26,6 +26,14 @@ message PreferenceScreenProto { optional PreferenceGroupProto root = 2; // If the preference screen provides complete hierarchy by source code. optional bool complete_hierarchy = 3; + // Parameterized screens (not recursive, provided on the top level only) + repeated ParameterizedPreferenceScreenProto parameterized_screens = 4; +} + +// Proto of parameterized preference screen +message ParameterizedPreferenceScreenProto { + optional BundleProto args = 1; + optional PreferenceScreenProto screen = 2; } // Proto of PreferenceGroup. diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt index 51813a1c9aab..27ce1c7246e6 100644 --- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt +++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt @@ -18,16 +18,24 @@ package com.android.settingslib.graph import android.app.Application import android.os.Bundle +import android.os.Parcelable +import android.os.SystemClock import com.android.settingslib.graph.proto.PreferenceGraphProto import com.android.settingslib.ipc.ApiHandler +import com.android.settingslib.ipc.ApiPermissionChecker import com.android.settingslib.ipc.MessageCodec +import com.android.settingslib.metadata.PreferenceRemoteOpMetricsLogger +import com.android.settingslib.metadata.PreferenceScreenCoordinate import com.android.settingslib.metadata.PreferenceScreenRegistry import com.android.settingslib.preference.PreferenceScreenProvider import java.util.Locale /** API to get preference graph. */ -abstract class GetPreferenceGraphApiHandler( - private val preferenceScreenProviders: Set<Class<out PreferenceScreenProvider>> +class GetPreferenceGraphApiHandler( + override val id: Int, + private val permissionChecker: ApiPermissionChecker<GetPreferenceGraphRequest>, + private val metricsLogger: PreferenceRemoteOpMetricsLogger? = null, + private val preferenceScreenProviders: Set<Class<out PreferenceScreenProvider>> = emptySet(), ) : ApiHandler<GetPreferenceGraphRequest, PreferenceGraphProto> { override val requestCodec: MessageCodec<GetPreferenceGraphRequest> @@ -36,37 +44,56 @@ abstract class GetPreferenceGraphApiHandler( override val responseCodec: MessageCodec<PreferenceGraphProto> get() = PreferenceGraphProtoCodec + override fun hasPermission( + application: Application, + callingPid: Int, + callingUid: Int, + request: GetPreferenceGraphRequest, + ) = permissionChecker.hasPermission(application, callingPid, callingUid, request) + override suspend fun invoke( application: Application, callingPid: Int, callingUid: Int, request: GetPreferenceGraphRequest, ): PreferenceGraphProto { - val builder = PreferenceGraphBuilder.of(application, callingPid, callingUid, request) - if (request.screenKeys.isEmpty()) { - PreferenceScreenRegistry.preferenceScreenMetadataFactories.forEachKeyAsync { - builder.addPreferenceScreenFromRegistry(it) - } - for (provider in preferenceScreenProviders) { - builder.addPreferenceScreenProvider(provider) + val elapsedRealtime = SystemClock.elapsedRealtime() + var success = false + try { + val builder = PreferenceGraphBuilder.of(application, callingPid, callingUid, request) + if (request.screens.isEmpty()) { + val factories = PreferenceScreenRegistry.preferenceScreenMetadataFactories + factories.forEachAsync { _, factory -> builder.addPreferenceScreen(factory) } + for (provider in preferenceScreenProviders) { + builder.addPreferenceScreenProvider(provider) + } } + val result = builder.build() + success = true + return result + } finally { + metricsLogger?.logGraphApi( + application, + callingUid, + success, + SystemClock.elapsedRealtime() - elapsedRealtime, + ) } - return builder.build() } } /** * Request of [GetPreferenceGraphApiHandler]. * - * @param screenKeys screen keys of the preference graph - * @param visitedScreens keys of the visited preference screen + * @param screens screens of the preference graph + * @param visitedScreens visited preference screens * @param locale locale of the preference graph */ data class GetPreferenceGraphRequest @JvmOverloads constructor( - val screenKeys: Set<String> = setOf(), - val visitedScreens: Set<String> = setOf(), + val screens: Set<PreferenceScreenCoordinate> = setOf(), + val visitedScreens: Set<PreferenceScreenCoordinate> = setOf(), val locale: Locale? = null, val flags: Int = PreferenceGetterFlags.ALL, val includeValueDescriptor: Boolean = true, @@ -75,26 +102,32 @@ constructor( object GetPreferenceGraphRequestCodec : MessageCodec<GetPreferenceGraphRequest> { override fun encode(data: GetPreferenceGraphRequest): Bundle = Bundle(4).apply { - putStringArray(KEY_SCREEN_KEYS, data.screenKeys.toTypedArray()) - putStringArray(KEY_VISITED_KEYS, data.visitedScreens.toTypedArray()) + putParcelableArray(KEY_SCREENS, data.screens.toTypedArray()) + putParcelableArray(KEY_VISITED_SCREENS, data.visitedScreens.toTypedArray()) putString(KEY_LOCALE, data.locale?.toLanguageTag()) putInt(KEY_FLAGS, data.flags) } + @Suppress("DEPRECATION") override fun decode(data: Bundle): GetPreferenceGraphRequest { - val screenKeys = data.getStringArray(KEY_SCREEN_KEYS) ?: arrayOf() - val visitedScreens = data.getStringArray(KEY_VISITED_KEYS) ?: arrayOf() + data.classLoader = PreferenceScreenCoordinate::class.java.classLoader + val screens = data.getParcelableArray(KEY_SCREENS) ?: arrayOf() + val visitedScreens = data.getParcelableArray(KEY_VISITED_SCREENS) ?: arrayOf() fun String?.toLocale() = if (this != null) Locale.forLanguageTag(this) else null + fun Array<Parcelable>.toScreenCoordinates() = + buildSet(size) { + for (element in this@toScreenCoordinates) add(element as PreferenceScreenCoordinate) + } return GetPreferenceGraphRequest( - screenKeys.toSet(), - visitedScreens.toSet(), + screens.toScreenCoordinates(), + visitedScreens.toScreenCoordinates(), data.getString(KEY_LOCALE).toLocale(), data.getInt(KEY_FLAGS), ) } - private const val KEY_SCREEN_KEYS = "k" - private const val KEY_VISITED_KEYS = "v" + private const val KEY_SCREENS = "s" + private const val KEY_VISITED_SCREENS = "v" private const val KEY_LOCALE = "l" private const val KEY_FLAGS = "f" } diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGetterApi.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGetterApi.kt index 6fc6b5405eb2..1d4e2c9e1bef 100644 --- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGetterApi.kt +++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGetterApi.kt @@ -17,6 +17,7 @@ package com.android.settingslib.graph import android.app.Application +import android.os.SystemClock import androidx.annotation.IntDef import com.android.settingslib.graph.proto.PreferenceProto import com.android.settingslib.ipc.ApiDescriptor @@ -24,6 +25,8 @@ import com.android.settingslib.ipc.ApiHandler import com.android.settingslib.ipc.ApiPermissionChecker import com.android.settingslib.metadata.PreferenceCoordinate import com.android.settingslib.metadata.PreferenceHierarchyNode +import com.android.settingslib.metadata.PreferenceRemoteOpMetricsLogger +import com.android.settingslib.metadata.PreferenceScreenCoordinate import com.android.settingslib.metadata.PreferenceScreenRegistry /** @@ -37,6 +40,7 @@ class PreferenceGetterRequest(val preferences: Array<PreferenceCoordinate>, val /** Error code of preference getter request. */ @Target(AnnotationTarget.TYPE) @IntDef( + PreferenceGetterErrorCode.OK, PreferenceGetterErrorCode.NOT_FOUND, PreferenceGetterErrorCode.DISALLOW, PreferenceGetterErrorCode.INTERNAL_ERROR, @@ -44,6 +48,8 @@ class PreferenceGetterRequest(val preferences: Array<PreferenceCoordinate>, val @Retention(AnnotationRetention.SOURCE) annotation class PreferenceGetterErrorCode { companion object { + /** Preference value is returned. */ + const val OK = 0 /** Preference is not found. */ const val NOT_FOUND = 1 /** Disallow to get preference value (e.g. uid not allowed). */ @@ -80,6 +86,7 @@ class PreferenceGetterApiDescriptor(override val id: Int) : class PreferenceGetterApiHandler( override val id: Int, private val permissionChecker: ApiPermissionChecker<PreferenceGetterRequest>, + private val metricsLogger: PreferenceRemoteOpMetricsLogger? = null, ) : ApiHandler<PreferenceGetterRequest, PreferenceGetterResponse> { override fun hasPermission( @@ -95,14 +102,27 @@ class PreferenceGetterApiHandler( callingUid: Int, request: PreferenceGetterRequest, ): PreferenceGetterResponse { + val elapsedRealtime = SystemClock.elapsedRealtime() val errors = mutableMapOf<PreferenceCoordinate, Int>() val preferences = mutableMapOf<PreferenceCoordinate, PreferenceProto>() val flags = request.flags - for ((screenKey, coordinates) in request.preferences.groupBy { it.screenKey }) { - val screenMetadata = PreferenceScreenRegistry.create(application, screenKey) + val groups = + request.preferences.groupBy { PreferenceScreenCoordinate(it.screenKey, it.args) } + for ((screen, coordinates) in groups) { + val screenMetadata = PreferenceScreenRegistry.create(application, screen) if (screenMetadata == null) { + val latencyMs = SystemClock.elapsedRealtime() - elapsedRealtime for (coordinate in coordinates) { errors[coordinate] = PreferenceGetterErrorCode.NOT_FOUND + metricsLogger?.logGetterApi( + application, + callingUid, + coordinate, + null, + null, + PreferenceGetterErrorCode.NOT_FOUND, + latencyMs, + ) } continue } @@ -117,27 +137,48 @@ class PreferenceGetterApiHandler( val node = nodes[coordinate.key] if (node == null) { errors[coordinate] = PreferenceGetterErrorCode.NOT_FOUND + metricsLogger?.logGetterApi( + application, + callingUid, + coordinate, + null, + null, + PreferenceGetterErrorCode.NOT_FOUND, + SystemClock.elapsedRealtime() - elapsedRealtime, + ) continue } val metadata = node.metadata - try { - val preferenceProto = - metadata.toProto( - application, - callingPid, - callingUid, - screenMetadata, - metadata.key == screenMetadata.key, - flags, - ) - if (flags == PreferenceGetterFlags.VALUE && !preferenceProto.hasValue()) { - errors[coordinate] = PreferenceGetterErrorCode.DISALLOW - } else { - preferences[coordinate] = preferenceProto + val errorCode = + try { + val preferenceProto = + metadata.toProto( + application, + callingPid, + callingUid, + screenMetadata, + metadata.key == screenMetadata.key, + flags, + ) + if (flags == PreferenceGetterFlags.VALUE && !preferenceProto.hasValue()) { + PreferenceGetterErrorCode.DISALLOW + } else { + preferences[coordinate] = preferenceProto + PreferenceGetterErrorCode.OK + } + } catch (e: Exception) { + PreferenceGetterErrorCode.INTERNAL_ERROR } - } catch (e: Exception) { - errors[coordinate] = PreferenceGetterErrorCode.INTERNAL_ERROR - } + if (errorCode != PreferenceGetterErrorCode.OK) errors[coordinate] = errorCode + metricsLogger?.logGetterApi( + application, + callingUid, + coordinate, + screenMetadata, + metadata, + errorCode, + SystemClock.elapsedRealtime() - elapsedRealtime, + ) } } return PreferenceGetterResponse(errors, preferences) 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 c0d244989044..4290437b0d02 100644 --- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt +++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt @@ -40,6 +40,7 @@ import com.android.settingslib.graph.proto.PreferenceProto import com.android.settingslib.graph.proto.PreferenceProto.ActionTarget import com.android.settingslib.graph.proto.PreferenceScreenProto import com.android.settingslib.graph.proto.TextProto +import com.android.settingslib.metadata.EXTRA_BINDING_SCREEN_ARGS import com.android.settingslib.metadata.IntRangeValuePreference import com.android.settingslib.metadata.PersistentPreference import com.android.settingslib.metadata.PreferenceAvailabilityProvider @@ -47,7 +48,10 @@ import com.android.settingslib.metadata.PreferenceHierarchy import com.android.settingslib.metadata.PreferenceMetadata import com.android.settingslib.metadata.PreferenceRestrictionProvider import com.android.settingslib.metadata.PreferenceScreenBindingKeyProvider +import com.android.settingslib.metadata.PreferenceScreenCoordinate import com.android.settingslib.metadata.PreferenceScreenMetadata +import com.android.settingslib.metadata.PreferenceScreenMetadataFactory +import com.android.settingslib.metadata.PreferenceScreenMetadataParameterizedFactory import com.android.settingslib.metadata.PreferenceScreenRegistry import com.android.settingslib.metadata.PreferenceSummaryProvider import com.android.settingslib.metadata.PreferenceTitleProvider @@ -72,15 +76,19 @@ private constructor( PreferenceScreenFactory(context.ofLocale(request.locale)) } private val builder by lazy { PreferenceGraphProto.newBuilder() } - private val visitedScreens = mutableSetOf<String>().apply { addAll(request.visitedScreens) } + private val visitedScreens = request.visitedScreens.toMutableSet() + private val screens = mutableMapOf<String, PreferenceScreenProto.Builder>() private suspend fun init() { - for (key in request.screenKeys) { - addPreferenceScreenFromRegistry(key) + for (screen in request.screens) { + PreferenceScreenRegistry.create(context, screen)?.let { addPreferenceScreen(it) } } } - fun build(): PreferenceGraphProto = builder.build() + fun build(): PreferenceGraphProto { + for ((key, screenBuilder) in screens) builder.putScreens(key, screenBuilder.build()) + return builder.build() + } /** * Adds an activity to the graph. @@ -138,19 +146,12 @@ private constructor( null } - suspend fun addPreferenceScreenFromRegistry(key: String): Boolean { - val metadata = PreferenceScreenRegistry.create(context, key) ?: return false - return addPreferenceScreenMetadata(metadata) + private suspend fun addPreferenceScreenFromRegistry(key: String): Boolean { + val factory = + PreferenceScreenRegistry.preferenceScreenMetadataFactories[key] ?: return false + return addPreferenceScreen(factory) } - private suspend fun addPreferenceScreenMetadata(metadata: PreferenceScreenMetadata): Boolean = - addPreferenceScreen(metadata.key) { - preferenceScreenProto { - completeHierarchy = metadata.hasCompleteHierarchy() - root = metadata.getPreferenceHierarchy(context).toProto(metadata, true) - } - } - suspend fun addPreferenceScreenProvider(activityClass: Class<*>) { Log.d(TAG, "add $activityClass") createPreferenceScreen { activityClass.newInstance() } @@ -188,26 +189,52 @@ private constructor( Log.e(TAG, "\"$preferenceScreen\" has no key") return } - @Suppress("CheckReturnValue") addPreferenceScreen(key) { preferenceScreen.toProto(intent) } + val args = preferenceScreen.peekExtras()?.getBundle(EXTRA_BINDING_SCREEN_ARGS) + @Suppress("CheckReturnValue") + addPreferenceScreen(key, args) { + this.intent = intent.toProto() + root = preferenceScreen.toProto() + } + } + + suspend fun addPreferenceScreen(factory: PreferenceScreenMetadataFactory): Boolean { + if (factory is PreferenceScreenMetadataParameterizedFactory) { + factory.parameters(context).collect { addPreferenceScreen(factory.create(context, it)) } + return true + } + return addPreferenceScreen(factory.create(context)) } + private suspend fun addPreferenceScreen(metadata: PreferenceScreenMetadata): Boolean = + addPreferenceScreen(metadata.key, metadata.arguments) { + completeHierarchy = metadata.hasCompleteHierarchy() + root = metadata.getPreferenceHierarchy(context).toProto(metadata, true) + } + private suspend fun addPreferenceScreen( key: String, - preferenceScreenProvider: suspend () -> PreferenceScreenProto, - ): Boolean = - if (visitedScreens.add(key)) { - builder.putScreens(key, preferenceScreenProvider()) - true - } else { - Log.w(TAG, "$key visited") - false + args: Bundle?, + init: suspend PreferenceScreenProto.Builder.() -> Unit, + ): Boolean { + if (!visitedScreens.add(PreferenceScreenCoordinate(key, args))) { + Log.w(TAG, "$key $args visited") + return false } - - private suspend fun PreferenceScreen.toProto(intent: Intent?): PreferenceScreenProto = - preferenceScreenProto { - intent?.let { this.intent = it.toProto() } - root = (this@toProto as PreferenceGroup).toProto() + if (args == null) { // normal screen + screens[key] = PreferenceScreenProto.newBuilder().also { init(it) } + } else if (args.isEmpty) { // parameterized screen with backward compatibility + val builder = screens.getOrPut(key) { PreferenceScreenProto.newBuilder() } + init(builder) + } else { // parameterized screen with non-empty arguments + val builder = screens.getOrPut(key) { PreferenceScreenProto.newBuilder() } + val parameterizedScreen = parameterizedPreferenceScreenProto { + setArgs(args.toProto()) + setScreen(PreferenceScreenProto.newBuilder().also { init(it) }) + } + builder.addParameterizedScreens(parameterizedScreen) } + return true + } private suspend fun PreferenceGroup.toProto(): PreferenceGroupProto = preferenceGroupProto { preference = (this@toProto as Preference).toProto() @@ -271,7 +298,7 @@ private constructor( .toProto(context, callingPid, callingUid, screenMetadata, isRoot, request.flags) .also { if (metadata is PreferenceScreenMetadata) { - @Suppress("CheckReturnValue") addPreferenceScreenMetadata(metadata) + @Suppress("CheckReturnValue") addPreferenceScreen(metadata) } metadata.intent(context)?.resolveActivity(context.packageManager)?.let { if (it.packageName == context.packageName) { @@ -322,7 +349,7 @@ private constructor( val screenKey = screen?.key if (!screenKey.isNullOrEmpty()) { @Suppress("CheckReturnValue") - addPreferenceScreen(screenKey) { screen.toProto(null) } + addPreferenceScreen(screenKey, null) { root = screen.toProto() } return actionTargetProto { key = screenKey } } } catch (e: Exception) { diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt index 728055c2f837..60f9c6bb92a3 100644 --- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt +++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt @@ -19,6 +19,7 @@ package com.android.settingslib.graph import android.app.Application import android.content.Context import android.os.Bundle +import android.os.SystemClock import androidx.annotation.IntDef import com.android.settingslib.graph.proto.PreferenceValueProto import com.android.settingslib.ipc.ApiDescriptor @@ -29,7 +30,9 @@ import com.android.settingslib.ipc.MessageCodec import com.android.settingslib.metadata.IntRangeValuePreference import com.android.settingslib.metadata.PersistentPreference import com.android.settingslib.metadata.PreferenceAvailabilityProvider +import com.android.settingslib.metadata.PreferenceCoordinate import com.android.settingslib.metadata.PreferenceMetadata +import com.android.settingslib.metadata.PreferenceRemoteOpMetricsLogger import com.android.settingslib.metadata.PreferenceRestrictionProvider import com.android.settingslib.metadata.PreferenceScreenRegistry import com.android.settingslib.metadata.ReadWritePermit @@ -37,11 +40,12 @@ import com.android.settingslib.metadata.SensitivityLevel.Companion.HIGH_SENSITIV import com.android.settingslib.metadata.SensitivityLevel.Companion.UNKNOWN_SENSITIVITY /** Request to set preference value. */ -data class PreferenceSetterRequest( - val screenKey: String, - val key: String, +class PreferenceSetterRequest( + screenKey: String, + args: Bundle?, + key: String, val value: PreferenceValueProto, -) +) : PreferenceCoordinate(screenKey, args, key) /** Result of preference setter request. */ @IntDef( @@ -97,6 +101,7 @@ class PreferenceSetterApiDescriptor(override val id: Int) : class PreferenceSetterApiHandler( override val id: Int, private val permissionChecker: ApiPermissionChecker<PreferenceSetterRequest>, + private val metricsLogger: PreferenceRemoteOpMetricsLogger? = null, ) : ApiHandler<PreferenceSetterRequest, Int> { override fun hasPermission( @@ -112,21 +117,24 @@ class PreferenceSetterApiHandler( callingUid: Int, request: PreferenceSetterRequest, ): Int { + val elapsedRealtime = SystemClock.elapsedRealtime() + fun notFound(): Int { + metricsLogger?.logSetterApi( + application, + callingUid, + request, + null, + null, + PreferenceSetterResult.UNSUPPORTED, + SystemClock.elapsedRealtime() - elapsedRealtime, + ) + return PreferenceSetterResult.UNSUPPORTED + } val screenMetadata = - PreferenceScreenRegistry.create(application, request.screenKey) - ?: return PreferenceSetterResult.UNSUPPORTED + PreferenceScreenRegistry.create(application, request) ?: return notFound() val key = request.key val metadata = - screenMetadata.getPreferenceHierarchy(application).find(key) - ?: return PreferenceSetterResult.UNSUPPORTED - if (metadata !is PersistentPreference<*>) return PreferenceSetterResult.UNSUPPORTED - if (!metadata.isEnabled(application)) return PreferenceSetterResult.DISABLED - if (metadata is PreferenceRestrictionProvider && metadata.isRestricted(application)) { - return PreferenceSetterResult.RESTRICTED - } - if (metadata is PreferenceAvailabilityProvider && !metadata.isAvailable(application)) { - return PreferenceSetterResult.UNAVAILABLE - } + screenMetadata.getPreferenceHierarchy(application).find(key) ?: return notFound() fun <T> PreferenceMetadata.checkWritePermit(value: T): Int { @Suppress("UNCHECKED_CAST") val preference = (this as PersistentPreference<T>) @@ -141,41 +149,64 @@ class PreferenceSetterApiHandler( } } - val storage = metadata.storage(application) - val value = request.value - try { - if (value.hasBooleanValue()) { - if (metadata.valueType != Boolean::class.javaObjectType) { - return PreferenceSetterResult.INVALID_REQUEST - } - val booleanValue = value.booleanValue - val resultCode = metadata.checkWritePermit(booleanValue) - if (resultCode != PreferenceSetterResult.OK) return resultCode - storage.setBoolean(key, booleanValue) - return PreferenceSetterResult.OK - } else if (value.hasIntValue()) { - val intValue = value.intValue - val resultCode = metadata.checkWritePermit(intValue) - if (resultCode != PreferenceSetterResult.OK) return resultCode - if ( - metadata is IntRangeValuePreference && - !metadata.isValidValue(application, intValue) - ) { - return PreferenceSetterResult.INVALID_REQUEST + fun invoke(): Int { + if (metadata !is PersistentPreference<*>) return PreferenceSetterResult.UNSUPPORTED + if (!metadata.isEnabled(application)) return PreferenceSetterResult.DISABLED + if (metadata is PreferenceRestrictionProvider && metadata.isRestricted(application)) { + return PreferenceSetterResult.RESTRICTED + } + if (metadata is PreferenceAvailabilityProvider && !metadata.isAvailable(application)) { + return PreferenceSetterResult.UNAVAILABLE + } + + val storage = metadata.storage(application) + val value = request.value + try { + if (value.hasBooleanValue()) { + if (metadata.valueType != Boolean::class.javaObjectType) { + return PreferenceSetterResult.INVALID_REQUEST + } + val booleanValue = value.booleanValue + val resultCode = metadata.checkWritePermit(booleanValue) + if (resultCode != PreferenceSetterResult.OK) return resultCode + storage.setBoolean(key, booleanValue) + return PreferenceSetterResult.OK + } else if (value.hasIntValue()) { + val intValue = value.intValue + val resultCode = metadata.checkWritePermit(intValue) + if (resultCode != PreferenceSetterResult.OK) return resultCode + if ( + metadata is IntRangeValuePreference && + !metadata.isValidValue(application, intValue) + ) { + return PreferenceSetterResult.INVALID_REQUEST + } + storage.setInt(key, intValue) + return PreferenceSetterResult.OK + } else if (value.hasFloatValue()) { + val floatValue = value.floatValue + val resultCode = metadata.checkWritePermit(floatValue) + if (resultCode != PreferenceSetterResult.OK) return resultCode + storage.setFloat(key, floatValue) + return PreferenceSetterResult.OK } - storage.setInt(key, intValue) - return PreferenceSetterResult.OK - } else if (value.hasFloatValue()) { - val floatValue = value.floatValue - val resultCode = metadata.checkWritePermit(floatValue) - if (resultCode != PreferenceSetterResult.OK) return resultCode - storage.setFloat(key, floatValue) - return PreferenceSetterResult.OK + } catch (e: Exception) { + return PreferenceSetterResult.INTERNAL_ERROR } - } catch (e: Exception) { - return PreferenceSetterResult.INTERNAL_ERROR + return PreferenceSetterResult.INVALID_REQUEST } - return PreferenceSetterResult.INVALID_REQUEST + + val result = invoke() + metricsLogger?.logSetterApi( + application, + callingUid, + request, + screenMetadata, + metadata, + result, + SystemClock.elapsedRealtime() - elapsedRealtime, + ) + return result } override val requestCodec: MessageCodec<PreferenceSetterRequest> @@ -205,6 +236,7 @@ object PreferenceSetterRequestCodec : MessageCodec<PreferenceSetterRequest> { override fun encode(data: PreferenceSetterRequest) = Bundle(3).apply { putString(SCREEN_KEY, data.screenKey) + putBundle(ARGS, data.args) putString(KEY, data.key) putByteArray(null, data.value.toByteArray()) } @@ -212,10 +244,12 @@ object PreferenceSetterRequestCodec : MessageCodec<PreferenceSetterRequest> { override fun decode(data: Bundle) = PreferenceSetterRequest( data.getString(SCREEN_KEY)!!, + data.getBundle(ARGS), data.getString(KEY)!!, PreferenceValueProto.parseFrom(data.getByteArray(null)!!), ) private const val SCREEN_KEY = "s" private const val KEY = "k" + private const val ARGS = "a" } diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/ProtoDsl.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/ProtoDsl.kt index adbe77318353..5f2a0d826407 100644 --- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/ProtoDsl.kt +++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/ProtoDsl.kt @@ -19,6 +19,7 @@ package com.android.settingslib.graph import com.android.settingslib.graph.proto.BundleProto import com.android.settingslib.graph.proto.BundleProto.BundleValue import com.android.settingslib.graph.proto.IntentProto +import com.android.settingslib.graph.proto.ParameterizedPreferenceScreenProto import com.android.settingslib.graph.proto.PreferenceGroupProto import com.android.settingslib.graph.proto.PreferenceOrGroupProto import com.android.settingslib.graph.proto.PreferenceProto @@ -39,6 +40,12 @@ inline fun preferenceScreenProto( init: PreferenceScreenProto.Builder.() -> Unit ): PreferenceScreenProto = PreferenceScreenProto.newBuilder().also(init).build() +/** Kotlin DSL-style builder for [PreferenceScreenProto]. */ +inline fun parameterizedPreferenceScreenProto( + init: ParameterizedPreferenceScreenProto.Builder.() -> Unit +): ParameterizedPreferenceScreenProto = + ParameterizedPreferenceScreenProto.newBuilder().also(init).build() + /** Returns preference or null. */ val PreferenceOrGroupProto.preferenceOrNull get() = if (hasPreference()) preference else null diff --git a/packages/SettingsLib/IntroPreference/res/layout/settingslib_expressive_preference_intro.xml b/packages/SettingsLib/IntroPreference/res/layout/settingslib_expressive_preference_intro.xml index 43cf6aa09109..7adcbf6c6601 100644 --- a/packages/SettingsLib/IntroPreference/res/layout/settingslib_expressive_preference_intro.xml +++ b/packages/SettingsLib/IntroPreference/res/layout/settingslib_expressive_preference_intro.xml @@ -18,6 +18,8 @@ <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/entity_header" + android:layout_width="match_parent" + android:layout_height="wrap_content" style="@style/SettingsLibEntityHeader"> <LinearLayout diff --git a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java index 73728bcd1ff7..7bd4b3f771ab 100644 --- a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java +++ b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java @@ -240,6 +240,11 @@ public class MainSwitchBar extends LinearLayout implements OnCheckedChangeListen } } + /** Removes all [OnCheckedChangeListener]s. */ + public void removeAllOnSwitchChangeListeners() { + mSwitchChangeListeners.clear(); + } + /** * Remove a listener for switch changes */ 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 83858d9c9c54..d883fb0594e6 100644 --- a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java +++ b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java @@ -17,12 +17,13 @@ package com.android.settingslib.widget; import android.content.Context; -import android.content.res.TypedArray; import android.os.Build; import android.util.AttributeSet; import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.preference.PreferenceViewHolder; import androidx.preference.TwoStatePreference; @@ -34,124 +35,67 @@ import java.util.List; /** * MainSwitchPreference is a Preference with a customized Switch. * This component is used as the main switch of the page - * to enable or disable the prefereces on the page. + * to enable or disable the preferences on the page. */ -public class MainSwitchPreference extends TwoStatePreference - implements OnCheckedChangeListener, GroupSectionDividerMixin { +public class MainSwitchPreference extends TwoStatePreference implements OnCheckedChangeListener, + GroupSectionDividerMixin { private final List<OnCheckedChangeListener> mSwitchChangeListeners = new ArrayList<>(); - private MainSwitchBar mMainSwitchBar; - - public MainSwitchPreference(Context context) { - super(context); - init(context, null); + public MainSwitchPreference(@NonNull Context context) { + this(context, null); } - public MainSwitchPreference(Context context, AttributeSet attrs) { - super(context, attrs); - init(context, attrs); + public MainSwitchPreference(@NonNull Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); } - public MainSwitchPreference(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - init(context, attrs); + public MainSwitchPreference(@NonNull Context context, @Nullable AttributeSet attrs, + int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); } - public MainSwitchPreference(Context context, AttributeSet attrs, int defStyleAttr, - int defStyleRes) { + public MainSwitchPreference(@NonNull Context context, @Nullable AttributeSet attrs, + int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); - init(context, attrs); + boolean isExpressive = SettingsThemeHelper.isExpressiveTheme(context); + int resId = isExpressive ? R.layout.settingslib_expressive_main_switch_layout + : R.layout.settingslib_main_switch_layout; + setLayoutResource(resId); } @Override - public void onBindViewHolder(PreferenceViewHolder holder) { + public void onBindViewHolder(@NonNull PreferenceViewHolder holder) { super.onBindViewHolder(holder); holder.setDividerAllowedAbove(false); holder.setDividerAllowedBelow(false); - mMainSwitchBar = (MainSwitchBar) holder.findViewById(R.id.settingslib_main_switch_bar); + MainSwitchBar mainSwitchBar = holder.itemView.requireViewById( + R.id.settingslib_main_switch_bar); + mainSwitchBar.setTitle(getTitle()); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) { + mainSwitchBar.setSummary(getSummary()); + } + mainSwitchBar.setIconSpaceReserved(isIconSpaceReserved()); // To support onPreferenceChange callback, it needs to call callChangeListener() when // MainSwitchBar is clicked. - mMainSwitchBar.setOnClickListener((view) -> callChangeListener(isChecked())); - setIconSpaceReserved(isIconSpaceReserved()); - updateStatus(isChecked()); - registerListenerToSwitchBar(); - } + mainSwitchBar.setOnClickListener(view -> callChangeListener(isChecked())); - private void init(Context context, AttributeSet attrs) { - boolean isExpressive = SettingsThemeHelper.isExpressiveTheme(context); - int resId = isExpressive - ? R.layout.settingslib_expressive_main_switch_layout - : R.layout.settingslib_main_switch_layout; - setLayoutResource(resId); - mSwitchChangeListeners.add(this); - if (attrs != null) { - final TypedArray a = context.obtainStyledAttributes(attrs, - androidx.preference.R.styleable.Preference, 0 /*defStyleAttr*/, - 0 /*defStyleRes*/); - final CharSequence title = a.getText( - androidx.preference.R.styleable.Preference_android_title); - setTitle(title); - - CharSequence summary = a.getText( - androidx.preference.R.styleable.Preference_android_summary); - setSummary(summary); - - final boolean bIconSpaceReserved = a.getBoolean( - androidx.preference.R.styleable.Preference_android_iconSpaceReserved, true); - setIconSpaceReserved(bIconSpaceReserved); - a.recycle(); - } - } + // Remove all listeners to 1. avoid triggering listener when update UI 2. prevent potential + // listener leaking when the view holder is reused by RecyclerView + mainSwitchBar.removeAllOnSwitchChangeListeners(); + mainSwitchBar.setChecked(isChecked()); + mainSwitchBar.addOnSwitchChangeListener(this); - @Override - public void setChecked(boolean checked) { - super.setChecked(checked); - if (mMainSwitchBar != null && mMainSwitchBar.isChecked() != checked) { - mMainSwitchBar.setChecked(checked); - } - } - - @Override - public void setTitle(CharSequence title) { - super.setTitle(title); - if (mMainSwitchBar != null) { - mMainSwitchBar.setTitle(title); - } - } - - @Override - public void setSummary(CharSequence summary) { - super.setSummary(summary); - if (mMainSwitchBar != null - && Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) { - mMainSwitchBar.setSummary(summary); - } - } - - @Override - public void setIconSpaceReserved(boolean iconSpaceReserved) { - super.setIconSpaceReserved(iconSpaceReserved); - if (mMainSwitchBar != null) { - mMainSwitchBar.setIconSpaceReserved(iconSpaceReserved); - } + mainSwitchBar.show(); } @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { super.setChecked(isChecked); - } - - /** - * Update the switch status of preference - */ - public void updateStatus(boolean checked) { - setChecked(checked); - if (mMainSwitchBar != null) { - mMainSwitchBar.setTitle(getTitle()); - mMainSwitchBar.show(); + for (OnCheckedChangeListener listener : mSwitchChangeListeners) { + listener.onCheckedChanged(buttonView, isChecked); } } @@ -162,10 +106,6 @@ public class MainSwitchPreference extends TwoStatePreference if (!mSwitchChangeListeners.contains(listener)) { mSwitchChangeListeners.add(listener); } - - if (mMainSwitchBar != null) { - mMainSwitchBar.addOnSwitchChangeListener(listener); - } } /** @@ -173,14 +113,5 @@ public class MainSwitchPreference extends TwoStatePreference */ public void removeOnSwitchChangeListener(OnCheckedChangeListener listener) { mSwitchChangeListeners.remove(listener); - if (mMainSwitchBar != null) { - mMainSwitchBar.removeOnSwitchChangeListener(listener); - } - } - - private void registerListenerToSwitchBar() { - for (OnCheckedChangeListener listener : mSwitchChangeListeners) { - mMainSwitchBar.addOnSwitchChangeListener(listener); - } } } diff --git a/packages/SettingsLib/Metadata/processor/src/com/android/settingslib/metadata/PreferenceScreenAnnotationProcessor.kt b/packages/SettingsLib/Metadata/processor/src/com/android/settingslib/metadata/PreferenceScreenAnnotationProcessor.kt index 38b641336547..69b75adea9d3 100644 --- a/packages/SettingsLib/Metadata/processor/src/com/android/settingslib/metadata/PreferenceScreenAnnotationProcessor.kt +++ b/packages/SettingsLib/Metadata/processor/src/com/android/settingslib/metadata/PreferenceScreenAnnotationProcessor.kt @@ -33,6 +33,9 @@ import javax.tools.Diagnostic /** Processor to gather preference screens annotated with `@ProvidePreferenceScreen`. */ class PreferenceScreenAnnotationProcessor : AbstractProcessor() { private val screens = mutableListOf<Screen>() + private val bundleType: TypeMirror by lazy { + processingEnv.elementUtils.getTypeElement("android.os.Bundle").asType() + } private val contextType: TypeMirror by lazy { processingEnv.elementUtils.getTypeElement("android.content.Context").asType() } @@ -83,19 +86,57 @@ class PreferenceScreenAnnotationProcessor : AbstractProcessor() { error("@$ANNOTATION_NAME must be added to $PREFERENCE_SCREEN_METADATA subclass", this) return } - val constructorType = getConstructorType() - if (constructorType == null) { + fun reportConstructorError() = error( - "Class must be an object, or has single public constructor that " + - "accepts no parameter or a Context parameter", + "Must have only one public constructor: constructor(), " + + "constructor(Context), constructor(Bundle) or constructor(Context, Bundle)", this, ) + val constructor = findConstructor() + if (constructor == null || constructor.parameters.size > 2) { + reportConstructorError() return } + val constructorHasContextParameter = constructor.hasParameter(0, contextType) + var index = if (constructorHasContextParameter) 1 else 0 val annotation = annotationMirrors.single { it.isElement(annotationElement) } val key = annotation.fieldValue<String>("value")!! val overlay = annotation.fieldValue<Boolean>("overlay") == true - screens.add(Screen(key, overlay, qualifiedName.toString(), constructorType)) + val parameterized = annotation.fieldValue<Boolean>("parameterized") == true + var parametersHasContextParameter = false + if (parameterized) { + val parameters = findParameters() + if (parameters == null) { + error("require a static 'parameters()' or 'parameters(Context)' method", this) + return + } + parametersHasContextParameter = parameters + if (constructor.hasParameter(index, bundleType)) { + index++ + } else { + error( + "Parameterized screen constructor must be" + + "constructor(Bundle) or constructor(Context, Bundle)", + this, + ) + return + } + } + if (index == constructor.parameters.size) { + screens.add( + Screen( + key, + overlay, + parameterized, + annotation.fieldValue<Boolean>("parameterizedMigration") == true, + qualifiedName.toString(), + constructorHasContextParameter, + parametersHasContextParameter, + ) + ) + } else { + reportConstructorError() + } } private fun codegen() { @@ -116,10 +157,15 @@ class PreferenceScreenAnnotationProcessor : AbstractProcessor() { screens.sort() processingEnv.filer.createSourceFile("$outputPkg.$outputClass").openWriter().use { it.write("package $outputPkg;\n\n") + it.write("import android.content.Context;\n") + it.write("import android.os.Bundle;\n") it.write("import $PACKAGE.FixedArrayMap;\n") it.write("import $PACKAGE.FixedArrayMap.OrderedInitializer;\n") - it.write("import $PACKAGE.$FACTORY;\n\n") - it.write("// Generated by annotation processor for @$ANNOTATION_NAME\n") + it.write("import $PACKAGE.$PREFERENCE_SCREEN_METADATA;\n") + it.write("import $PACKAGE.$FACTORY;\n") + it.write("import $PACKAGE.$PARAMETERIZED_FACTORY;\n") + it.write("import kotlinx.coroutines.flow.Flow;\n") + it.write("\n// Generated by annotation processor for @$ANNOTATION_NAME\n") it.write("public final class $outputClass {\n") it.write(" private $outputClass() {}\n\n") it.write(" public static FixedArrayMap<String, $FACTORY> $outputFun() {\n") @@ -127,10 +173,29 @@ class PreferenceScreenAnnotationProcessor : AbstractProcessor() { it.write(" return new FixedArrayMap<>($size, $outputClass::init);\n") it.write(" }\n\n") fun Screen.write() { - it.write(" screens.put(\"$key\", context -> new $klass(") - when (constructorType) { - ConstructorType.DEFAULT -> it.write("));") - ConstructorType.CONTEXT -> it.write("context));") + it.write(" screens.put(\"$key\", ") + if (parameterized) { + it.write("new $PARAMETERIZED_FACTORY() {\n") + it.write(" @Override public PreferenceScreenMetadata create") + it.write("(Context context, Bundle args) {\n") + it.write(" return new $klass(") + if (constructorHasContextParameter) it.write("context, ") + it.write("args);\n") + it.write(" }\n\n") + it.write(" @Override public Flow<Bundle> parameters(Context context) {\n") + it.write(" return $klass.parameters(") + if (parametersHasContextParameter) it.write("context") + it.write(");\n") + it.write(" }\n") + if (parameterizedMigration) { + it.write("\n @Override public boolean acceptEmptyArguments()") + it.write(" { return true; }\n") + } + it.write(" });") + } else { + it.write("context -> new $klass(") + if (constructorHasContextParameter) it.write("context") + it.write("));") } if (overlay) it.write(" // overlay") it.write("\n") @@ -159,7 +224,7 @@ class PreferenceScreenAnnotationProcessor : AbstractProcessor() { } private fun AnnotationMirror.isElement(element: TypeElement) = - processingEnv.typeUtils.isSameType(annotationType.asElement().asType(), element.asType()) + annotationType.asElement().asType().isSameType(element.asType()) @Suppress("UNCHECKED_CAST") private fun <T> AnnotationMirror.fieldValue(name: String): T? = field(name)?.value as? T @@ -171,7 +236,7 @@ class PreferenceScreenAnnotationProcessor : AbstractProcessor() { return null } - private fun TypeElement.getConstructorType(): ConstructorType? { + private fun TypeElement.findConstructor(): ExecutableElement? { var constructor: ExecutableElement? = null for (element in enclosedElements) { if (element.kind != ElementKind.CONSTRUCTOR) continue @@ -179,16 +244,30 @@ class PreferenceScreenAnnotationProcessor : AbstractProcessor() { if (constructor != null) return null constructor = element as ExecutableElement } - return constructor?.parameters?.run { - when { - isEmpty() -> ConstructorType.DEFAULT - size == 1 && processingEnv.typeUtils.isSameType(this[0].asType(), contextType) -> - ConstructorType.CONTEXT - else -> null - } + return constructor + } + + private fun TypeElement.findParameters(): Boolean? { + for (element in enclosedElements) { + if (element.kind != ElementKind.METHOD) continue + if (!element.modifiers.contains(Modifier.PUBLIC)) continue + if (!element.modifiers.contains(Modifier.STATIC)) continue + if (!element.simpleName.contentEquals("parameters")) return null + val parameters = (element as ExecutableElement).parameters + if (parameters.isEmpty()) return false + if (parameters.size == 1 && parameters[0].asType().isSameType(contextType)) return true + error("parameters method should have no parameter or a Context parameter", element) + return null } + return null } + private fun ExecutableElement.hasParameter(index: Int, typeMirror: TypeMirror) = + index < parameters.size && parameters[index].asType().isSameType(typeMirror) + + private fun TypeMirror.isSameType(typeMirror: TypeMirror) = + processingEnv.typeUtils.isSameType(this, typeMirror) + private fun warn(msg: CharSequence) = processingEnv.messager.printMessage(Diagnostic.Kind.WARNING, msg) @@ -198,8 +277,11 @@ class PreferenceScreenAnnotationProcessor : AbstractProcessor() { private data class Screen( val key: String, val overlay: Boolean, + val parameterized: Boolean, + val parameterizedMigration: Boolean, val klass: String, - val constructorType: ConstructorType, + val constructorHasContextParameter: Boolean, + val parametersHasContextParameter: Boolean, ) : Comparable<Screen> { override fun compareTo(other: Screen): Int { val diff = key.compareTo(other.key) @@ -207,17 +289,13 @@ class PreferenceScreenAnnotationProcessor : AbstractProcessor() { } } - private enum class ConstructorType { - DEFAULT, // default constructor with no parameter - CONTEXT, // constructor with a Context parameter - } - companion object { private const val PACKAGE = "com.android.settingslib.metadata" private const val ANNOTATION_NAME = "ProvidePreferenceScreen" private const val ANNOTATION = "$PACKAGE.$ANNOTATION_NAME" private const val PREFERENCE_SCREEN_METADATA = "PreferenceScreenMetadata" private const val FACTORY = "PreferenceScreenMetadataFactory" + private const val PARAMETERIZED_FACTORY = "PreferenceScreenMetadataParameterizedFactory" private const val OPTIONS_NAME = "ProvidePreferenceScreenOptions" private const val OPTIONS = "$PACKAGE.$OPTIONS_NAME" diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/Annotations.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/Annotations.kt index 4bed795ea760..449c78ce8965 100644 --- a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/Annotations.kt +++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/Annotations.kt @@ -22,14 +22,27 @@ package com.android.settingslib.metadata * The annotated class must satisfy either condition: * - the primary constructor has no parameter * - the primary constructor has a single [android.content.Context] parameter + * - (parameterized) the primary constructor has a single [android.os.Bundle] parameter to override + * [PreferenceScreenMetadata.arguments] + * - (parameterized) the primary constructor has a [android.content.Context] and a + * [android.os.Bundle] parameter to override [PreferenceScreenMetadata.arguments] * * @param value unique preference screen key * @param overlay if true, current annotated screen will overlay the screen that has identical key + * @param parameterized if true, the screen relies on additional arguments to build its content + * @param parameterizedMigration whether the parameterized screen was a normal screen, in which case + * `Bundle.EMPTY` will be passed as arguments to take care of backward compatibility + * @see PreferenceScreenMetadata */ @Retention(AnnotationRetention.SOURCE) @Target(AnnotationTarget.CLASS) @MustBeDocumented -annotation class ProvidePreferenceScreen(val value: String, val overlay: Boolean = false) +annotation class ProvidePreferenceScreen( + val value: String, + val overlay: Boolean = false, + val parameterized: Boolean = false, + val parameterizedMigration: Boolean = false, // effective only when parameterized is true +) /** * Provides options for [ProvidePreferenceScreen] annotation processor. diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/Bundles.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/Bundles.kt new file mode 100644 index 000000000000..a63576510aec --- /dev/null +++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/Bundles.kt @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.metadata + +import android.content.Intent +import android.os.Bundle + +@Suppress("DEPRECATION") +fun Bundle?.contentEquals(other: Bundle?): Boolean { + if (this == null) return other == null + if (other == null) return false + if (keySet() != other.keySet()) return false + fun Any?.valueEquals(other: Any?) = + when (this) { + is Bundle -> other is Bundle && this.contentEquals(other) + is Intent -> other is Intent && this.filterEquals(other) + is BooleanArray -> other is BooleanArray && this contentEquals other + is ByteArray -> other is ByteArray && this contentEquals other + is CharArray -> other is CharArray && this contentEquals other + is DoubleArray -> other is DoubleArray && this contentEquals other + is FloatArray -> other is FloatArray && this contentEquals other + is IntArray -> other is IntArray && this contentEquals other + is LongArray -> other is LongArray && this contentEquals other + is ShortArray -> other is ShortArray && this contentEquals other + is Array<*> -> other is Array<*> && this contentDeepEquals other + else -> this == other + } + for (key in keySet()) { + if (!get(key).valueEquals(other.get(key))) return false + } + return true +} diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/Metrics.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/Metrics.kt index 7323488c5299..e532e545cc11 100644 --- a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/Metrics.kt +++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/Metrics.kt @@ -16,6 +16,8 @@ package com.android.settingslib.metadata +import android.content.Context + /** Metrics logger for preference actions triggered by user interaction. */ interface PreferenceUiActionMetricsLogger { @@ -28,8 +30,34 @@ interface PreferenceUiActionMetricsLogger { screen: PreferenceScreenMetadata, preference: PreferenceMetadata, value: Any?, - ) {} + ) } /** Metrics logger for preference remote operations (e.g. external get/set). */ -interface PreferenceRemoteOpMetricsLogger +interface PreferenceRemoteOpMetricsLogger { + + /** Logs get preference metadata operation. */ + fun logGetterApi( + context: Context, + callingUid: Int, + preferenceCoordinate: PreferenceCoordinate, + screen: PreferenceScreenMetadata?, + preference: PreferenceMetadata?, + errorCode: Int, + latencyMs: Long, + ) + + /** Logs set preference value operation. */ + fun logSetterApi( + context: Context, + callingUid: Int, + preferenceCoordinate: PreferenceCoordinate, + screen: PreferenceScreenMetadata?, + preference: PreferenceMetadata?, + errorCode: Int, + latencyMs: Long, + ) + + /** Logs get preference graph operation. */ + fun logGraphApi(context: Context, callingUid: Int, success: Boolean, latencyMs: Long) +} 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 63f1050df94e..e456a7f1aa1c 100644 --- a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PersistentPreference.kt +++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PersistentPreference.kt @@ -78,13 +78,8 @@ annotation class SensitivityLevel { /** Preference metadata that has a value persisted in datastore. */ interface PersistentPreference<T> : PreferenceMetadata { - /** - * The value type the preference is associated with. - * - * TODO(b/388167302): Remove the default implementation once all subclasses are migrated. - */ - val valueType: Class<T>? - get() = null + /** The value type the preference is associated with. */ + val valueType: Class<T> /** * Returns the key-value storage of the preference. diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceCoordinate.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceCoordinate.kt index 2dd736ae6083..ac08847b6002 100644 --- a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceCoordinate.kt +++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceCoordinate.kt @@ -16,26 +16,41 @@ package com.android.settingslib.metadata +import android.os.Bundle import android.os.Parcel import android.os.Parcelable /** * Coordinate to locate a preference. * - * Within an app, the preference screen key (unique among screens) plus preference key (unique on - * the screen) is used to locate a preference. + * Within an app, the preference screen coordinate (unique among screens) plus preference key + * (unique on the screen) is used to locate a preference. */ -data class PreferenceCoordinate(val screenKey: String, val key: String) : Parcelable { +open class PreferenceCoordinate : PreferenceScreenCoordinate { + val key: String - constructor(parcel: Parcel) : this(parcel.readString()!!, parcel.readString()!!) + constructor(screenKey: String, key: String) : this(screenKey, null, key) + + constructor(screenKey: String, args: Bundle?, key: String) : super(screenKey, args) { + this.key = key + } + + constructor(parcel: Parcel) : super(parcel) { + this.key = parcel.readString()!! + } override fun writeToParcel(parcel: Parcel, flags: Int) { - parcel.writeString(screenKey) + super.writeToParcel(parcel, flags) parcel.writeString(key) } override fun describeContents() = 0 + override fun equals(other: Any?) = + super.equals(other) && key == (other as PreferenceCoordinate).key + + override fun hashCode() = super.hashCode() xor key.hashCode() + companion object CREATOR : Parcelable.Creator<PreferenceCoordinate> { override fun createFromParcel(parcel: Parcel) = PreferenceCoordinate(parcel) @@ -43,3 +58,46 @@ data class PreferenceCoordinate(val screenKey: String, val key: String) : Parcel override fun newArray(size: Int) = arrayOfNulls<PreferenceCoordinate>(size) } } + +/** Coordinate to locate a preference screen. */ +open class PreferenceScreenCoordinate : Parcelable { + /** Unique preference screen key. */ + val screenKey: String + + /** Arguments to create parameterized preference screen. */ + val args: Bundle? + + constructor(screenKey: String, args: Bundle?) { + this.screenKey = screenKey + this.args = args + } + + constructor(parcel: Parcel) { + screenKey = parcel.readString()!! + args = parcel.readBundle(javaClass.classLoader) + } + + override fun writeToParcel(parcel: Parcel, flags: Int) { + parcel.writeString(screenKey) + parcel.writeBundle(args) + } + + override fun describeContents() = 0 + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + other as PreferenceScreenCoordinate + return screenKey == other.screenKey && args.contentEquals(other.args) + } + + // "args" is not included intentionally, otherwise we need to take care of array, etc. + override fun hashCode() = screenKey.hashCode() + + companion object CREATOR : Parcelable.Creator<PreferenceScreenCoordinate> { + + override fun createFromParcel(parcel: Parcel) = PreferenceScreenCoordinate(parcel) + + override fun newArray(size: Int) = arrayOfNulls<PreferenceScreenCoordinate>(size) + } +} diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceHierarchy.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceHierarchy.kt index 876f6152cccd..3bd051dee41d 100644 --- a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceHierarchy.kt +++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceHierarchy.kt @@ -17,6 +17,7 @@ package com.android.settingslib.metadata import android.content.Context +import android.os.Bundle /** A node in preference hierarchy that is associated with [PreferenceMetadata]. */ open class PreferenceHierarchyNode internal constructor(val metadata: PreferenceMetadata) { @@ -54,8 +55,14 @@ internal constructor(private val context: Context, metadata: PreferenceMetadata) * * @throws NullPointerException if screen is not registered to [PreferenceScreenRegistry] */ - operator fun String.unaryPlus() = - +PreferenceHierarchyNode(PreferenceScreenRegistry.create(context, this)!!) + operator fun String.unaryPlus() = addPreferenceScreen(this, null) + + /** + * Adds parameterized preference screen with given key (as a placeholder) to the hierarchy. + * + * @see String.unaryPlus + */ + infix fun String.args(args: Bundle) = createPreferenceScreenHierarchy(this, args) operator fun PreferenceHierarchyNode.unaryPlus() = also { children.add(it) } @@ -122,6 +129,14 @@ internal constructor(private val context: Context, metadata: PreferenceMetadata) } /** + * Adds parameterized preference screen with given key (as a placeholder) to the hierarchy. + * + * @see addPreferenceScreen + */ + fun addParameterizedScreen(screenKey: String, args: Bundle) = + addPreferenceScreen(screenKey, args) + + /** * Adds preference screen with given key (as a placeholder) to the hierarchy. * * This is mainly to support Android Settings overlays. OEMs might want to custom some of the @@ -132,11 +147,13 @@ internal constructor(private val context: Context, metadata: PreferenceMetadata) * * @throws NullPointerException if screen is not registered to [PreferenceScreenRegistry] */ - fun addPreferenceScreen(screenKey: String) { - children.add( - PreferenceHierarchy(context, PreferenceScreenRegistry.create(context, screenKey)!!) - ) - } + fun addPreferenceScreen(screenKey: String) = addPreferenceScreen(screenKey, null) + + private fun addPreferenceScreen(screenKey: String, args: Bundle?): PreferenceHierarchyNode = + createPreferenceScreenHierarchy(screenKey, args).also { children.add(it) } + + private fun createPreferenceScreenHierarchy(screenKey: String, args: Bundle?) = + PreferenceHierarchyNode(PreferenceScreenRegistry.create(context, screenKey, args)!!) /** Extensions to add more preferences to the hierarchy. */ operator fun PreferenceHierarchy.plusAssign(init: PreferenceHierarchy.() -> Unit) = init(this) diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceScreenBindingKeyProvider.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceScreenBindingKeyProvider.kt index 84014f191f68..4fd13ede6803 100644 --- a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceScreenBindingKeyProvider.kt +++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceScreenBindingKeyProvider.kt @@ -17,13 +17,20 @@ package com.android.settingslib.metadata import android.content.Context +import android.os.Bundle /** Provides the associated preference screen key for binding. */ interface PreferenceScreenBindingKeyProvider { /** Returns the associated preference screen key. */ fun getPreferenceScreenBindingKey(context: Context): String? + + /** Returns the arguments to build preference screen. */ + fun getPreferenceScreenBindingArgs(context: Context): Bundle? } /** Extra key to provide the preference screen key for binding. */ const val EXTRA_BINDING_SCREEN_KEY = "settingslib:binding_screen_key" + +/** Extra key to provide arguments for preference screen binding. */ +const val EXTRA_BINDING_SCREEN_ARGS = "settingslib:binding_screen_args" diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceScreenMetadata.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceScreenMetadata.kt index 850d4523e96e..7f1ded71e30a 100644 --- a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceScreenMetadata.kt +++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceScreenMetadata.kt @@ -18,12 +18,25 @@ package com.android.settingslib.metadata import android.content.Context import android.content.Intent +import android.os.Bundle import androidx.annotation.AnyThread import androidx.fragment.app.Fragment +import kotlinx.coroutines.flow.Flow -/** Metadata of preference screen. */ +/** + * Metadata of preference screen. + * + * For parameterized preference screen that relies on additional information (e.g. package name, + * language code) to build its content, the subclass must: + * - override [arguments] in constructor + * - add a static method `fun parameters(context: Context): List<Bundle>` (context is optional) to + * provide all possible arguments + */ @AnyThread interface PreferenceScreenMetadata : PreferenceMetadata { + /** Arguments to build the screen content. */ + val arguments: Bundle? + get() = null /** * The screen title resource, which precedes [getScreenTitle] if provided. @@ -65,7 +78,12 @@ interface PreferenceScreenMetadata : PreferenceMetadata { fun getLaunchIntent(context: Context, metadata: PreferenceMetadata?): Intent? = null } -/** Factory of [PreferenceScreenMetadata]. */ +/** + * Factory of [PreferenceScreenMetadata]. + * + * Annotation processor generates implementation of this interface based on + * [ProvidePreferenceScreen] when [ProvidePreferenceScreen.parameterized] is `false`. + */ fun interface PreferenceScreenMetadataFactory { /** @@ -75,3 +93,44 @@ fun interface PreferenceScreenMetadataFactory { */ fun create(context: Context): PreferenceScreenMetadata } + +/** + * Parameterized factory of [PreferenceScreenMetadata]. + * + * Annotation processor generates implementation of this interface based on + * [ProvidePreferenceScreen] when [ProvidePreferenceScreen.parameterized] is `true`. + */ +interface PreferenceScreenMetadataParameterizedFactory : PreferenceScreenMetadataFactory { + override fun create(context: Context) = create(context, Bundle.EMPTY) + + /** + * Creates a new [PreferenceScreenMetadata] with given arguments. + * + * @param context application context to create the PreferenceScreenMetadata + * @param args arguments to create the screen metadata, [Bundle.EMPTY] is reserved for the + * default case when screen is migrated from normal to parameterized + */ + fun create(context: Context, args: Bundle): PreferenceScreenMetadata + + /** + * Returns all possible arguments to create [PreferenceScreenMetadata]. + * + * Note that [Bundle.EMPTY] is a special arguments reserved for backward compatibility when a + * preference screen was a normal screen but migrated to parameterized screen later: + * 1. Set [ProvidePreferenceScreen.parameterizedMigration] to `true`, so that the generated + * [acceptEmptyArguments] will be `true`. + * 1. In the original [parameters] implementation, produce a [Bundle.EMPTY] for the default + * case. + * + * Do not use [Bundle.EMPTY] for other purpose. + */ + fun parameters(context: Context): Flow<Bundle> + + /** + * Returns true when the parameterized screen was a normal screen. + * + * The [PreferenceScreenMetadata] is expected to accept an empty arguments ([Bundle.EMPTY]) and + * take care of backward compatibility. + */ + fun acceptEmptyArguments(): Boolean = false +} diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceScreenRegistry.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceScreenRegistry.kt index c74b3151abb2..246310984db9 100644 --- a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceScreenRegistry.kt +++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceScreenRegistry.kt @@ -17,10 +17,13 @@ package com.android.settingslib.metadata import android.content.Context +import android.os.Bundle +import android.util.Log import com.android.settingslib.datastore.KeyValueStore /** Registry of all available preference screens in the app. */ object PreferenceScreenRegistry : ReadWritePermitProvider { + private const val TAG = "ScreenRegistry" /** Provider of key-value store. */ private lateinit var keyValueStoreProvider: KeyValueStoreProvider @@ -52,9 +55,28 @@ object PreferenceScreenRegistry : ReadWritePermitProvider { fun getKeyValueStore(context: Context, preference: PreferenceMetadata): KeyValueStore? = keyValueStoreProvider.getKeyValueStore(context, preference) - /** Creates [PreferenceScreenMetadata] of particular screen key. */ - fun create(context: Context, screenKey: String?): PreferenceScreenMetadata? = - screenKey?.let { preferenceScreenMetadataFactories[it]?.create(context.applicationContext) } + /** Creates [PreferenceScreenMetadata] of particular screen. */ + fun create(context: Context, screenCoordinate: PreferenceScreenCoordinate) = + create(context, screenCoordinate.screenKey, screenCoordinate.args) + + /** Creates [PreferenceScreenMetadata] of particular screen key with given arguments. */ + fun create(context: Context, screenKey: String?, args: Bundle?): PreferenceScreenMetadata? { + if (screenKey == null) return null + val factory = preferenceScreenMetadataFactories[screenKey] ?: return null + val appContext = context.applicationContext + if (factory is PreferenceScreenMetadataParameterizedFactory) { + if (args != null) return factory.create(appContext, args) + // In case the parameterized screen was a normal scree, it is expected to accept + // Bundle.EMPTY arguments and take care of backward compatibility. + if (factory.acceptEmptyArguments()) return factory.create(appContext) + Log.e(TAG, "screen $screenKey is parameterized but args is not provided") + return null + } else { + if (args == null) return factory.create(appContext) + Log.e(TAG, "screen $screenKey is not parameterized but args is provided") + return null + } + } /** * Sets the provider to check read write permit. Read and write requests are denied by default. 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 65fbe2b66e77..dbac17d4e8b8 100644 --- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt +++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt @@ -22,6 +22,7 @@ import androidx.preference.PreferenceCategory import androidx.preference.PreferenceScreen import androidx.preference.SwitchPreferenceCompat import androidx.preference.TwoStatePreference +import com.android.settingslib.metadata.EXTRA_BINDING_SCREEN_ARGS import com.android.settingslib.metadata.EXTRA_BINDING_SCREEN_KEY import com.android.settingslib.metadata.PreferenceMetadata import com.android.settingslib.metadata.PreferenceScreenMetadata @@ -35,9 +36,11 @@ interface PreferenceScreenBinding : PreferenceBinding { super.bind(preference, metadata) val context = preference.context val screenMetadata = metadata as PreferenceScreenMetadata + val extras = preference.extras // Pass the preference key to fragment, so that the fragment could find associated // preference screen registered in PreferenceScreenRegistry - preference.extras.putString(EXTRA_BINDING_SCREEN_KEY, preference.key) + extras.putString(EXTRA_BINDING_SCREEN_KEY, preference.key) + screenMetadata.arguments?.let { extras.putBundle(EXTRA_BINDING_SCREEN_ARGS, it) } if (preference is PreferenceScreen) { val screenTitle = screenMetadata.screenTitle preference.title = diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceFragment.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceFragment.kt index ffe181d0c350..02f91c1bb50b 100644 --- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceFragment.kt +++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceFragment.kt @@ -23,6 +23,7 @@ import android.util.Log import androidx.annotation.XmlRes import androidx.lifecycle.Lifecycle import androidx.preference.PreferenceScreen +import com.android.settingslib.metadata.EXTRA_BINDING_SCREEN_ARGS import com.android.settingslib.metadata.EXTRA_BINDING_SCREEN_KEY import com.android.settingslib.metadata.PreferenceScreenBindingKeyProvider import com.android.settingslib.metadata.PreferenceScreenRegistry @@ -89,13 +90,19 @@ open class PreferenceFragment : @XmlRes protected open fun getPreferenceScreenResId(context: Context): Int = 0 protected fun getPreferenceScreenCreator(context: Context): PreferenceScreenCreator? = - (PreferenceScreenRegistry.create(context, getPreferenceScreenBindingKey(context)) - as? PreferenceScreenCreator) + (PreferenceScreenRegistry.create( + context, + getPreferenceScreenBindingKey(context), + getPreferenceScreenBindingArgs(context), + ) as? PreferenceScreenCreator) ?.run { if (isFlagEnabled(context)) this else null } override fun getPreferenceScreenBindingKey(context: Context): String? = arguments?.getString(EXTRA_BINDING_SCREEN_KEY) + override fun getPreferenceScreenBindingArgs(context: Context): Bundle? = + arguments?.getBundle(EXTRA_BINDING_SCREEN_ARGS) + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) preferenceScreenBindingHelper?.onCreate() 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 4a6a589cd3c9..1cb8005ddae0 100644 --- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt +++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt @@ -31,6 +31,7 @@ import com.android.settingslib.datastore.KeyValueStore import com.android.settingslib.datastore.KeyedDataObservable import com.android.settingslib.datastore.KeyedObservable import com.android.settingslib.datastore.KeyedObserver +import com.android.settingslib.metadata.EXTRA_BINDING_SCREEN_ARGS import com.android.settingslib.metadata.PersistentPreference import com.android.settingslib.metadata.PreferenceChangeReason import com.android.settingslib.metadata.PreferenceHierarchy @@ -227,14 +228,16 @@ class PreferenceScreenBindingHelper( /** Updates preference screen that has incomplete hierarchy. */ @JvmStatic fun bind(preferenceScreen: PreferenceScreen) { - PreferenceScreenRegistry.create(preferenceScreen.context, preferenceScreen.key)?.run { + val context = preferenceScreen.context + val args = preferenceScreen.peekExtras()?.getBundle(EXTRA_BINDING_SCREEN_ARGS) + PreferenceScreenRegistry.create(context, preferenceScreen.key, args)?.run { if (!hasCompleteHierarchy()) { val preferenceBindingFactory = (this as? PreferenceScreenCreator)?.preferenceBindingFactory ?: return bindRecursively( preferenceScreen, preferenceBindingFactory, - getPreferenceHierarchy(preferenceScreen.context), + getPreferenceHierarchy(context), ) } } diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenFactory.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenFactory.kt index 211b3bdaea70..88c4fe6bf188 100644 --- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenFactory.kt +++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenFactory.kt @@ -17,10 +17,12 @@ package com.android.settingslib.preference import android.content.Context +import android.os.Bundle import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceManager import androidx.preference.PreferenceScreen +import com.android.settingslib.metadata.EXTRA_BINDING_SCREEN_ARGS import com.android.settingslib.metadata.PreferenceScreenRegistry /** Factory to create preference screen. */ @@ -81,8 +83,12 @@ class PreferenceScreenFactory { * * The screen must be registered in [PreferenceScreenFactory] and provide a complete hierarchy. */ - fun createBindingScreen(context: Context, screenKey: String?): PreferenceScreen? { - val metadata = PreferenceScreenRegistry.create(context, screenKey) ?: return null + fun createBindingScreen( + context: Context, + screenKey: String?, + args: Bundle?, + ): PreferenceScreen? { + val metadata = PreferenceScreenRegistry.create(context, screenKey, args) ?: return null if (metadata is PreferenceScreenCreator && metadata.hasCompleteHierarchy()) { return metadata.createPreferenceScreen(this) } @@ -94,8 +100,9 @@ class PreferenceScreenFactory { @JvmStatic fun createBindingScreen(preference: Preference): PreferenceScreen? { val context = preference.context + val args = preference.peekExtras()?.getBundle(EXTRA_BINDING_SCREEN_ARGS) val preferenceScreenCreator = - (PreferenceScreenRegistry.create(context, preference.key) + (PreferenceScreenRegistry.create(context, preference.key, args) as? PreferenceScreenCreator) ?: return null if (!preferenceScreenCreator.hasCompleteHierarchy()) return null val factory = PreferenceScreenFactory(context) diff --git a/packages/SettingsLib/Service/src/com/android/settingslib/service/PreferenceGraphApi.kt b/packages/SettingsLib/Service/src/com/android/settingslib/service/PreferenceGraphApi.kt deleted file mode 100644 index ae9642a38778..000000000000 --- a/packages/SettingsLib/Service/src/com/android/settingslib/service/PreferenceGraphApi.kt +++ /dev/null @@ -1,40 +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.settingslib.service - -import android.app.Application -import com.android.settingslib.graph.GetPreferenceGraphApiHandler -import com.android.settingslib.graph.GetPreferenceGraphRequest -import com.android.settingslib.ipc.ApiPermissionChecker -import com.android.settingslib.preference.PreferenceScreenProvider - -/** Api to get preference graph. */ -internal class PreferenceGraphApi( - preferenceScreenProviders: Set<Class<out PreferenceScreenProvider>>, - private val permissionChecker: ApiPermissionChecker<GetPreferenceGraphRequest>, -) : GetPreferenceGraphApiHandler(preferenceScreenProviders) { - - override val id: Int - get() = API_GET_PREFERENCE_GRAPH - - override fun hasPermission( - application: Application, - callingPid: Int, - callingUid: Int, - request: GetPreferenceGraphRequest, - ) = permissionChecker.hasPermission(application, callingPid, callingUid, request) -} diff --git a/packages/SettingsLib/Service/src/com/android/settingslib/service/PreferenceService.kt b/packages/SettingsLib/Service/src/com/android/settingslib/service/PreferenceService.kt index 7cb36db856eb..5def592a1afa 100644 --- a/packages/SettingsLib/Service/src/com/android/settingslib/service/PreferenceService.kt +++ b/packages/SettingsLib/Service/src/com/android/settingslib/service/PreferenceService.kt @@ -16,6 +16,7 @@ package com.android.settingslib.service +import com.android.settingslib.graph.GetPreferenceGraphApiHandler import com.android.settingslib.graph.GetPreferenceGraphRequest import com.android.settingslib.graph.PreferenceGetterApiHandler import com.android.settingslib.graph.PreferenceGetterRequest @@ -25,6 +26,7 @@ import com.android.settingslib.ipc.ApiHandler import com.android.settingslib.ipc.ApiPermissionChecker import com.android.settingslib.ipc.MessengerService import com.android.settingslib.ipc.PermissionChecker +import com.android.settingslib.metadata.PreferenceRemoteOpMetricsLogger import com.android.settingslib.preference.PreferenceScreenProvider /** @@ -40,16 +42,26 @@ open class PreferenceService( graphPermissionChecker: ApiPermissionChecker<GetPreferenceGraphRequest>? = null, setterPermissionChecker: ApiPermissionChecker<PreferenceSetterRequest>? = null, getterPermissionChecker: ApiPermissionChecker<PreferenceGetterRequest>? = null, + metricsLogger: PreferenceRemoteOpMetricsLogger? = null, vararg apiHandlers: ApiHandler<*, *>, ) : MessengerService( mutableListOf<ApiHandler<*, *>>().apply { - graphPermissionChecker?.let { add(PreferenceGraphApi(preferenceScreenProviders, it)) } + graphPermissionChecker?.let { + add( + GetPreferenceGraphApiHandler( + API_GET_PREFERENCE_GRAPH, + it, + metricsLogger, + preferenceScreenProviders, + ) + ) + } setterPermissionChecker?.let { - add(PreferenceSetterApiHandler(API_PREFERENCE_SETTER, it)) + add(PreferenceSetterApiHandler(API_PREFERENCE_SETTER, it, metricsLogger)) } getterPermissionChecker?.let { - add(PreferenceGetterApiHandler(API_PREFERENCE_GETTER, it)) + add(PreferenceGetterApiHandler(API_PREFERENCE_GETTER, it, metricsLogger)) } addAll(apiHandlers) }, diff --git a/packages/SettingsLib/SettingsTheme/res/values-af/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-af/strings.xml index eb48b4ec2515..5ebeab57b42b 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-af/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-af/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"Maak toe"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-am/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-am/strings.xml index 442819244b25..27dda46b72cf 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-am/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-am/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"አሰናብት"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-ar/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-ar/strings.xml index e6d4c7b6cd19..522977e596bb 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-ar/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-ar/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"إغلاق"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-as/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-as/strings.xml index 2b5a5c98b98c..094650753503 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-as/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-as/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"অগ্ৰাহ্য কৰক"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-az/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-az/strings.xml index c7adee3095f2..4ba23ddb1382 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-az/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-az/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"Qapadı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 index 8b245a63cd91..02e1e3a1eca1 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-b+sr+Latn/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-b+sr+Latn/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"Odbacite"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-be/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-be/strings.xml index b468f819397a..e92bae7fb49b 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-be/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-be/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"Закрыць"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-bg/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-bg/strings.xml index b177fa76a7d9..4a7f6a526e49 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-bg/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-bg/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"Отхвърляне"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-bn/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-bn/strings.xml index 67bb59fab828..22be594c76ae 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-bn/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-bn/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"বাতিল করুন"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-bs/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-bs/strings.xml index 31afc8b07002..3498577f3a7f 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-bs/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-bs/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"Odbacivanje"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-ca/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-ca/strings.xml index 0f999c9a20e2..0279fdea78cb 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-ca/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-ca/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"Ignora"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-cs/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-cs/strings.xml index 144dba82e50b..82e88c476f80 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-cs/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-cs/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"Zavřít"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-da/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-da/strings.xml index 85497f143ef9..fc72e00434b1 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-da/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-da/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"Luk"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-de/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-de/strings.xml index 9e47741a0945..cdf6da04173c 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-de/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-de/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"Schließen"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-el/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-el/strings.xml index 0b325b5ac4cd..6f6b753714d4 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-el/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-el/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"Παράβλεψη"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-en-rAU/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-en-rAU/strings.xml index 2539aa040a60..bb98403afa91 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-en-rAU/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-en-rAU/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"Dismiss"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-en-rCA/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-en-rCA/strings.xml index 2539aa040a60..bb98403afa91 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-en-rCA/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-en-rCA/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"Dismiss"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-en-rGB/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-en-rGB/strings.xml index 2539aa040a60..bb98403afa91 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-en-rGB/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-en-rGB/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"Dismiss"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-en-rIN/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-en-rIN/strings.xml index 2539aa040a60..bb98403afa91 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-en-rIN/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-en-rIN/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"Dismiss"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-es-rUS/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-es-rUS/strings.xml index c976a2ed7757..8e70d7c6ed60 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-es-rUS/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-es-rUS/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"Descartar"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-es/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-es/strings.xml index 72ba9d11ecf1..8fc34e254d47 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-es/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-es/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"Cerrar"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-et/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-et/strings.xml index 856360669354..92ca8492fe6c 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-et/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-et/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"Loobumine"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-eu/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-eu/strings.xml index d8c2c35aed31..e352421fdc45 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-eu/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-eu/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"Baztertu"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-fa/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-fa/strings.xml index 24087411b5b9..5b71864cbc7e 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-fa/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-fa/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"بستن"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-fi/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-fi/strings.xml index 0d226bf8bbe6..b4f46b5b53b9 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-fi/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-fi/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"Ohita"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-fr-rCA/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-fr-rCA/strings.xml index fecfaa275e5e..d5495df76ced 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-fr-rCA/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-fr-rCA/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"Fermer"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-fr/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-fr/strings.xml index fecfaa275e5e..d5495df76ced 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-fr/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-fr/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"Fermer"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-gl/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-gl/strings.xml index 7999aa1b4658..1f7bb4285b80 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-gl/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-gl/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"Pechar"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-gu/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-gu/strings.xml index 1457d34d3691..90a6c4e0fa4f 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-gu/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-gu/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"છોડી દો"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-hi/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-hi/strings.xml index 856379aaea84..e24aa793da80 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-hi/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-hi/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"खारिज करें"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-hr/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-hr/strings.xml index 2d637f4a94c0..9d95ca936b10 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-hr/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-hr/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"Odbaci"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-hu/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-hu/strings.xml index 42731635f35c..88e54d248fd5 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-hu/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-hu/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"Elvetés"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-hy/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-hy/strings.xml index 2fc65f0cb149..b7b09b3f6b4d 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-hy/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-hy/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"Փակել"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-in/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-in/strings.xml index 97c1d602b94a..bfe166c5e9d7 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-in/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-in/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"Tutup"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-is/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-is/strings.xml index cc4d05bc869c..d905546e7d36 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-is/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-is/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"Loka"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-it/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-it/strings.xml index 8edcf2450b22..88a7a60d73a9 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-it/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-it/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"Ignora"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-iw/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-iw/strings.xml index 784bd8e34789..80e48aca8497 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-iw/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-iw/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"סגירה"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-ja/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-ja/strings.xml index 4e7287c4eedd..8cd92737db31 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-ja/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-ja/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"閉じる"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-ka/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-ka/strings.xml index ec8f1ddaad7d..ab8b37092c84 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-ka/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-ka/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"დახურვა"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-kk/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-kk/strings.xml index 329ccbbcddde..801e938ceea7 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-kk/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-kk/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"Жабу"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-km/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-km/strings.xml index 5ca0269e8eb8..36d81e6213b2 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-km/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-km/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"ច្រានចោល"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-kn/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-kn/strings.xml index 3fc1fcc728a7..c11b77cb2ef1 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-kn/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-kn/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"ವಜಾಗೊಳಿಸಿ"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-ko/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-ko/strings.xml index 05d4921064cc..0724d5b99d32 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-ko/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-ko/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"닫기"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-ky/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-ky/strings.xml index 4f5b8dc3643d..b2c16e940e15 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-ky/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-ky/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"Жабуу"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-lo/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-lo/strings.xml index d6ec47939596..4e48b900563c 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-lo/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-lo/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"ປິດໄວ້"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-lt/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-lt/strings.xml index 54076c4fd3c2..a79877adb536 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-lt/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-lt/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"Uždaryti"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-lv/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-lv/strings.xml index 2f87b0eb7abe..aa2bd8c64efb 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-lv/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-lv/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"Nerādīt"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-mk/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-mk/strings.xml index b4f8fb901e26..704deb6d197f 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-mk/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-mk/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"Отфрли"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-ml/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-ml/strings.xml index c68141e31d96..e83d44db6994 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-ml/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-ml/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"ഡിസ്മിസ് ചെയ്യുക"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-mn/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-mn/strings.xml index 86b333d115d0..dda19c3bc510 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-mn/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-mn/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"Хаах"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-mr/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-mr/strings.xml index db9d42280528..26640cc895d0 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-mr/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-mr/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"डिसमिस करा"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-ms/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-ms/strings.xml index 1ffa5a083575..31a93ad67e85 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-ms/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-ms/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"Ketepikan"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-my/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-my/strings.xml index 6f79acc6e8ad..22aa721edb92 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-my/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-my/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"ပယ်ရန်"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-nb/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-nb/strings.xml index 359c9abe316e..c08ea5492888 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-nb/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-nb/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"Lukk"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-ne/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-ne/strings.xml index 374fd31129c9..b2ccbc5ef90b 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-ne/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-ne/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"बन्द गर्नुहोस्"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-nl/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-nl/strings.xml index 76a4f9996340..7469d3f34ee4 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-nl/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-nl/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"Sluiten"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-or/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-or/strings.xml index 1e1e87025986..18701e87034f 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-or/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-or/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"ଖାରଜ କରନ୍ତୁ"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-pa/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-pa/strings.xml index 48a756b81c9c..a47dec4030d2 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-pa/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-pa/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"ਖਾਰਜ ਕਰੋ"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-pl/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-pl/strings.xml index da273b3bd137..ad3bf851e34b 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-pl/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-pl/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"Zamknij"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-pt-rBR/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-pt-rBR/strings.xml index 4e3d0e624d16..b18b025dfb2f 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-pt-rBR/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-pt-rBR/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"Dispensar"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-pt-rPT/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-pt-rPT/strings.xml index 58bd936a8bae..185e7bdc4c3c 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-pt-rPT/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-pt-rPT/strings.xml @@ -19,4 +19,5 @@ 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">"Reduzir"</string> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"Ignorar"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-pt/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-pt/strings.xml index 4e3d0e624d16..b18b025dfb2f 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-pt/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-pt/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"Dispensar"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-ro/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-ro/strings.xml index ec208843efa4..2b705cfd4708 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-ro/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-ro/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"Închide"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-ru/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-ru/strings.xml index ba6ab9687d41..ec17fb956862 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-ru/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-ru/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"Закрыть"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-si/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-si/strings.xml index 9adb6466c9a4..e7351cda575b 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-si/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-si/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"අස් කරන්න"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-sk/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-sk/strings.xml index 574ee839a089..64ad93730023 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-sk/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-sk/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"Zavrieť"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-sl/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-sl/strings.xml index 6fd67c59ff3d..4b2483776edf 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-sl/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-sl/strings.xml @@ -19,4 +19,5 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Razširi"</string> <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Strni"</string> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"Opusti"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-sq/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-sq/strings.xml index e02ecbfada36..8accb18c9aa0 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-sq/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-sq/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"Hiq"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-sr/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-sr/strings.xml index 35f6aa3a635a..376276bb48c7 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-sr/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-sr/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"Одбаците"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-sv/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-sv/strings.xml index 241683984626..67d398406360 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-sv/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-sv/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"Stäng"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-sw/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-sw/strings.xml index 9a6075842a22..81cb868fc43b 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-sw/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-sw/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"Ondoa"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-ta/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-ta/strings.xml index 4a0fb4d96e03..27bae1f2846c 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-ta/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-ta/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"மூடும்"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-te/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-te/strings.xml index 706e225560c1..55a1c3ab924c 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-te/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-te/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"విస్మరించండి"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-th/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-th/strings.xml index d6dce9c076c9..895143e5f5bb 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-th/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-th/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"ปิด"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-tl/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-tl/strings.xml index ef5825ff1c3e..704aeb405ed0 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-tl/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-tl/strings.xml @@ -19,4 +19,5 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"I-expand"</string> <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"I-collapse"</string> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"I-dismiss"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-tr/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-tr/strings.xml index 8c7dbcfd987a..6849bfeca192 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-tr/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-tr/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"Kapat"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-uk/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-uk/strings.xml index 6da0ca81450e..f4ab5e29bfd0 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-uk/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-uk/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"Закрити"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-ur/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-ur/strings.xml index 2e020b4e0676..07d0de5b879c 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-ur/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-ur/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"برخاست کریں"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-uz/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-uz/strings.xml index 16e389b88405..c444171fa6b4 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-uz/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-uz/strings.xml @@ -19,4 +19,5 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="settingslib_expressive_text_expand" msgid="7520894876795775876">"Yoyish"</string> <string name="settingslib_expressive_text_collapse" msgid="5625043934702341576">"Yopish"</string> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"Yopish"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/styles_expressive.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/styles_expressive.xml index ec67d068123a..a6fe6ec94e01 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-v31/styles_expressive.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-v31/styles_expressive.xml @@ -104,18 +104,6 @@ <item name="android:layout_width">match_parent</item> </style> - <style name="SettingslibTextAppearance.LinkableTextStyle.Expressive" - parent="@style/TextAppearance.SettingsLib.LabelLarge"> - <item name="android:textColor">?android:attr/colorAccent</item> - </style> - - <style name="SettingslibTextButtonStyle.Expressive" - parent="@style/Widget.Material3Expressive.Button.TextButton.Icon"> - <item name="android:theme">@style/Theme.Material3.DynamicColors.DayNight</item> - <item name="android:layout_width">wrap_content</item> - <item name="android:layout_height">wrap_content</item> - </style> - <style name="SettingsLibCardStyle" parent=""> <item name="android:layout_width">match_parent</item> <item name="android:layout_height">wrap_content</item> diff --git a/packages/SettingsLib/SettingsTheme/res/values-vi/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-vi/strings.xml index 46f3351b7964..bfd2196f170b 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-vi/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-vi/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"Đóng"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-zh-rCN/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-zh-rCN/strings.xml index ea5f29b2413b..1b949be434f5 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-zh-rCN/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-zh-rCN/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"关闭"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-zh-rHK/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-zh-rHK/strings.xml index 36203139566c..d3555b4e0ea3 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-zh-rHK/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-zh-rHK/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"關閉"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-zh-rTW/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-zh-rTW/strings.xml index 36203139566c..d3555b4e0ea3 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-zh-rTW/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-zh-rTW/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"關閉"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-zu/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-zu/strings.xml index 725d8bc27b2f..91d184614b32 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-zu/strings.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-zu/strings.xml @@ -19,4 +19,5 @@ 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> + <string name="settingslib_dismiss_button_content_description" msgid="6466433970910120385">"Chitha"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values/styles_expressive.xml b/packages/SettingsLib/SettingsTheme/res/values/styles_expressive.xml index f73e100906c8..686c1488fb62 100644 --- a/packages/SettingsLib/SettingsTheme/res/values/styles_expressive.xml +++ b/packages/SettingsLib/SettingsTheme/res/values/styles_expressive.xml @@ -250,4 +250,16 @@ <item name="android:lineHeight" tools:targetApi="28">16sp</item> <item name="android:textAllCaps">false</item> </style> + + <style name="SettingslibTextAppearance.LinkableTextStyle.Expressive" + parent="@style/TextAppearance.SettingsLib.LabelLarge"> + <item name="android:textColor">?android:attr/colorAccent</item> + </style> + + <style name="SettingslibTextButtonStyle.Expressive" + parent="@style/Widget.Material3Expressive.Button.TextButton.Icon"> + <item name="android:theme">@style/Theme.Material3.DynamicColors.DayNight</item> + <item name="android:layout_width">wrap_content</item> + <item name="android:layout_height">wrap_content</item> + </style> </resources>
\ No newline at end of file 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 3309faaa8db2..3a6327962dc2 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 @@ -32,6 +32,7 @@ import kotlinx.coroutines.flow.flowOn data class EnhancedConfirmation( val key: String, val packageName: String, + val isRestrictedSettingAllowed: Boolean? ) data class Restrictions( val userId: Int = UserHandle.myUserId(), @@ -92,6 +93,9 @@ internal class RestrictionsProviderImpl( } restrictions.enhancedConfirmation?.let { ec -> + if (ec.isRestrictedSettingAllowed == true) { + return NoRestricted + } RestrictedLockUtilsInternal .checkIfRequiresEnhancedConfirmation(context, ec.key, ec.packageName) diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt index 7466f95e3fb8..5580d2e3211b 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt @@ -155,7 +155,7 @@ internal fun <T : AppRecord> TogglePermissionAppListModel<T>.TogglePermissionApp } RestrictedSwitchPreference( model = switchModel, - restrictions = getRestrictions(userId, packageName), + restrictions = getRestrictions(userId, packageName, isAllowed()), ifBlockedByAdminOverrideCheckedValueTo = switchifBlockedByAdminOverrideCheckedValueTo, restrictionsProviderFactory = restrictionsProviderFactory, ) diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt index d2867af1eda6..771eb85ee21a 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt @@ -116,12 +116,15 @@ fun <T : AppRecord> TogglePermissionAppListModel<T>.isChangeableWithSystemUidChe fun <T : AppRecord> TogglePermissionAppListModel<T>.getRestrictions( userId: Int, packageName: String, + isRestrictedSettingAllowed: Boolean? ) = Restrictions( userId = userId, keys = switchRestrictionKeys, enhancedConfirmation = - enhancedConfirmationKey?.let { key -> EnhancedConfirmation(key, packageName) }, + enhancedConfirmationKey?.let { + key -> EnhancedConfirmation(key, packageName, isRestrictedSettingAllowed) + }, ) interface TogglePermissionAppListProvider { diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt index ec44d2af4ffa..bef2bdaaefaf 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt @@ -149,13 +149,14 @@ internal class TogglePermissionInternalAppListModel<T : AppRecord>( @Composable fun getSummary(record: T): () -> String { + val allowed = listModel.isAllowed(record) val restrictions = listModel.getRestrictions( userId = record.app.userId, packageName = record.app.packageName, + allowed() ) val restrictedMode by restrictionsProviderFactory.rememberRestrictedMode(restrictions) - val allowed = listModel.isAllowed(record) return RestrictedSwitchPreferenceModel.getSummary( context = context, summaryIfNoRestricted = { getSummaryIfNoRestricted(allowed()) }, diff --git a/packages/SettingsLib/res/values-da/strings.xml b/packages/SettingsLib/res/values-da/strings.xml index aa85c1aadb12..43e9cfdc7d7f 100644 --- a/packages/SettingsLib/res/values-da/strings.xml +++ b/packages/SettingsLib/res/values-da/strings.xml @@ -683,9 +683,9 @@ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"Du kan gemme eller slette din aktivitet ved afslutning"</string> <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"Nulstil for at slette sessionsaktiviteten nu, eller gem eller slet aktivitet ved afslutning"</string> <string name="user_image_photo_selector" msgid="433658323306627093">"Vælg billede"</string> - <string name="failed_attempts_now_wiping_device" msgid="4016329172216428897">"For mange forkerte forsøg. Dataene på denne enhed slettes."</string> - <string name="failed_attempts_now_wiping_user" msgid="469060411789668050">"For mange forkerte forsøg. Denne bruger slettes."</string> - <string name="failed_attempts_now_wiping_profile" msgid="7626589520888963129">"For mange forkerte forsøg. Denne arbejdsprofil og de tilhørende data slettes."</string> + <string name="failed_attempts_now_wiping_device" msgid="4016329172216428897">"For mange mislykkede forsøg. Dataene på denne enhed slettes."</string> + <string name="failed_attempts_now_wiping_user" msgid="469060411789668050">"For mange mislykkede forsøg. Denne bruger slettes."</string> + <string name="failed_attempts_now_wiping_profile" msgid="7626589520888963129">"For mange mislykkede forsøg. Denne arbejdsprofil og de tilhørende data slettes."</string> <string name="failed_attempts_now_wiping_dialog_dismiss" msgid="2749889771223578925">"Luk"</string> <string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"Enhedens standardindstilling"</string> <string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Deaktiveret"</string> diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedDropDownPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedDropDownPreference.java index c36ade979d47..d3219c287f4b 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedDropDownPreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedDropDownPreference.java @@ -41,10 +41,23 @@ public class RestrictedDropDownPreference extends DropDownPreference implements * package. Marks the preference as disabled if so. * @param settingIdentifier The key identifying the setting * @param packageName the package to check the settingIdentifier for + * @param settingEnabled Whether the setting in question is enabled + */ + public void checkEcmRestrictionAndSetDisabled(@NonNull String settingIdentifier, + @NonNull String packageName, boolean settingEnabled) { + mHelper.checkEcmRestrictionAndSetDisabled(settingIdentifier, packageName, settingEnabled); + } + + /** + * Checks if the given setting is subject to Enhanced Confirmation Mode restrictions for this + * package. Marks the preference as disabled if so. + * TODO b/390196024: remove this and update all callers to use the "settingEnabled" version + * @param settingIdentifier The key identifying the setting + * @param packageName the package to check the settingIdentifier for */ public void checkEcmRestrictionAndSetDisabled(@NonNull String settingIdentifier, @NonNull String packageName) { - mHelper.checkEcmRestrictionAndSetDisabled(settingIdentifier, packageName); + mHelper.checkEcmRestrictionAndSetDisabled(settingIdentifier, packageName, false); } @Override diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java index 332042a5c4f9..ec1b2b3e2589 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java @@ -107,12 +107,25 @@ public class RestrictedPreference extends TwoTargetPreference implements /** * Checks if the given setting is subject to Enhanced Confirmation Mode restrictions for this * package. Marks the preference as disabled if so. + * TODO b/390196024: remove this and update all callers to use the "settingEnabled" version * @param settingIdentifier The key identifying the setting * @param packageName the package to check the settingIdentifier for */ public void checkEcmRestrictionAndSetDisabled(@NonNull String settingIdentifier, @NonNull String packageName) { - mHelper.checkEcmRestrictionAndSetDisabled(settingIdentifier, packageName); + mHelper.checkEcmRestrictionAndSetDisabled(settingIdentifier, packageName, false); + } + + /** + * Checks if the given setting is subject to Enhanced Confirmation Mode restrictions for this + * package. Marks the preference as disabled if so. + * @param settingIdentifier The key identifying the setting + * @param packageName the package to check the settingIdentifier for + * @param settingEnabled Whether the setting in question is enabled + */ + public void checkEcmRestrictionAndSetDisabled(@NonNull String settingIdentifier, + @NonNull String packageName, boolean settingEnabled) { + mHelper.checkEcmRestrictionAndSetDisabled(settingIdentifier, packageName, settingEnabled); } @Override diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java index 25628fba1b66..212e43aa4044 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java @@ -204,16 +204,33 @@ public class RestrictedPreferenceHelper { * package. Marks the preference as disabled if so. * @param settingIdentifier The key identifying the setting * @param packageName the package to check the settingIdentifier for + * @param settingEnabled Whether the setting in question is enabled */ public void checkEcmRestrictionAndSetDisabled(@NonNull String settingIdentifier, - @NonNull String packageName) { + @NonNull String packageName, boolean settingEnabled) { updatePackageDetails(packageName, android.os.Process.INVALID_UID); + if (settingEnabled) { + setDisabledByEcm(null); + return; + } Intent intent = RestrictedLockUtilsInternal.checkIfRequiresEnhancedConfirmation( mContext, settingIdentifier, packageName); setDisabledByEcm(intent); } /** + * Checks if the given setting is subject to Enhanced Confirmation Mode restrictions for this + * package. Marks the preference as disabled if so. + * TODO b/390196024: remove this and update all callers to use the "settingEnabled" version + * @param settingIdentifier The key identifying the setting + * @param packageName the package to check the settingIdentifier for + */ + public void checkEcmRestrictionAndSetDisabled(@NonNull String settingIdentifier, + @NonNull String packageName) { + checkEcmRestrictionAndSetDisabled(settingIdentifier, packageName, false); + } + + /** * @return EnforcedAdmin if we have been passed the restriction in the xml. */ public EnforcedAdmin checkRestrictionEnforced() { diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedSelectorWithWidgetPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedSelectorWithWidgetPreference.java index 573869db5073..f8f16a9dd63b 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedSelectorWithWidgetPreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedSelectorWithWidgetPreference.java @@ -134,10 +134,11 @@ public class RestrictedSelectorWithWidgetPreference extends SelectorWithWidgetPr * * @param settingIdentifier The key identifying the setting * @param packageName the package to check the settingIdentifier for + * @param settingEnabled Whether the setting in question is enabled */ - public void checkEcmRestrictionAndSetDisabled( - @NonNull String settingIdentifier, @NonNull String packageName) { - mHelper.checkEcmRestrictionAndSetDisabled(settingIdentifier, packageName); + public void checkEcmRestrictionAndSetDisabled(@NonNull String settingIdentifier, + @NonNull String packageName, boolean settingEnabled) { + mHelper.checkEcmRestrictionAndSetDisabled(settingIdentifier, packageName, settingEnabled); } @Override diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java index 0aac9a1104e9..a5fa6a854e97 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java @@ -220,10 +220,23 @@ public class RestrictedSwitchPreference extends SwitchPreferenceCompat implement * package. Marks the preference as disabled if so. * @param settingIdentifier The key identifying the setting * @param packageName the package to check the settingIdentifier for + * @param settingEnabled Whether the setting in question is enabled */ public void checkEcmRestrictionAndSetDisabled(@NonNull String settingIdentifier, - @NonNull String packageName) { - mHelper.checkEcmRestrictionAndSetDisabled(settingIdentifier, packageName); + @NonNull String packageName, boolean settingEnabled) { + mHelper.checkEcmRestrictionAndSetDisabled(settingIdentifier, packageName, settingEnabled); + } + + /** + * Checks if the given setting is subject to Enhanced Confirmation Mode restrictions for this + * package. Marks the preference as disabled if so. + * TODO b/390196024: remove this and update all callers to use the "settingEnabled" version + * @param settingIdentifier The key identifying the setting + * @param packageName the package to check the settingIdentifier for + */ + public void checkEcmRestrictionAndSetDisabled(@NonNull String settingIdentifier, + @NonNull String packageName) { + mHelper.checkEcmRestrictionAndSetDisabled(settingIdentifier, packageName, false); } @Override diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java index 68e9fe703090..a00484ac28ab 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java @@ -1169,4 +1169,38 @@ public class BluetoothUtils { String metadataValue = getFastPairCustomizedField(bluetoothDevice, TEMP_BOND_TYPE); return Objects.equals(metadataValue, TEMP_BOND_DEVICE_METADATA_VALUE); } + + /** + * Set temp bond metadata to device + * + * @param device the BluetoothDevice to be marked as temp bond + * + * Note: It is a workaround since Bluetooth API is not ready. + * Avoid using this method if possible + */ + public static void setTemporaryBondMetadata(@Nullable BluetoothDevice device) { + if (device == null) return; + if (!Flags.enableTemporaryBondDevicesUi()) { + Log.d(TAG, "Skip setTemporaryBondMetadata, flag is disabled"); + return; + } + String fastPairCustomizedMeta = getStringMetaData(device, + METADATA_FAST_PAIR_CUSTOMIZED_FIELDS); + String fullContentWithTag = generateExpressionWithTag(TEMP_BOND_TYPE, + TEMP_BOND_DEVICE_METADATA_VALUE); + if (TextUtils.isEmpty(fastPairCustomizedMeta)) { + fastPairCustomizedMeta = fullContentWithTag; + } else { + String oldValue = extraTagValue(TEMP_BOND_TYPE, fastPairCustomizedMeta); + if (TextUtils.isEmpty(oldValue)) { + fastPairCustomizedMeta += fullContentWithTag; + } else { + fastPairCustomizedMeta = + fastPairCustomizedMeta.replace( + generateExpressionWithTag(TEMP_BOND_TYPE, oldValue), + fullContentWithTag); + } + } + device.setMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS, fastPairCustomizedMeta.getBytes()); + } } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java index 7d5eece6c30e..84156429809b 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java @@ -1087,8 +1087,9 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { private String generateRandomPassword() { String randomUUID = UUID.randomUUID().toString(); - // first 12 chars from xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx - return randomUUID.substring(0, 8) + randomUUID.substring(9, 13); + // first 16 chars from xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx + return randomUUID.substring(0, 8) + randomUUID.substring(9, 13) + randomUUID.substring(14, + 18); } private void registerContentObserver() { diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java index cafe19ff9a9b..7c46db96595f 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java @@ -22,9 +22,11 @@ import static com.android.settingslib.flags.Flags.FLAG_ENABLE_DETERMINING_ADVANC import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -44,6 +46,8 @@ import android.media.AudioDeviceAttributes; import android.media.AudioDeviceInfo; import android.media.AudioManager; import android.net.Uri; +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.util.Pair; @@ -109,6 +113,7 @@ public class BluetoothUtilsTest { + "</HEARABLE_CONTROL_SLICE_WITH_WIDTH>"; private static final String TEMP_BOND_METADATA = "<TEMP_BOND_TYPE>" + LE_AUDIO_SHARING_METADATA + "</TEMP_BOND_TYPE>"; + private static final String FAKE_TEMP_BOND_METADATA = "<TEMP_BOND_TYPE>fake</TEMP_BOND_TYPE>"; private static final String TEST_EXCLUSIVE_MANAGER_PACKAGE = "com.test.manager"; private static final String TEST_EXCLUSIVE_MANAGER_COMPONENT = "com.test.manager/.component"; private static final int TEST_BROADCAST_ID = 25; @@ -1348,4 +1353,34 @@ public class BluetoothUtilsTest { assertThat(BluetoothUtils.isTemporaryBondDevice(mBluetoothDevice)).isTrue(); } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_TEMPORARY_BOND_DEVICES_UI) + public void setTemporaryBondDevice_flagOff_doNothing() { + when(mBluetoothDevice.getMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS)) + .thenReturn(new byte[]{}); + BluetoothUtils.setTemporaryBondMetadata(mBluetoothDevice); + verify(mBluetoothDevice, never()).setMetadata(eq(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS), + any()); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_TEMPORARY_BOND_DEVICES_UI) + public void setTemporaryBondDevice_flagOn_setCorrectValue() { + when(mBluetoothDevice.getMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS)) + .thenReturn(new byte[]{}); + BluetoothUtils.setTemporaryBondMetadata(mBluetoothDevice); + verify(mBluetoothDevice).setMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS, + TEMP_BOND_METADATA.getBytes()); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_TEMPORARY_BOND_DEVICES_UI) + public void setTemporaryBondDevice_flagOff_replaceAndSetCorrectValue() { + when(mBluetoothDevice.getMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS)) + .thenReturn(FAKE_TEMP_BOND_METADATA.getBytes()); + BluetoothUtils.setTemporaryBondMetadata(mBluetoothDevice); + verify(mBluetoothDevice).setMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS, + TEMP_BOND_METADATA.getBytes()); + } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/satellite/SatelliteDialogUtilsTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/satellite/SatelliteDialogUtilsTest.kt index 2078b363f434..1deb62510daf 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/satellite/SatelliteDialogUtilsTest.kt +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/satellite/SatelliteDialogUtilsTest.kt @@ -67,7 +67,7 @@ class SatelliteDialogUtilsTest { @Test @RequiresFlagsEnabled(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) - fun mayStartSatelliteWarningDialog_satelliteIsOn_showWarningDialog() = runBlocking { + fun mayStartSatelliteWarningDialog_satelliteIsOn_showWarningDialog(): Unit = runBlocking { `when`(satelliteManager.registerForModemStateChanged(any(), any())) .thenAnswer { invocation -> val callback = invocation diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/MainSwitchPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/MainSwitchPreferenceTest.java index c2e81bd7d54c..a47b4d5c354f 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/MainSwitchPreferenceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/MainSwitchPreferenceTest.java @@ -23,6 +23,7 @@ import android.view.View; import android.widget.TextView; import androidx.preference.PreferenceViewHolder; +import androidx.test.core.app.ApplicationProvider; import com.android.settingslib.widget.mainswitch.R; @@ -30,19 +31,17 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; @RunWith(RobolectricTestRunner.class) public class MainSwitchPreferenceTest { - private Context mContext; + private final Context mContext = ApplicationProvider.getApplicationContext(); private View mRootView; private PreferenceViewHolder mHolder; private MainSwitchPreference mPreference; @Before public void setUp() { - mContext = RuntimeEnvironment.application; mRootView = View.inflate(mContext, R.layout.settingslib_main_switch_layout, null /* parent */); mHolder = PreferenceViewHolder.createInstanceForTests(mRootView); @@ -50,23 +49,22 @@ public class MainSwitchPreferenceTest { } @Test - public void setTitle_shouldUpdateTitle() { + public void onBindViewHolder_title() { final String defaultOnText = "Test title"; - mPreference.onBindViewHolder(mHolder); mPreference.setTitle(defaultOnText); - mPreference.updateStatus(true /* checked */); + mPreference.onBindViewHolder(mHolder); - assertThat(((TextView) mRootView.findViewById(R.id.switch_text)).getText()) - .isEqualTo(defaultOnText); + assertThat(mRootView.<TextView>requireViewById( + R.id.switch_text).getText().toString()).isEqualTo(defaultOnText); } @Test - public void updateStatus_shouldMatchTheStatus() { + public void onBindViewHolder_checked() { + mPreference.setChecked(true); mPreference.onBindViewHolder(mHolder); - mPreference.updateStatus(true); - assertThat(mPreference.isChecked()).isTrue(); + assertThat(mRootView.<MainSwitchBar>requireViewById( + R.id.settingslib_main_switch_bar).isChecked()).isTrue(); } - } diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java index d936a5c699c7..27a3cf1198b2 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java @@ -291,5 +291,6 @@ public class SecureSettings { Settings.Secure.FACE_KEYGUARD_ENABLED, Settings.Secure.FINGERPRINT_APP_ENABLED, Settings.Secure.FINGERPRINT_KEYGUARD_ENABLED, + Settings.Secure.DUAL_SHADE, }; } diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java index 919c3c4721f2..8dca39fdc107 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java @@ -460,5 +460,6 @@ public class SecureSettingsValidators { VALIDATORS.put(Secure.FACE_KEYGUARD_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.FINGERPRINT_APP_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.FINGERPRINT_KEYGUARD_ENABLED, BOOLEAN_VALIDATOR); + VALIDATORS.put(Secure.DUAL_SHADE, BOOLEAN_VALIDATOR); } } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java index c1c3e04d46fd..fc402d45c3ec 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java @@ -89,6 +89,7 @@ import java.io.OutputStream; import java.time.DateTimeException; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Objects; @@ -97,7 +98,6 @@ 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; /** @@ -214,6 +214,17 @@ public class SettingsBackupAgent extends BackupAgentHelper { "failed_to_restore_softap_config"; private static final String ERROR_FAILED_TO_RESTORE_WIFI_CONFIG = "failed_to_restore_wifi_config"; + private static final String ERROR_FAILED_TO_RESTORE_SIM_SPECIFIC_SETTINGS = + "failed_to_restore_sim_specific_settings"; + private static final String ERROR_FAILED_TO_CONVERT_NETWORK_POLICIES = + "failed_to_convert_network_policies"; + private static final String ERROR_UNKNOWN_BACKUP_SERIALIZATION_VERSION = + "unknown_backup_serialization_version"; + private static final String INTERRUPTED_EXCEPTION = "interrupted_exception"; + private static final String ERROR_FAILED_TO_RETRIEVE_WIFI_SETTINGS_BACKUP_DATA = + "failed_to_retrieve_wifi_settings_backup_data"; + private static final String ERROR_FAILED_TO_RESTORE_WIFI_SETTINGS_BACKUP_DATA = + "failed_to_restore_wifi_settings_backup_data"; // Name of the temporary file we use during full backup/restore. This is @@ -1436,6 +1447,7 @@ public class SettingsBackupAgent extends BackupAgentHelper { try { out.writeInt(NETWORK_POLICIES_BACKUP_VERSION); out.writeInt(policies.length); + int numberOfPoliciesBackedUp = 0; for (NetworkPolicy policy : policies) { // We purposefully only backup policies that the user has // defined; any inferred policies might include @@ -1445,13 +1457,25 @@ public class SettingsBackupAgent extends BackupAgentHelper { out.writeByte(BackupUtils.NOT_NULL); out.writeInt(marshaledPolicy.length); out.write(marshaledPolicy); + if (areAgentMetricsEnabled) { + numberOfPoliciesBackedUp++; + } } else { out.writeByte(BackupUtils.NULL); } } + if (areAgentMetricsEnabled) { + numberOfSettingsPerKey.put(KEY_NETWORK_POLICIES, numberOfPoliciesBackedUp); + } } catch (IOException ioe) { Log.e(TAG, "Failed to convert NetworkPolicies to byte array " + ioe.getMessage()); baos.reset(); + if (areAgentMetricsEnabled) { + mBackupRestoreEventLogger.logItemsBackupFailed( + KEY_NETWORK_POLICIES, + policies.length, + ERROR_FAILED_TO_CONVERT_NETWORK_POLICIES); + } } } return baos.toByteArray(); @@ -1502,6 +1526,12 @@ public class SettingsBackupAgent extends BackupAgentHelper { try { int version = in.readInt(); if (version < 1 || version > NETWORK_POLICIES_BACKUP_VERSION) { + if (areAgentMetricsEnabled) { + mBackupRestoreEventLogger.logItemsRestoreFailed( + KEY_NETWORK_POLICIES, + /* count= */ 1, + ERROR_UNKNOWN_BACKUP_SERIALIZATION_VERSION); + } throw new BackupUtils.BadVersionException( "Unknown Backup Serialization Version"); } @@ -1518,10 +1548,20 @@ public class SettingsBackupAgent extends BackupAgentHelper { } // Only set the policies if there was no error in the restore operation networkPolicyManager.setNetworkPolicies(policies); + if (areAgentMetricsEnabled) { + mBackupRestoreEventLogger + .logItemsRestored(KEY_NETWORK_POLICIES, policies.length); + } } catch (NullPointerException | IOException | BackupUtils.BadVersionException | DateTimeException e) { // NPE can be thrown when trying to instantiate a NetworkPolicy Log.e(TAG, "Failed to convert byte array to NetworkPolicies " + e.getMessage()); + if (areAgentMetricsEnabled) { + mBackupRestoreEventLogger.logItemsRestoreFailed( + KEY_NETWORK_POLICIES, + /* count= */ 1, + ERROR_FAILED_TO_CONVERT_NETWORK_POLICIES); + } } } } @@ -1592,7 +1632,8 @@ public class SettingsBackupAgent extends BackupAgentHelper { return true; } - private byte[] getSimSpecificSettingsData() { + @VisibleForTesting + byte[] getSimSpecificSettingsData() { byte[] simSpecificData = new byte[0]; PackageManager packageManager = getBaseContext().getPackageManager(); if (packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) { @@ -1600,17 +1641,36 @@ public class SettingsBackupAgent extends BackupAgentHelper { simSpecificData = subManager.getAllSimSpecificSettingsForBackup(); Log.i(TAG, "sim specific data of length + " + simSpecificData.length + " successfully retrieved"); + if (areAgentMetricsEnabled) { + // We're unable to determine how many settings this includes, so we'll just log 1. + numberOfSettingsPerKey.put(KEY_SIM_SPECIFIC_SETTINGS_2, 1); + } } return simSpecificData; } - private void restoreSimSpecificSettings(byte[] data) { + @VisibleForTesting + void restoreSimSpecificSettings(byte[] data) { PackageManager packageManager = getBaseContext().getPackageManager(); boolean hasTelephony = packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY); if (hasTelephony) { SubscriptionManager subManager = SubscriptionManager.from(getBaseContext()); - subManager.restoreAllSimSpecificSettingsFromBackup(data); + if (areAgentMetricsEnabled) { + try { + subManager.restoreAllSimSpecificSettingsFromBackup(data); + mBackupRestoreEventLogger + .logItemsRestored(KEY_SIM_SPECIFIC_SETTINGS_2, /* count= */ 1); + } catch (Exception e) { + mBackupRestoreEventLogger + .logItemsRestoreFailed( + KEY_SIM_SPECIFIC_SETTINGS_2, + /* count= */ 1, + ERROR_FAILED_TO_RESTORE_SIM_SPECIFIC_SETTINGS); + } + } else { + subManager.restoreAllSimSpecificSettingsFromBackup(data); + } } } @@ -1637,20 +1697,49 @@ public class SettingsBackupAgent extends BackupAgentHelper { }); // cts requires B&R with 10 seconds if (latch.await(10, TimeUnit.SECONDS) && backupWifiData.value != null) { + if (areAgentMetricsEnabled) { + numberOfSettingsPerKey.put(KEY_WIFI_SETTINGS_BACKUP_DATA, 1); + } return backupWifiData.value; } } catch (InterruptedException ie) { Log.e(TAG, "fail to retrieveWifiBackupData, " + ie); + if (areAgentMetricsEnabled) { + mBackupRestoreEventLogger.logItemsBackupFailed( + KEY_WIFI_SETTINGS_BACKUP_DATA, + /* count= */ 1, + INTERRUPTED_EXCEPTION); + } } Log.e(TAG, "fail to retrieveWifiBackupData"); + if (areAgentMetricsEnabled) { + mBackupRestoreEventLogger.logItemsBackupFailed( + KEY_WIFI_SETTINGS_BACKUP_DATA, + /* count= */ 1, + ERROR_FAILED_TO_RETRIEVE_WIFI_SETTINGS_BACKUP_DATA); + } return new byte[0]; } - private void restoreWifiData(byte[] data) { + @VisibleForTesting + void restoreWifiData(byte[] data) { if (DEBUG_BACKUP) { Log.v(TAG, "Applying restored all wifi data"); } - mWifiManager.restoreWifiBackupData(data); + if (areAgentMetricsEnabled) { + try { + mWifiManager.restoreWifiBackupData(data); + mBackupRestoreEventLogger.logItemsRestored( + KEY_WIFI_SETTINGS_BACKUP_DATA, /* count= */ 1); + } catch (Exception e) { + mBackupRestoreEventLogger.logItemsRestoreFailed( + KEY_WIFI_SETTINGS_BACKUP_DATA, + /* count= */ 1, + ERROR_FAILED_TO_RESTORE_WIFI_SETTINGS_BACKUP_DATA); + } + } else { + mWifiManager.restoreWifiBackupData(data); + } } private void updateWindowManagerIfNeeded(Integer previousDensity) { @@ -1664,8 +1753,8 @@ public class SettingsBackupAgent extends BackupAgentHelper { if (previousDensity == null || previousDensity != newDensity) { // From nothing to something is a change. - DisplayDensityConfiguration.setForcedDisplayDensity( - Display.DEFAULT_DISPLAY, newDensity); + DisplayDensityConfiguration.setForcedDisplayDensity(getBaseContext(), + info -> info.type == Display.TYPE_INTERNAL, newDensity); } } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java index 0f6311552de9..c98a741f8254 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java @@ -18,10 +18,13 @@ package com.android.providers.settings; import static android.os.Process.FIRST_APPLICATION_UID; +import static com.android.aconfig_new_storage.Flags.enableAconfigStorageDaemon; + import android.aconfig.Aconfig.flag_permission; import android.aconfig.Aconfig.flag_state; import android.aconfig.Aconfig.parsed_flag; import android.aconfig.Aconfig.parsed_flags; +import android.aconfigd.AconfigdFlagInfo; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; @@ -65,14 +68,10 @@ import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; -import java.io.InputStream; import java.io.IOException; import java.io.PrintWriter; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.attribute.PosixFileAttributes; -import java.nio.file.attribute.PosixFilePermission; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -84,18 +83,6 @@ import java.util.Objects; import java.util.Set; import java.util.concurrent.CountDownLatch; -// FOR ACONFIGD TEST MISSION AND ROLLOUT -import java.io.DataInputStream; -import java.io.DataOutputStream; -import android.util.proto.ProtoInputStream; -import android.aconfigd.Aconfigd.StorageRequestMessage; -import android.aconfigd.Aconfigd.StorageRequestMessages; -import android.aconfigd.Aconfigd.StorageReturnMessage; -import android.aconfigd.Aconfigd.StorageReturnMessages; -import android.aconfigd.AconfigdClientSocket; -import android.aconfigd.AconfigdFlagInfo; -import android.aconfigd.AconfigdJavaUtils; -import static com.android.aconfig_new_storage.Flags.enableAconfigStorageDaemon; /** * This class contains the state for one type of settings. It is responsible * for saving the state asynchronously to an XML file after a mutation and @@ -393,22 +380,6 @@ public class SettingsState { getAllAconfigFlagsFromSettings(mAconfigDefaultFlags); } } - - if (isConfigSettingsKey(mKey)) { - requests = handleBulkSyncToNewStorage(mAconfigDefaultFlags); - } - } - - if (enableAconfigStorageDaemon()) { - if (isConfigSettingsKey(mKey)){ - AconfigdClientSocket localSocket = AconfigdJavaUtils.getAconfigdClientSocket(); - if (requests != null) { - InputStream res = localSocket.send(requests.getBytes()); - if (res == null) { - Slog.w(LOG_TAG, "Bulk sync request to acongid failed."); - } - } - } } } @@ -482,87 +453,6 @@ public class SettingsState { return flag; } - - // TODO(b/341764371): migrate aconfig flag push to GMS core - @VisibleForTesting - @GuardedBy("mLock") - public ProtoOutputStream handleBulkSyncToNewStorage( - Map<String, AconfigdFlagInfo> aconfigFlagMap) { - // get marker or add marker if it does not exist - Setting markerSetting = mSettings.get(BULK_SYNC_MARKER); - int localCounter = 0; - if (markerSetting == null) { - markerSetting = new Setting(BULK_SYNC_MARKER, "0", false, "aconfig", "aconfig"); - mSettings.put(BULK_SYNC_MARKER, markerSetting); - } - try { - localCounter = Integer.parseInt(markerSetting.value); - } catch (NumberFormatException e) { - // reset local counter - markerSetting.value = "0"; - } - - if (enableAconfigStorageDaemon()) { - Setting bulkSyncCounter = mSettings.get(BULK_SYNC_TRIGGER_COUNTER); - int serverCounter = 0; - if (bulkSyncCounter != null) { - try { - serverCounter = Integer.parseInt(bulkSyncCounter.value); - } catch (NumberFormatException e) { - // reset the local value of server counter - bulkSyncCounter.value = "0"; - } - } - - boolean shouldSync = localCounter < serverCounter; - if (!shouldSync) { - // CASE 1, flag is on, bulk sync marker true, nothing to do - return null; - } else { - // CASE 2, flag is on, bulk sync marker false. Do following two tasks - // (1) Do bulk sync here. - // (2) After bulk sync, set marker to true. - - // first add storage reset request - ProtoOutputStream requests = new ProtoOutputStream(); - AconfigdJavaUtils.writeResetStorageRequest(requests); - - // loop over all settings and add flag override requests - for (AconfigdFlagInfo flag : aconfigFlagMap.values()) { - // don't sync read_only flags - if (!flag.getIsReadWrite()) { - continue; - } - - if (flag.getHasServerOverride()) { - AconfigdJavaUtils.writeFlagOverrideRequest( - requests, - flag.getPackageName(), - flag.getFlagName(), - flag.getServerFlagValue(), - StorageRequestMessage.SERVER_ON_REBOOT); - } - - if (flag.getHasLocalOverride()) { - AconfigdJavaUtils.writeFlagOverrideRequest( - requests, - flag.getPackageName(), - flag.getFlagName(), - flag.getLocalFlagValue(), - StorageRequestMessage.LOCAL_ON_REBOOT); - } - } - - // mark sync has been done - markerSetting.value = String.valueOf(serverCounter); - scheduleWriteIfNeededLocked(); - return requests; - } - } else { - return null; - } - } - @GuardedBy("mLock") private void loadAconfigDefaultValuesLocked(List<String> filePaths) { for (String fileName : filePaths) { diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index 9aad5d5f8367..246aa7158cab 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -178,6 +178,7 @@ public class SettingsBackupTest { Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT, Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES, + Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_EXPERIENCE_FEATURES, Settings.Global.DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES, Settings.Global.DEVELOPMENT_FORCE_RTL, Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW, 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 6e5b602c02c5..48c360b635ea 100644 --- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java +++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java @@ -18,6 +18,8 @@ package com.android.providers.settings; import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_WIFI_NEW_CONFIG; import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_SOFTAP_CONFIG; +import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_SIM_SPECIFIC_SETTINGS_2; +import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_WIFI_SETTINGS_BACKUP_DATA; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNotNull; @@ -59,6 +61,7 @@ import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; import android.provider.settings.validators.SettingsValidators; import android.provider.settings.validators.Validator; +import android.telephony.SubscriptionManager; import android.test.mock.MockContentProvider; import android.test.mock.MockContentResolver; @@ -136,6 +139,7 @@ public class SettingsBackupAgentTest extends BaseSettingsProviderTest { @Mock private BackupDataInput mBackupDataInput; @Mock private BackupDataOutput mBackupDataOutput; @Mock private static WifiManager mWifiManager; + @Mock private static SubscriptionManager mSubscriptionManager; private TestFriendlySettingsBackupAgent mAgentUnderTest; private Context mContext; @@ -906,6 +910,110 @@ public class SettingsBackupAgentTest extends BaseSettingsProviderTest { assertNull(getLoggingResultForDatatype(KEY_WIFI_NEW_CONFIG, mAgentUnderTest)); } + @Test + @EnableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS) + public void + getSimSpecificSettingsData_agentMetricsAreEnabled_numberOfSettingsInKeyAreRecorded() { + mAgentUnderTest.onCreate( + UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.BACKUP); + when(mSubscriptionManager.getAllSimSpecificSettingsForBackup()).thenReturn(new byte[0]); + + mAgentUnderTest.getSimSpecificSettingsData(); + + assertEquals(mAgentUnderTest.getNumberOfSettingsPerKey(KEY_SIM_SPECIFIC_SETTINGS_2), 1); + } + + @Test + @EnableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS) + public void + restoreSimSpecificSettings_agentMetricsAreEnabled_restoreIsSuccessful_successMetricsAreLogged() { + mAgentUnderTest.onCreate( + UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.RESTORE); + doNothing().when(mSubscriptionManager).restoreAllSimSpecificSettingsFromBackup(any()); + + mAgentUnderTest.restoreSimSpecificSettings(new byte[0]); + + DataTypeResult loggingResult = + getLoggingResultForDatatype(KEY_SIM_SPECIFIC_SETTINGS_2, mAgentUnderTest); + assertNotNull(loggingResult); + assertEquals(loggingResult.getSuccessCount(), 1); + } + + @Test + @EnableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS) + public void + restoreSimSpecificSettings_agentMetricsAreEnabled_restoreIsNotSuccessful_failureMetricsAreLogged() { + mAgentUnderTest.onCreate( + UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.RESTORE); + doThrow(new RuntimeException()) + .when(mSubscriptionManager) + .restoreAllSimSpecificSettingsFromBackup(any()); + + mAgentUnderTest.restoreSimSpecificSettings(new byte[0]); + + DataTypeResult loggingResult = + getLoggingResultForDatatype(KEY_SIM_SPECIFIC_SETTINGS_2, mAgentUnderTest); + assertNotNull(loggingResult); + assertEquals(loggingResult.getFailCount(), 1); + } + + @Test + @DisableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS) + public void + restoreSimSpecificSettings_agentMetricsAreNotEnabled_metricsAreNotLogged() { + mAgentUnderTest.onCreate( + UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.RESTORE); + doNothing().when(mSubscriptionManager).restoreAllSimSpecificSettingsFromBackup(any()); + + mAgentUnderTest.restoreSimSpecificSettings(new byte[0]); + + assertNull(getLoggingResultForDatatype(KEY_SIM_SPECIFIC_SETTINGS_2, mAgentUnderTest)); + } + + @Test + @EnableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS) + public void + restoreWifiData_agentMetricsAreEnabled_restoreIsSuccessful_successMetricsAreLogged() { + mAgentUnderTest.onCreate( + UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.RESTORE); + doNothing().when(mWifiManager).restoreWifiBackupData(any()); + + mAgentUnderTest.restoreWifiData(new byte[0]); + + DataTypeResult loggingResult = + getLoggingResultForDatatype(KEY_WIFI_SETTINGS_BACKUP_DATA, mAgentUnderTest); + assertNotNull(loggingResult); + assertEquals(loggingResult.getSuccessCount(), 1); + } + + @Test + @EnableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS) + public void + restoreWifiData_agentMetricsAreEnabled_restoreIsNotSuccessful_failureMetricsAreLogged() { + mAgentUnderTest.onCreate( + UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.RESTORE); + doThrow(new RuntimeException()).when(mWifiManager).restoreWifiBackupData(any()); + + mAgentUnderTest.restoreWifiData(new byte[0]); + + DataTypeResult loggingResult = + getLoggingResultForDatatype(KEY_WIFI_SETTINGS_BACKUP_DATA, mAgentUnderTest); + assertNotNull(loggingResult); + assertEquals(loggingResult.getFailCount(), 1); + } + + @Test + @DisableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS) + public void restoreWifiData_agentMetricsAreDisabled_metricsAreNotLogged() { + mAgentUnderTest.onCreate( + UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.RESTORE); + doNothing().when(mWifiManager).restoreWifiBackupData(any()); + + mAgentUnderTest.restoreWifiData(new byte[0]); + + assertNull(getLoggingResultForDatatype(KEY_WIFI_SETTINGS_BACKUP_DATA, mAgentUnderTest)); + } + private byte[] generateBackupData(Map<String, String> keyValueData) { int totalBytes = 0; for (String key : keyValueData.keySet()) { @@ -1106,10 +1214,14 @@ public class SettingsBackupAgentTest extends BaseSettingsProviderTest { @Override public Object getSystemService(String name) { - if (name.equals(Context.WIFI_SERVICE)) { - return mWifiManager; + switch (name) { + case Context.WIFI_SERVICE: + return mWifiManager; + case Context.TELEPHONY_SUBSCRIPTION_SERVICE: + return mSubscriptionManager; + default: + return super.getSystemService(name); } - return super.getSystemService(name); } } 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 6e7576631147..13eb1d4b2603 100644 --- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java +++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java @@ -31,19 +31,20 @@ import android.platform.test.annotations.RequiresFlagsDisabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.util.Xml; -import android.util.proto.ProtoOutputStream; import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; import com.android.modules.utils.TypedXmlSerializer; -import android.platform.test.annotations.EnableFlags; -import android.platform.test.annotations.DisableFlags; -import android.platform.test.flag.junit.SetFlagsRule; - import com.google.common.base.Strings; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; @@ -52,11 +53,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) public class SettingsStateTest { @@ -1085,124 +1081,6 @@ public class SettingsStateTest { assertTrue(flag1.getHasLocalOverride()); } - @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); - - @Test - @EnableFlags(com.android.aconfig_new_storage.Flags.FLAG_ENABLE_ACONFIG_STORAGE_DAEMON) - public void testHandleBulkSyncWithAconfigdEnabled() { - int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0); - Object lock = new Object(); - SettingsState settingsState = - new SettingsState( - InstrumentationRegistry.getContext(), - lock, - mSettingsFile, - configKey, - SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, - Looper.getMainLooper()); - - Map<String, AconfigdFlagInfo> flags = new HashMap<>(); - flags.put( - "com.android.flags/flag1", - AconfigdFlagInfo.newBuilder() - .setPackageName("com.android.flags") - .setFlagName("flag1") - .setBootFlagValue("true") - .setIsReadWrite(true) - .build()); - - flags.put( - "com.android.flags/flag2", - AconfigdFlagInfo.newBuilder() - .setPackageName("com.android.flags") - .setFlagName("flag2") - .setBootFlagValue("true") - .setIsReadWrite(false) - .build()); - - String bulkSyncMarker = "aconfigd_marker/bulk_synced"; - String bulkSyncCounter = - "core_experiments_team_internal/" + - "BulkSyncTriggerCounterFlag__bulk_sync_trigger_counter"; - - synchronized (lock) { - settingsState.insertSettingLocked(bulkSyncMarker, "0", null, false, "aconfig"); - settingsState.insertSettingLocked(bulkSyncCounter, "1", null, false, - "com.google.android.platform.core_experiments_team_internal"); - - // first bulk sync - ProtoOutputStream requests = settingsState.handleBulkSyncToNewStorage(flags); - assertTrue(requests != null); - String value = settingsState.getSettingLocked("aconfigd_marker/bulk_synced").getValue(); - assertEquals("1", value); - - // send time should no longer bulk sync - requests = settingsState.handleBulkSyncToNewStorage(flags); - assertNull(requests); - value = settingsState.getSettingLocked("aconfigd_marker/bulk_synced").getValue(); - assertEquals("1", value); - - // won't sync if the marker is string - settingsState.insertSettingLocked(bulkSyncMarker, "true", null, false, "aconfig"); - settingsState.insertSettingLocked(bulkSyncCounter, "0", null, false, - "com.google.android.platform.core_experiments_team_internal"); - requests = settingsState.handleBulkSyncToNewStorage(flags); - assertNull(requests); - value = settingsState.getSettingLocked("aconfigd_marker/bulk_synced").getValue(); - assertEquals("0", value); - - // won't sync if the marker and counter value are the same - settingsState.insertSettingLocked(bulkSyncMarker, "1", null, false, "aconfig"); - settingsState.insertSettingLocked(bulkSyncCounter, "1", null, false, - "com.google.android.platform.core_experiments_team_internal"); - requests = settingsState.handleBulkSyncToNewStorage(flags); - assertNull(requests); - value = settingsState.getSettingLocked("aconfigd_marker/bulk_synced").getValue(); - assertEquals("1", value); - } - } - - @Test - @DisableFlags(com.android.aconfig_new_storage.Flags.FLAG_ENABLE_ACONFIG_STORAGE_DAEMON) - public void testHandleBulkSyncWithAconfigdDisabled() { - int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0); - Object lock = new Object(); - SettingsState settingsState = new SettingsState( - InstrumentationRegistry.getContext(), lock, mSettingsFile, configKey, - SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper()); - - Map<String, AconfigdFlagInfo> flags = new HashMap<>(); - String bulkSyncMarker = "aconfigd_marker/bulk_synced"; - String bulkSyncCounter = - "core_experiments_team_internal/" + - "BulkSyncTriggerCounterFlag__bulk_sync_trigger_counter"; - synchronized (lock) { - settingsState.insertSettingLocked("aconfigd_marker/bulk_synced", - "true", null, false, "aconfig"); - - // when aconfigd is off, should change the marker to false - ProtoOutputStream requests = settingsState.handleBulkSyncToNewStorage(flags); - assertNull(requests); - String value = settingsState.getSettingLocked("aconfigd_marker/bulk_synced").getValue(); - assertEquals("0", value); - - // marker started with false value, after call, it should remain false - requests = settingsState.handleBulkSyncToNewStorage(flags); - assertNull(requests); - value = settingsState.getSettingLocked("aconfigd_marker/bulk_synced").getValue(); - assertEquals("0", value); - - // won't sync - settingsState.insertSettingLocked(bulkSyncMarker, "0", null, false, "aconfig"); - settingsState.insertSettingLocked(bulkSyncCounter, "1", null, false, - "com.google.android.platform.core_experiments_team_internal"); - requests = settingsState.handleBulkSyncToNewStorage(flags); - assertNull(requests); - value = settingsState.getSettingLocked("aconfigd_marker/bulk_synced").getValue(); - assertEquals("0", value); - } - } - @Test public void testGetAllAconfigFlagsFromSettings() throws Exception { final Object lock = new Object(); diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index b53198d8ae98..6491bf5c8f5b 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -392,6 +392,9 @@ <!-- To be able to decipher default applications for certain roles in shortcut helper --> <uses-permission android:name="android.permission.MANAGE_DEFAULT_APPLICATIONS" /> + <!-- To be able to set unrestricted system gesture exclusion rects --> + <uses-permission android:name="android.permission.SET_UNRESTRICTED_GESTURE_EXCLUSION"/> + <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" /> @@ -548,10 +551,13 @@ android:exported="true" /> <service android:name=".wallpapers.GradientColorWallpaper" - android:featureFlag="android.app.enable_connected_displays_wallpaper" android:singleUser="true" android:permission="android.permission.BIND_WALLPAPER" - android:exported="true" /> + android:exported="true"> + <meta-data android:name="android.service.wallpaper" + android:resource="@xml/gradient_color_wallpaper"> + </meta-data> + </service> <activity android:name=".tuner.TunerActivity" android:enabled="false" diff --git a/packages/SystemUI/accessibility/accessibilitymenu/aconfig/Android.bp b/packages/SystemUI/accessibility/accessibilitymenu/aconfig/Android.bp index 0ff856e0b91e..1d74774c7c11 100644 --- a/packages/SystemUI/accessibility/accessibilitymenu/aconfig/Android.bp +++ b/packages/SystemUI/accessibility/accessibilitymenu/aconfig/Android.bp @@ -5,7 +5,7 @@ package { aconfig_declarations { name: "com_android_a11y_menu_flags", package: "com.android.systemui.accessibility.accessibilitymenu", - container: "system", + container: "system_ext", srcs: [ "accessibility.aconfig", ], diff --git a/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig b/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig index 6d790114803a..bdf6d4242e68 100644 --- a/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig +++ b/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig @@ -1,5 +1,5 @@ package: "com.android.systemui.accessibility.accessibilitymenu" -container: "system" +container: "system_ext" # NOTE: Keep alphabetized to help limit merge conflicts from multiple simultaneous editors. diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index f753316cb67a..a92df8026715 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -186,6 +186,16 @@ flag { } flag { + name: "notifications_pinned_hun_in_shade" + namespace: "systemui" + description: "Fixes displaying pinned HUNs in the Shade, making sure that their y and z positions are correct." + bug: "385041854" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "pss_app_selector_recents_split_screen" namespace: "systemui" description: "Allows recent apps selected for partial screenshare to be launched in split screen mode" @@ -227,6 +237,13 @@ flag { } flag { + name: "notification_bundle_ui" + namespace: "systemui" + description: "Three-level group UI for notification bundles" + bug: "367996732" +} + +flag { name: "notification_background_tint_optimization" namespace: "systemui" description: "Re-enable the codepath that removed tinting of notifications when the" @@ -524,6 +541,16 @@ flag { } flag { + name: "indication_text_a11y_fix" + namespace: "systemui" + description: "add double shadow to the indication text at the bottom of the lock screen" + bug: "349297241" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "rest_to_unlock" namespace: "systemui" description: "Require prolonged touch for fingerprint authentication" @@ -1769,7 +1796,14 @@ flag { flag { name: "bouncer_ui_revamp" namespace: "systemui" - description: "Updates to background (blur), button animations and font changes." + description: "Updates to background (blur) for bouncer" + bug: "370555003" +} + +flag { + name: "bouncer_ui_revamp_2" + namespace: "systemui" + description: "Updates to button animations and font changes for bouncer, bouncer_ui_revamp will cover only the blur changes." bug: "376491880" } @@ -1825,6 +1859,13 @@ flag { } flag { + name: "double_tap_to_sleep" + namespace: "systemui" + description: "Enable Double Tap to Sleep" + bug: "385194612" +} + +flag{ name: "gsf_bouncer" namespace: "systemui" description: "Applies GSF font styles to Bouncer surfaces." @@ -1839,13 +1880,6 @@ flag { } flag { - name: "glanceable_hub_shortcut_button" - namespace: "systemui" - description: "Adds a shortcut button to lockscreen to show glanceable hub." - bug: "378173531" -} - -flag { name: "spatial_model_launcher_pushback" namespace: "systemui" description: "Implement the depth push scaling effect on Launcher when users pull down shade." @@ -1915,16 +1949,6 @@ flag { } flag { - name: "stabilize_heads_up_group" - namespace: "systemui" - description: "Stabilize heads up groups in VisualStabilityCoordinator" - bug: "381864715" - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag { name: "magnetic_notification_horizontal_swipe" namespace: "systemui" description: "Add support for magnetic behavior on horizontal notification swipes." @@ -1942,8 +1966,35 @@ flag { } flag { + name: "permission_helper_ui_rich_ongoing" + namespace: "systemui" + description: "[RONs] Guards inline permission helper for demoting RONs" + bug: "379186372" +} + +flag { name: "aod_ui_rich_ongoing" namespace: "systemui" description: "Show a rich ongoing notification on the always-on display (depends on ui_rich_ongoing)" bug: "369151941" -}
\ No newline at end of file +} + +flag { + name: "stabilize_heads_up_group_v2" + namespace: "systemui" + description: "Stabilize heads up groups in VisualStabilityCoordinator" + bug: "357753857" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + name: "shade_launch_accessibility" + namespace: "systemui" + description: "Intercept accessibility focus events for the Shade during launch animations to avoid stray TalkBack events." + bug: "379222226" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/FontVariationUtils.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/FontVariationUtils.kt index 78ae4af258fc..9545bda80b2d 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/FontVariationUtils.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/FontVariationUtils.kt @@ -1,9 +1,12 @@ package com.android.systemui.animation -private const val TAG_WGHT = "wght" -private const val TAG_WDTH = "wdth" -private const val TAG_OPSZ = "opsz" -private const val TAG_ROND = "ROND" +object GSFAxes { + const val WEIGHT = "wght" + const val WIDTH = "wdth" + const val SLANT = "slnt" + const val ROUND = "ROND" + const val OPTICAL_SIZE = "opsz" +} class FontVariationUtils { private var mWeight = -1 @@ -21,7 +24,7 @@ class FontVariationUtils { weight: Int = -1, width: Int = -1, opticalSize: Int = -1, - roundness: Int = -1 + roundness: Int = -1, ): String { isUpdated = false if (weight >= 0 && mWeight != weight) { @@ -43,16 +46,20 @@ class FontVariationUtils { } var resultString = "" if (mWeight >= 0) { - resultString += "'$TAG_WGHT' $mWeight" + resultString += "'${GSFAxes.WEIGHT}' $mWeight" } if (mWidth >= 0) { - resultString += (if (resultString.isBlank()) "" else ", ") + "'$TAG_WDTH' $mWidth" + resultString += + (if (resultString.isBlank()) "" else ", ") + "'${GSFAxes.WIDTH}' $mWidth" } if (mOpticalSize >= 0) { - resultString += (if (resultString.isBlank()) "" else ", ") + "'$TAG_OPSZ' $mOpticalSize" + resultString += + (if (resultString.isBlank()) "" else ", ") + + "'${GSFAxes.OPTICAL_SIZE}' $mOpticalSize" } if (mRoundness >= 0) { - resultString += (if (resultString.isBlank()) "" else ", ") + "'$TAG_ROND' $mRoundness" + resultString += + (if (resultString.isBlank()) "" else ", ") + "'${GSFAxes.ROUND}' $mRoundness" } return if (isUpdated) resultString else "" } diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RunTestShouldUseKosmosDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RunTestShouldUseKosmosDetector.kt new file mode 100644 index 000000000000..4927fb9dc67d --- /dev/null +++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RunTestShouldUseKosmosDetector.kt @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.systemui.lint + +import com.android.tools.lint.detector.api.Category +import com.android.tools.lint.detector.api.Detector +import com.android.tools.lint.detector.api.Implementation +import com.android.tools.lint.detector.api.Issue +import com.android.tools.lint.detector.api.JavaContext +import com.android.tools.lint.detector.api.Scope +import com.android.tools.lint.detector.api.Severity +import com.android.tools.lint.detector.api.SourceCodeScanner +import com.android.tools.lint.detector.api.getReceiver +import com.intellij.psi.PsiMethod +import org.jetbrains.uast.UCallExpression +import org.jetbrains.uast.getContainingUFile + +/** + * Detects test function naming violations regarding use of the backtick-wrapped space-allowed + * feature of Kotlin functions. + */ +class RunTestShouldUseKosmosDetector : Detector(), SourceCodeScanner { + override fun getApplicableMethodNames() = listOf("runTest") + + override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) { + if (method.getReceiver()?.qualifiedName == "kotlinx.coroutines.test.TestScope") { + + val imports = + node.getContainingUFile()?.imports.orEmpty().mapNotNull { + it.importReference?.asSourceString() + } + if (imports.any { it == "com.android.systemui.kosmos.Kosmos" }) { + context.report( + issue = ISSUE, + scope = node, + location = context.getLocation(node.methodIdentifier), + message = + "Prefer Kosmos.runTest to TestScope.runTest in sysui tests that use Kosmos. go/kosmos-runtest", + ) + super.visitMethodCall(context, node, method) + } + } + } + + companion object { + @JvmStatic + val ISSUE = + Issue.create( + id = "RunTestShouldUseKosmos", + briefDescription = "When you can, use Kosmos.runTest instead of TestScope.runTest.", + explanation = + """ + Kosmos.runTest helps to ensure that the test uses the same coroutine + dispatchers that are used in Kosmos fixtures, preventing subtle bugs. + See go/kosmos-runtest + """, + category = Category.TESTING, + priority = 8, + severity = Severity.WARNING, + implementation = + Implementation( + RunTestShouldUseKosmosDetector::class.java, + Scope.JAVA_FILE_SCOPE, + ), + ) + } +} diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RunTestShouldUseKosmosDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RunTestShouldUseKosmosDetectorTest.kt new file mode 100644 index 000000000000..41e90b863fab --- /dev/null +++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RunTestShouldUseKosmosDetectorTest.kt @@ -0,0 +1,214 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.internal.systemui.lint + +import com.android.tools.lint.checks.infrastructure.TestFile +import com.android.tools.lint.checks.infrastructure.TestFiles +import com.android.tools.lint.checks.infrastructure.TestLintResult +import com.android.tools.lint.detector.api.Detector +import com.android.tools.lint.detector.api.Issue +import org.junit.Test + +class RunTestShouldUseKosmosDetectorTest : SystemUILintDetectorTest() { + override fun getDetector(): Detector = RunTestShouldUseKosmosDetector() + + override fun getIssues(): List<Issue> = listOf(RunTestShouldUseKosmosDetector.ISSUE) + + @Test + fun wronglyTriesToUseScopeRunTest() { + val runOnSource = + runOnSource( + """ + package test.pkg.name + + import com.android.systemui.kosmos.Kosmos + import kotlinx.coroutines.test.runTest + import kotlinx.coroutines.test.TestScope + import org.junit.Test + + class MyTest { + val scope: TestScope + val kosmos: Kosmos + + @Test + fun badTest() = scope.runTest { + // test code + } + } + """ + ) + + runOnSource + .expectWarningCount(1) + .expect( + """ + src/test/pkg/name/MyTest.kt:13: Warning: Prefer Kosmos.runTest to TestScope.runTest in sysui tests that use Kosmos. go/kosmos-runtest [RunTestShouldUseKosmos] + fun badTest() = scope.runTest { + ~~~~~~~ + 0 errors, 1 warnings + """ + ) + } + + @Test + fun testScopeRunTestIsOKifKosmosNotUsed() { + runOnSource( + """ + package test.pkg.name + + import kotlinx.coroutines.test.runTest + import kotlinx.coroutines.test.TestScope + import org.junit.Test + + class MyTest { + val scope: TestScope + + @Test + fun okTest() = scope.runTest { + // test code + } + } + """ + ) + .expectWarningCount(0) + } + + @Test + fun otherTestScopeMethodsAreOK() { + runOnSource( + """ + package test.pkg.name + + import com.android.systemui.kosmos.Kosmos + import com.android.systemui.kosmos.runTest + import kotlinx.coroutines.test.TestScope + import org.junit.Test + + class MyTest { + val scope: TestScope + val kosmos: Kosmos + + @Test + fun okTest() = kosmos.runTest { + scope.cancel() + // test code + } + } + """ + ) + .expectWarningCount(0) + } + + @Test + fun correctlyUsesKosmosRunTest() { + runOnSource( + """ + package test.pkg.name + + import com.android.systemui.kosmos.Kosmos + import com.android.systemui.kosmos.runTest + import kotlinx.coroutines.test.TestScope + import org.junit.Test + + class MyTest { + val scope: TestScope + val kosmos: Kosmos + + @Test + fun okTest() = kosmos.runTest { + // test code + } + } + """ + ) + .expectWarningCount(0) + } + + private fun runOnSource(source: String): TestLintResult { + return lint() + .files( + TestFiles.kotlin(source).indented(), + testAnnotationStub, + runTestStub, + testScopeStub, + kosmosStub, + kosmosRunTestStub, + ) + .issues(RunTestShouldUseKosmosDetector.ISSUE) + .run() + } + + companion object { + private val testAnnotationStub: TestFile = + kotlin( + """ + package org.junit + + import java.lang.annotation.ElementType + import java.lang.annotation.Retention + import java.lang.annotation.RetentionPolicy + import java.lang.annotation.Target + + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.METHOD}) + annotation class Test + """ + ) + + private val runTestStub: TestFile = + kotlin( + """ + package kotlinx.coroutines.test + + fun TestScope.runTest( + timeout: Duration = DEFAULT_TIMEOUT.getOrThrow(), + testBody: suspend TestScope.() -> Unit + ): Unit = {} + """ + ) + + private val testScopeStub: TestFile = + kotlin( + """ + package kotlinx.coroutines.test + + class TestScope + + public fun TestScope.cancel() {} + """ + ) + + private val kosmosStub: TestFile = + kotlin( + """ + package com.android.systemui.kosmos + + class Kosmos + """ + ) + + private val kosmosRunTestStub: TestFile = + kotlin( + """ + package com.android.systemui.kosmos + + fun Kosmos.runTest(testBody: suspend Kosmos.() -> Unit) + """ + ) + } +} diff --git a/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt b/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt index a27bf8af1806..195b060932eb 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt +++ b/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt @@ -464,13 +464,15 @@ private class NestedDraggableNode( velocity: Velocity, performFling: suspend (Velocity) -> Velocity, ): Velocity { + // Make sure we only use the velocity in this draggable orientation. + val orientationVelocity = velocity.toFloat().toVelocity() return if (overscrollEffect != null) { - overscrollEffect.applyToFling(velocity) { performFling(it) } + overscrollEffect.applyToFling(orientationVelocity) { performFling(it) } // Effects always consume the whole velocity. velocity } else { - performFling(velocity) + performFling(orientationVelocity) } } diff --git a/packages/SystemUI/compose/core/src/com/android/compose/gesture/effect/ContentOverscrollEffect.kt b/packages/SystemUI/compose/core/src/com/android/compose/gesture/effect/ContentOverscrollEffect.kt index 4ee6db3d516c..49e510791929 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/gesture/effect/ContentOverscrollEffect.kt +++ b/packages/SystemUI/compose/core/src/com/android/compose/gesture/effect/ContentOverscrollEffect.kt @@ -23,7 +23,9 @@ 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.HorizontalSpaceVectorConverter import com.android.compose.ui.util.SpaceVectorConverter +import com.android.compose.ui.util.VerticalSpaceVectorConverter import kotlin.math.abs import kotlin.math.sign import kotlinx.coroutines.CoroutineScope @@ -40,13 +42,12 @@ interface ContentOverscrollEffect : OverscrollEffect { } open class BaseContentOverscrollEffect( - orientation: Orientation, private val animationScope: CoroutineScope, private val animationSpec: AnimationSpec<Float>, -) : ContentOverscrollEffect, SpaceVectorConverter by SpaceVectorConverter(orientation) { - +) : ContentOverscrollEffect { /** The [Animatable] that holds the current overscroll value. */ private val animatable = Animatable(initialValue = 0f, visibilityThreshold = 0.5f) + private var lastConverter: SpaceVectorConverter? = null override val overscrollDistance: Float get() = animatable.value @@ -59,6 +60,15 @@ open class BaseContentOverscrollEffect( source: NestedScrollSource, performScroll: (Offset) -> Offset, ): Offset { + val converter = converterOrNull(delta.x, delta.y) ?: return performScroll(delta) + return converter.applyToScroll(delta, source, performScroll) + } + + private fun SpaceVectorConverter.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 @@ -106,6 +116,14 @@ open class BaseContentOverscrollEffect( velocity: Velocity, performFling: suspend (Velocity) -> Velocity, ) { + val converter = converterOrNull(velocity.x, velocity.y) ?: return + converter.applyToFling(velocity, performFling) + } + + private suspend fun SpaceVectorConverter.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. @@ -117,4 +135,35 @@ open class BaseContentOverscrollEffect( } } } + + protected fun requireConverter(): SpaceVectorConverter { + return checkNotNull(lastConverter) { + "lastConverter is null, make sure to call requireConverter() only when " + + "overscrollDistance != 0f" + } + } + + private fun converterOrNull(x: Float, y: Float): SpaceVectorConverter? { + val converter: SpaceVectorConverter = + when { + x != 0f && y != 0f -> + error( + "BaseContentOverscrollEffect only supports single orientation scrolls " + + "and velocities" + ) + x == 0f && y == 0f -> lastConverter ?: return null + x != 0f -> HorizontalSpaceVectorConverter + else -> VerticalSpaceVectorConverter + } + + if (lastConverter != null) { + check(lastConverter == converter) { + "BaseContentOverscrollEffect should always be used in the same orientation" + } + } else { + lastConverter = converter + } + + return converter + } } diff --git a/packages/SystemUI/compose/core/src/com/android/compose/gesture/effect/OffsetOverscrollEffect.kt b/packages/SystemUI/compose/core/src/com/android/compose/gesture/effect/OffsetOverscrollEffect.kt index fc01c78f8c54..1bb8ae5019fb 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/gesture/effect/OffsetOverscrollEffect.kt +++ b/packages/SystemUI/compose/core/src/com/android/compose/gesture/effect/OffsetOverscrollEffect.kt @@ -19,7 +19,7 @@ package com.android.compose.gesture.effect import androidx.annotation.VisibleForTesting import androidx.compose.animation.core.AnimationSpec import androidx.compose.foundation.OverscrollEffect -import androidx.compose.foundation.gestures.Orientation +import androidx.compose.foundation.OverscrollFactory import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable @@ -37,24 +37,42 @@ import androidx.compose.ui.unit.dp 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>, -) : BaseContentOverscrollEffect(orientation, animationScope, animationSpec) { - private var _node: DelegatableNode = newNode() - override val node: DelegatableNode - get() = _node +@Composable +@OptIn(ExperimentalMaterial3ExpressiveApi::class) +fun rememberOffsetOverscrollEffect( + animationSpec: AnimationSpec<Float> = MaterialTheme.motionScheme.slowSpatialSpec() +): OffsetOverscrollEffect { + val animationScope = rememberCoroutineScope() + return remember(animationScope, animationSpec) { + OffsetOverscrollEffect(animationScope, animationSpec) + } +} - 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() - } +@Composable +@OptIn(ExperimentalMaterial3ExpressiveApi::class) +fun rememberOffsetOverscrollEffectFactory( + animationSpec: AnimationSpec<Float> = MaterialTheme.motionScheme.slowSpatialSpec() +): OverscrollFactory { + val animationScope = rememberCoroutineScope() + return remember(animationScope, animationSpec) { + OffsetOverscrollEffectFactory(animationScope, animationSpec) + } +} +data class OffsetOverscrollEffectFactory( + private val animationScope: CoroutineScope, + private val animationSpec: AnimationSpec<Float>, +) : OverscrollFactory { + override fun createOverscrollEffect(): OverscrollEffect { + return OffsetOverscrollEffect(animationScope, animationSpec) + } +} + +/** An [OverscrollEffect] that offsets the content by the overscroll value. */ +class OffsetOverscrollEffect(animationScope: CoroutineScope, animationSpec: AnimationSpec<Float>) : + BaseContentOverscrollEffect(animationScope, animationSpec) { + override val node: DelegatableNode = + object : Modifier.Node(), LayoutModifierNode { override fun MeasureScope.measure( measurable: Measurable, constraints: Constraints, @@ -62,11 +80,16 @@ class OffsetOverscrollEffect( val placeable = measurable.measure(constraints) return layout(placeable.width, placeable.height) { val offsetPx = computeOffset(density = this@measure, overscrollDistance) - placeable.placeRelativeWithLayer(position = offsetPx.toIntOffset()) + if (offsetPx != 0) { + placeable.placeRelativeWithLayer( + with(requireConverter()) { offsetPx.toIntOffset() } + ) + } else { + placeable.placeRelative(0, 0) + } } } } - } companion object { private val MaxDistance = 400.dp @@ -80,18 +103,6 @@ class OffsetOverscrollEffect( } } -@OptIn(ExperimentalMaterial3ExpressiveApi::class) -@Composable -fun rememberOffsetOverscrollEffect( - orientation: Orientation, - animationSpec: AnimationSpec<Float> = MaterialTheme.motionScheme.defaultSpatialSpec(), -): OffsetOverscrollEffect { - val animationScope = rememberCoroutineScope() - return remember(orientation, animationScope, animationSpec) { - OffsetOverscrollEffect(orientation, animationScope, animationSpec) - } -} - /** This converter lets you change a linear progress into a function of your choice. */ fun interface ProgressConverter { fun convert(progress: Float): Float diff --git a/packages/SystemUI/compose/core/src/com/android/compose/ui/util/SpaceVectorConverter.kt b/packages/SystemUI/compose/core/src/com/android/compose/ui/util/SpaceVectorConverter.kt index ca50e778d131..ac0e17a591dc 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/ui/util/SpaceVectorConverter.kt +++ b/packages/SystemUI/compose/core/src/com/android/compose/ui/util/SpaceVectorConverter.kt @@ -37,11 +37,11 @@ interface SpaceVectorConverter { fun SpaceVectorConverter(orientation: Orientation) = when (orientation) { - Orientation.Horizontal -> HorizontalConverter - Orientation.Vertical -> VerticalConverter + Orientation.Horizontal -> HorizontalSpaceVectorConverter + Orientation.Vertical -> VerticalSpaceVectorConverter } -private data object HorizontalConverter : SpaceVectorConverter { +data object HorizontalSpaceVectorConverter : SpaceVectorConverter { override fun Offset.toFloat() = x override fun Velocity.toFloat() = x @@ -55,7 +55,7 @@ private data object HorizontalConverter : SpaceVectorConverter { override fun Int.toIntOffset() = IntOffset(this, 0) } -private data object VerticalConverter : SpaceVectorConverter { +data object VerticalSpaceVectorConverter : SpaceVectorConverter { override fun Offset.toFloat() = y override fun Velocity.toFloat() = y diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/effect/OffsetOverscrollEffectTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/effect/OffsetOverscrollEffectTest.kt index 5a3f240deb44..e7c47fb56130 100644 --- a/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/effect/OffsetOverscrollEffectTest.kt +++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/effect/OffsetOverscrollEffectTest.kt @@ -55,10 +55,7 @@ class OffsetOverscrollEffectTest { } } - private fun setupOverscrollableBox( - scrollableOrientation: Orientation, - overscrollEffectOrientation: Orientation = scrollableOrientation, - ): LayoutInfo { + private fun setupOverscrollableBox(scrollableOrientation: Orientation): LayoutInfo { val layoutSize: Dp = 200.dp var touchSlop: Float by Delegates.notNull() // The draggable touch slop, i.e. the min px distance a touch pointer must move before it is @@ -67,7 +64,7 @@ class OffsetOverscrollEffectTest { rule.setContent { density = LocalDensity.current touchSlop = LocalViewConfiguration.current.touchSlop - val overscrollEffect = rememberOffsetOverscrollEffect(overscrollEffectOrientation) + val overscrollEffect = rememberOffsetOverscrollEffect() Box( Modifier.overscroll(overscrollEffect) @@ -102,11 +99,7 @@ class OffsetOverscrollEffectTest { @Test fun applyNoOffset_duringHorizontalOverscroll() { - val info = - setupOverscrollableBox( - scrollableOrientation = Orientation.Vertical, - overscrollEffectOrientation = Orientation.Horizontal, - ) + val info = setupOverscrollableBox(scrollableOrientation = Orientation.Vertical) rule.onNodeWithTag(BOX_TAG).assertTopPositionInRootIsEqualTo(0.dp) 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 fea34921b853..30dfa5bb826a 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 @@ -37,6 +37,7 @@ import com.android.systemui.communal.smartspace.SmartspaceInteractionHandler import com.android.systemui.communal.ui.compose.section.AmbientStatusBarSection import com.android.systemui.communal.ui.compose.section.CommunalPopupSection import com.android.systemui.communal.ui.compose.section.CommunalToDreamButtonSection +import com.android.systemui.communal.ui.compose.section.HubOnboardingSection import com.android.systemui.communal.ui.view.layout.sections.CommunalAppWidgetSection import com.android.systemui.communal.ui.viewmodel.CommunalViewModel import com.android.systemui.keyguard.ui.composable.blueprint.BlueprintAlignmentLines @@ -62,6 +63,7 @@ constructor( private val communalPopupSection: CommunalPopupSection, private val widgetSection: CommunalAppWidgetSection, private val communalToDreamButtonSection: CommunalToDreamButtonSection, + private val hubOnboardingSection: HubOnboardingSection, ) { @Composable @@ -83,6 +85,7 @@ constructor( modifier = Modifier.element(Communal.Elements.Grid), contentScope = this@Content, ) + with(hubOnboardingSection) { BottomSheet() } } if (communalSettingsInteractor.isV2FlagEnabled()) { Icon( diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt index 7a500805809d..c972d3e3cf15 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt @@ -44,6 +44,8 @@ import androidx.compose.ui.unit.round import androidx.compose.ui.unit.toOffset import androidx.compose.ui.unit.toSize import com.android.systemui.Flags.communalWidgetResizing +import com.android.systemui.communal.domain.model.CommunalContentModel +import com.android.systemui.communal.shared.model.CommunalContentSize import com.android.systemui.communal.ui.compose.extensions.firstItemAtOffset import com.android.systemui.communal.ui.compose.extensions.plus import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel @@ -105,6 +107,9 @@ internal constructor( private var draggingItemDraggedDelta by mutableStateOf(Offset.Zero) private var draggingItemInitialOffset by mutableStateOf(Offset.Zero) + private val spacer = CommunalContentModel.Spacer(CommunalContentSize.Responsive(1)) + private var spacerIndex: Int? = null + private var previousTargetItemKey: Any? = null internal val draggingItemOffset: Offset @@ -140,6 +145,17 @@ internal constructor( ?.apply { draggingItemKey = key as String draggingItemInitialOffset = this.offset.toOffset() + // Add a spacer after the last widget if it is larger than the dragging widget. + // This allows overscrolling, enabling the dragging widget to be placed beyond it. + val lastWidget = contentListState.list.lastOrNull { it.isWidgetContent() } + if ( + lastWidget != null && + draggingItemLayoutInfo != null && + lastWidget.size.span > draggingItemLayoutInfo!!.span + ) { + contentListState.list.add(spacer) + spacerIndex = contentListState.list.size - 1 + } return true } @@ -162,6 +178,11 @@ internal constructor( previousTargetItemKey = null draggingItemDraggedDelta = Offset.Zero draggingItemInitialOffset = Offset.Zero + // Remove spacer, if any, when a drag gesture finishes. + spacerIndex?.let { + contentListState.list.removeAt(it) + spacerIndex = null + } } internal fun onDrag(offset: Offset, layoutDirection: LayoutDirection) { 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 index c7930549abe8..44c375d6ac5e 100644 --- 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 @@ -272,9 +272,8 @@ private fun calculateNumCellsWidth(width: Dp) = } private fun calculateNumCellsHeight(height: Dp) = - // See https://developer.android.com/develop/ui/views/layout/use-window-size-classes when { - height >= 900.dp -> 3 + height >= 1000.dp -> 3 height >= 480.dp -> 2 else -> 1 } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/HubOnboardingSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/HubOnboardingSection.kt new file mode 100644 index 000000000000..6943e9b00ed8 --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/HubOnboardingSection.kt @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.communal.ui.compose.section + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +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.width +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.ChargingStation +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.android.systemui.communal.ui.viewmodel.HubOnboardingViewModel +import com.android.systemui.lifecycle.rememberViewModel +import com.android.systemui.res.R +import com.android.systemui.statusbar.phone.ComponentSystemUIDialog +import com.android.systemui.statusbar.phone.SystemUIDialogFactory +import com.android.systemui.statusbar.phone.createBottomSheet +import javax.inject.Inject + +class HubOnboardingSection +@Inject +constructor( + private val viewModelFactory: HubOnboardingViewModel.Factory, + private val dialogFactory: SystemUIDialogFactory, +) { + @Composable + fun BottomSheet() { + val viewModel = rememberViewModel("HubOnboardingSection") { viewModelFactory.create() } + val shouldShowHubOnboarding by + viewModel.shouldShowHubOnboarding.collectAsStateWithLifecycle(false) + + if (!shouldShowHubOnboarding) { + return + } + + HubOnboardingBottomSheet(shouldShowBottomSheet = true, dialogFactory = dialogFactory) { + viewModel.onDismissed() + } + } +} + +@Composable +private fun HubOnboardingBottomSheet( + shouldShowBottomSheet: Boolean, + dialogFactory: SystemUIDialogFactory, + onDismiss: () -> Unit, +) { + var dialog: ComponentSystemUIDialog? by remember { mutableStateOf(null) } + var dismissingDueToCancel by remember { mutableStateOf(false) } + + DisposableEffect(shouldShowBottomSheet) { + if (shouldShowBottomSheet) { + dialog = + dialogFactory + .createBottomSheet( + content = { HubOnboardingBottomSheetContent { dialog?.dismiss() } }, + isDraggable = true, + maxWidth = 627.dp, + ) + .apply { + setOnDismissListener { + // Don't set the onboarding dismissed flag if the dismiss was due to a + // cancel. Note that a "dismiss" is something initiated by the user + // (e.g. swipe down or tapping outside), while a "cancel" is a dismiss + // not initiated by the user (e.g. timing out to dream). We only want + // to mark the bottom sheet as dismissed if the user explicitly + // dismissed it. + if (!dismissingDueToCancel) { + onDismiss() + } + } + setOnCancelListener { dismissingDueToCancel = true } + show() + } + } + + onDispose { + dialog?.cancel() + dialog = null + } + } +} + +@Composable +private fun HubOnboardingBottomSheetContent(onButtonClicked: () -> Unit) { + val colors = MaterialTheme.colorScheme + Column( + modifier = Modifier.fillMaxWidth().padding(48.dp), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Icon( + imageVector = Icons.Outlined.ChargingStation, + contentDescription = null, + modifier = Modifier.size(32.dp), + ) + Spacer(modifier = Modifier.height(16.dp)) + Text( + text = stringResource(R.string.hub_onboarding_bottom_sheet_title), + style = MaterialTheme.typography.headlineMedium, + ) + Spacer(modifier = Modifier.height(32.dp)) + // TODO(b/388283881): Replace with correct animations and possibly add a content description + // if necessary. + Image(painter = painterResource(R.drawable.hub_onboarding_bg), contentDescription = null) + Spacer(modifier = Modifier.height(32.dp)) + Text( + modifier = Modifier.width(300.dp), + text = stringResource(R.string.hub_onboarding_bottom_sheet_text), + textAlign = TextAlign.Center, + ) + Spacer(modifier = Modifier.height(32.dp)) + Button( + modifier = Modifier.align(Alignment.End), + colors = + ButtonDefaults.buttonColors( + containerColor = colors.primary, + contentColor = colors.onPrimary, + ), + onClick = onButtonClicked, + ) { + Text( + stringResource(R.string.hub_onboarding_bottom_sheet_action_button), + style = MaterialTheme.typography.labelLarge, + ) + } + } +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt index c55a3fdfc6c0..478970f4e210 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt @@ -45,6 +45,7 @@ import com.android.systemui.keyguard.ui.composable.section.StatusBarSection import com.android.systemui.keyguard.ui.composable.section.TopAreaSection import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel import com.android.systemui.res.R +import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiAod import java.util.Optional import javax.inject.Inject import kotlin.math.roundToInt @@ -128,11 +129,14 @@ constructor( with(notificationSection) { if (!isShadeLayoutWide && !isBypassEnabled) { Box(modifier = Modifier.weight(weight = 1f)) { - AodNotificationIcons( - modifier = - Modifier.align(alignment = Alignment.TopStart) - .padding(start = aodIconPadding) - ) + Column(Modifier.align(alignment = Alignment.TopStart)) { + if (PromotedNotificationUiAod.isEnabled) { + AodPromotedNotification() + } + AodNotificationIcons( + modifier = Modifier.padding(start = aodIconPadding) + ) + } Notifications( areNotificationsVisible = areNotificationsVisible, isShadeLayoutWide = false, @@ -140,9 +144,14 @@ constructor( ) } } else { - AodNotificationIcons( - modifier = Modifier.padding(start = aodIconPadding) - ) + Column { + if (PromotedNotificationUiAod.isEnabled) { + AodPromotedNotification() + } + AodNotificationIcons( + modifier = Modifier.padding(start = aodIconPadding) + ) + } } } if (!isUdfpsVisible && ambientIndicationSectionOptional.isPresent) { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/MediaCarouselSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/MediaCarouselSection.kt index 0ff567bf90ad..d8b3f742b447 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/MediaCarouselSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/MediaCarouselSection.kt @@ -19,12 +19,11 @@ package com.android.systemui.keyguard.ui.composable.section import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.dimensionResource -import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.scene.ContentScope import com.android.systemui.keyguard.ui.viewmodel.KeyguardMediaViewModel +import com.android.systemui.lifecycle.rememberViewModel import com.android.systemui.media.controls.ui.composable.MediaCarousel import com.android.systemui.media.controls.ui.controller.MediaCarouselController import com.android.systemui.media.controls.ui.view.MediaHost @@ -38,7 +37,7 @@ class MediaCarouselSection constructor( private val mediaCarouselController: MediaCarouselController, @param:Named(MediaModule.KEYGUARD) private val mediaHost: MediaHost, - private val keyguardMediaViewModel: KeyguardMediaViewModel, + private val keyguardMediaViewModelFactory: KeyguardMediaViewModel.Factory, ) { @Composable @@ -46,7 +45,10 @@ constructor( isShadeLayoutWide: Boolean, modifier: Modifier = Modifier, ) { - val isMediaVisible by keyguardMediaViewModel.isMediaVisible.collectAsStateWithLifecycle() + val viewModel = + rememberViewModel(traceName = "KeyguardMediaCarousel") { + keyguardMediaViewModelFactory.create() + } val horizontalPadding = if (isShadeLayoutWide) { dimensionResource(id = R.dimen.notification_side_paddings) @@ -55,7 +57,7 @@ constructor( dimensionResource(id = R.dimen.notification_panel_margin_horizontal) } MediaCarousel( - isVisible = isMediaVisible, + isVisible = viewModel.isMediaVisible, mediaHost = mediaHost, modifier = modifier.fillMaxWidth().padding(horizontal = horizontalPadding), carouselController = mediaCarouselController, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt index 2bc392d386bf..b66690c2fe89 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt @@ -53,6 +53,8 @@ import com.android.systemui.statusbar.notification.icon.ui.viewbinder.AlwaysOnDi import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel +import com.android.systemui.statusbar.notification.promoted.AODPromotedNotification +import com.android.systemui.statusbar.notification.promoted.ui.viewmodel.AODPromotedNotificationViewModel import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer @@ -84,6 +86,7 @@ constructor( private val iconBindingFailureTracker: StatusBarIconViewBindingFailureTracker, private val nicAodViewModel: NotificationIconContainerAlwaysOnDisplayViewModel, private val nicAodIconViewStore: AlwaysOnDisplayNotificationIconViewStore, + private val aodPromotedNotificationViewModelFactory: AODPromotedNotificationViewModel.Factory, private val systemBarUtilsState: SystemBarUtilsState, private val clockInteractor: KeyguardClockInteractor, ) { @@ -107,6 +110,11 @@ constructor( } @Composable + fun AodPromotedNotification() { + AODPromotedNotification(aodPromotedNotificationViewModelFactory) + } + + @Composable fun AodNotificationIcons(modifier: Modifier = Modifier) { val isVisible by keyguardRootViewModel.isNotifIconContainerVisible.collectAsStateWithLifecycle() diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaContentPicker.kt b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaContentPicker.kt index 215a43382b06..6e9e8efea894 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaContentPicker.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaContentPicker.kt @@ -41,8 +41,8 @@ object MediaContentPicker : StaticElementContentPicker { override fun contentDuringTransition( element: ElementKey, transition: TransitionState.Transition, - fromContentZIndex: Float, - toContentZIndex: Float, + fromContentZIndex: Long, + toContentZIndex: Long, ): ContentKey { return when { transition.isTransitioningBetween(Scenes.Lockscreen, Scenes.Shade) -> { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt index 9eb1f68dd669..931134795a53 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt @@ -41,9 +41,10 @@ import com.android.systemui.res.R import com.android.systemui.scene.session.ui.composable.SaveableSession import com.android.systemui.scene.shared.model.Overlays import com.android.systemui.scene.ui.composable.Overlay -import com.android.systemui.shade.ui.composable.CollapsedShadeHeader import com.android.systemui.shade.ui.composable.OverlayShade +import com.android.systemui.shade.ui.composable.OverlayShadeHeader import com.android.systemui.shade.ui.composable.SingleShadeMeasurePolicy +import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerStatusBarViewBinder import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView import com.android.systemui.statusbar.phone.ui.StatusBarIconController import com.android.systemui.statusbar.phone.ui.TintedIconManager @@ -60,6 +61,8 @@ constructor( private val tintedIconManagerFactory: TintedIconManager.Factory, private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory, private val statusBarIconController: StatusBarIconController, + private val notificationIconContainerStatusBarViewBinder: + NotificationIconContainerStatusBarViewBinder, private val shadeSession: SaveableSession, private val stackScrollView: Lazy<NotificationScrollView>, private val clockSection: DefaultClockSection, @@ -79,7 +82,6 @@ constructor( @Composable override fun ContentScope.Content(modifier: Modifier) { - val notificationStackPadding = dimensionResource(id = R.dimen.notification_side_paddings) val viewModel = @@ -92,26 +94,29 @@ constructor( } OverlayShade( + isShadeLayoutWide = viewModel.isShadeLayoutWide, panelAlignment = Alignment.TopStart, modifier = modifier, onScrimClicked = viewModel::onScrimClicked, + header = { + OverlayShadeHeader( + viewModelFactory = viewModel.shadeHeaderViewModelFactory, + createTintedIconManager = tintedIconManagerFactory::create, + createBatteryMeterViewController = batteryMeterViewControllerFactory::create, + statusBarIconController = statusBarIconController, + notificationIconContainerStatusBarViewBinder = + notificationIconContainerStatusBarViewBinder, + modifier = + Modifier.element(NotificationsShade.Elements.StatusBar) + .layoutId(SingleShadeMeasurePolicy.LayoutId.ShadeHeader), + ) + }, ) { Box { Column { if (viewModel.showHeader) { val burnIn = rememberBurnIn(clockInteractor) - CollapsedShadeHeader( - viewModelFactory = viewModel.shadeHeaderViewModelFactory, - createTintedIconManager = tintedIconManagerFactory::create, - createBatteryMeterViewController = - batteryMeterViewControllerFactory::create, - statusBarIconController = statusBarIconController, - modifier = - Modifier.element(NotificationsShade.Elements.StatusBar) - .layoutId(SingleShadeMeasurePolicy.LayoutId.ShadeHeader), - ) - with(clockSection) { SmallClock( burnInParams = burnIn.parameters, @@ -126,16 +131,17 @@ constructor( stackScrollView = stackScrollView.get(), viewModel = placeholderViewModel, maxScrimTop = { 0f }, + shouldPunchHoleBehindScrim = false, stackTopPadding = notificationStackPadding, stackBottomPadding = notificationStackPadding, - shouldPunchHoleBehindScrim = false, shouldFillMaxSize = false, shouldShowScrim = false, supportNestedScrolling = false, modifier = Modifier.fillMaxWidth(), ) } - // Communicates the bottom position of the drawable area within the shade to NSSL. + // Communicates the bottom position of the drawable area within the shade to + // NSSL. NotificationStackCutoffGuideline( stackScrollView = stackScrollView.get(), viewModel = placeholderViewModel, 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 50bae8a094ad..3ec14a23421c 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 @@ -37,6 +37,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.boundsInWindow +import androidx.compose.ui.layout.layoutId import androidx.compose.ui.layout.onPlaced import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp @@ -49,6 +50,7 @@ import com.android.systemui.brightness.ui.compose.BrightnessSliderContainer import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.dagger.SysUISingleton import com.android.systemui.lifecycle.rememberViewModel +import com.android.systemui.notifications.ui.composable.NotificationsShade import com.android.systemui.notifications.ui.composable.SnoozeableHeadsUpNotificationSpace import com.android.systemui.qs.composefragment.ui.GridAnchor import com.android.systemui.qs.flags.QsDetailedView @@ -61,8 +63,11 @@ import com.android.systemui.qs.ui.viewmodel.QuickSettingsShadeOverlayActionsView import com.android.systemui.qs.ui.viewmodel.QuickSettingsShadeOverlayContentViewModel import com.android.systemui.scene.shared.model.Overlays import com.android.systemui.scene.ui.composable.Overlay -import com.android.systemui.shade.ui.composable.CollapsedShadeHeader import com.android.systemui.shade.ui.composable.OverlayShade +import com.android.systemui.shade.ui.composable.OverlayShadeHeader +import com.android.systemui.shade.ui.composable.QuickSettingsOverlayHeader +import com.android.systemui.shade.ui.composable.SingleShadeMeasurePolicy +import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerStatusBarViewBinder import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView @@ -82,6 +87,8 @@ constructor( private val tintedIconManagerFactory: TintedIconManager.Factory, private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory, private val statusBarIconController: StatusBarIconController, + private val notificationIconContainerStatusBarViewBinder: + NotificationIconContainerStatusBarViewBinder, private val notificationStackScrollView: Lazy<NotificationScrollView>, private val notificationsPlaceholderViewModelFactory: NotificationsPlaceholderViewModel.Factory, ) : Overlay { @@ -108,43 +115,7 @@ constructor( // set the bounds to null when the QuickSettings overlay disappears DisposableEffect(Unit) { onDispose { viewModel.onPanelShapeChanged(null) } } - OverlayShade( - panelAlignment = Alignment.TopEnd, - modifier = modifier, - onScrimClicked = viewModel::onScrimClicked, - ) { - Column( - modifier = - Modifier.onPlaced { coordinates -> - val boundsInWindow = coordinates.boundsInWindow() - val shadeScrimBounds = - ShadeScrimBounds( - left = boundsInWindow.left, - top = boundsInWindow.top, - right = boundsInWindow.right, - bottom = boundsInWindow.bottom, - ) - val shape = - ShadeScrimShape( - bounds = shadeScrimBounds, - topRadius = 0, - bottomRadius = panelCornerRadius, - ) - viewModel.onPanelShapeChanged(shape) - } - ) { - if (viewModel.showHeader) { - CollapsedShadeHeader( - viewModelFactory = viewModel.shadeHeaderViewModelFactory, - createTintedIconManager = tintedIconManagerFactory::create, - createBatteryMeterViewController = - batteryMeterViewControllerFactory::create, - statusBarIconController = statusBarIconController, - ) - } - ShadeBody(viewModel = viewModel.quickSettingsContainerViewModel) - } - + Box(modifier = modifier) { SnoozeableHeadsUpNotificationSpace( stackScrollView = notificationStackScrollView.get(), viewModel = @@ -152,6 +123,58 @@ constructor( notificationsPlaceholderViewModelFactory.create() }, ) + OverlayShade( + isShadeLayoutWide = viewModel.isShadeLayoutWide, + panelAlignment = Alignment.TopEnd, + onScrimClicked = viewModel::onScrimClicked, + header = { + OverlayShadeHeader( + viewModelFactory = viewModel.shadeHeaderViewModelFactory, + createTintedIconManager = tintedIconManagerFactory::create, + createBatteryMeterViewController = + batteryMeterViewControllerFactory::create, + statusBarIconController = statusBarIconController, + notificationIconContainerStatusBarViewBinder = + notificationIconContainerStatusBarViewBinder, + modifier = + Modifier.element(NotificationsShade.Elements.StatusBar) + .layoutId(SingleShadeMeasurePolicy.LayoutId.ShadeHeader), + ) + }, + ) { + ShadeBody( + viewModel = viewModel.quickSettingsContainerViewModel, + modifier = + Modifier.onPlaced { coordinates -> + val boundsInWindow = coordinates.boundsInWindow() + val shadeScrimBounds = + ShadeScrimBounds( + left = boundsInWindow.left, + top = boundsInWindow.top, + right = boundsInWindow.right, + bottom = boundsInWindow.bottom, + ) + val shape = + ShadeScrimShape( + bounds = shadeScrimBounds, + topRadius = 0, + bottomRadius = panelCornerRadius, + ) + viewModel.onPanelShapeChanged(shape) + }, + header = { + if (viewModel.isShadeLayoutWide) { + QuickSettingsOverlayHeader( + viewModelFactory = viewModel.shadeHeaderViewModelFactory, + createBatteryMeterViewController = + batteryMeterViewControllerFactory::create, + modifier = + Modifier.padding(top = QuickSettingsShade.Dimensions.Padding), + ) + } + }, + ) + } } } } @@ -166,7 +189,11 @@ sealed interface ShadeBodyState { } @Composable -fun ContentScope.ShadeBody(viewModel: QuickSettingsContainerViewModel) { +fun ContentScope.ShadeBody( + viewModel: QuickSettingsContainerViewModel, + modifier: Modifier = Modifier, + header: @Composable () -> Unit, +) { val isEditing by viewModel.editModeViewModel.isEditing.collectAsStateWithLifecycle() val tileDetails = if (QsDetailedView.isEnabled) viewModel.detailsViewModel.activeTileDetails else null @@ -185,16 +212,19 @@ fun ContentScope.ShadeBody(viewModel: QuickSettingsContainerViewModel) { EditMode( viewModel = viewModel.editModeViewModel, modifier = - Modifier.fillMaxWidth().padding(QuickSettingsShade.Dimensions.Padding), + modifier.fillMaxWidth().padding(QuickSettingsShade.Dimensions.Padding), ) } + ShadeBodyState.TileDetails -> { - TileDetails(viewModel.detailsViewModel) + TileDetails(modifier = modifier, viewModel.detailsViewModel) } + else -> { QuickSettingsLayout( viewModel = viewModel, - modifier = Modifier.sysuiResTag("quick_settings_panel"), + modifier = modifier.sysuiResTag("quick_settings_panel"), + header = header, ) } } @@ -206,6 +236,7 @@ fun ContentScope.ShadeBody(viewModel: QuickSettingsContainerViewModel) { fun ContentScope.QuickSettingsLayout( viewModel: QuickSettingsContainerViewModel, modifier: Modifier = Modifier, + header: @Composable () -> Unit, ) { Column( verticalArrangement = Arrangement.spacedBy(QuickSettingsShade.Dimensions.Padding), @@ -217,6 +248,7 @@ fun ContentScope.QuickSettingsLayout( bottom = QuickSettingsShade.Dimensions.Padding, ), ) { + header() Toolbar( modifier = Modifier.fillMaxWidth().requiredHeight(QuickSettingsShade.Dimensions.ToolbarHeight), 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 0ca7a6af01e0..6c0c5c7e49b9 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 @@ -16,6 +16,7 @@ package com.android.systemui.scene.ui.composable +import androidx.compose.foundation.LocalOverscrollFactory import androidx.compose.foundation.gestures.awaitEachGesture import androidx.compose.foundation.gestures.awaitFirstDown import androidx.compose.foundation.layout.Box @@ -44,6 +45,7 @@ import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult import com.android.compose.animation.scene.observableTransitionState import com.android.compose.animation.scene.rememberMutableSceneTransitionLayoutState +import com.android.compose.gesture.effect.rememberOffsetOverscrollEffectFactory import com.android.systemui.lifecycle.rememberActivated import com.android.systemui.qs.ui.adapter.QSSceneAdapter import com.android.systemui.qs.ui.composable.QuickSettingsTheme @@ -52,6 +54,7 @@ import com.android.systemui.scene.shared.model.SceneDataSourceDelegator import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.ui.view.SceneJankMonitor import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel +import com.android.systemui.shade.ui.composable.isFullWidthShade import javax.inject.Provider /** @@ -150,6 +153,13 @@ fun SceneContainer( } } + // Overlays use the offset overscroll effect when shown on large screens, otherwise they + // stretch. All scenes use the OffsetOverscrollEffect. + val offsetOverscrollEffectFactory = rememberOffsetOverscrollEffectFactory() + val stretchOverscrollEffectFactory = checkNotNull(LocalOverscrollFactory.current) + val overlayEffectFactory = + if (isFullWidthShade()) stretchOverscrollEffectFactory else offsetOverscrollEffectFactory + // Inflate qsView here so that shade has the correct qqs height in the first measure pass after // rebooting if ( @@ -192,6 +202,7 @@ fun SceneContainer( scene( key = sceneKey, userActions = userActionsByContentKey.getOrDefault(sceneKey, emptyMap()), + effectFactory = offsetOverscrollEffectFactory, ) { // Activate the scene. LaunchedEffect(scene) { scene.activate() } @@ -208,6 +219,7 @@ fun SceneContainer( overlay( key = overlayKey, userActions = userActionsByContentKey.getOrDefault(overlayKey, emptyMap()), + effectFactory = overlayEffectFactory, ) { // Activate the overlay. LaunchedEffect(overlay) { overlay.activate() } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt index 019639da48c5..b4c60037b426 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt @@ -17,8 +17,11 @@ package com.android.systemui.scene.ui.composable.transitions import androidx.compose.animation.core.tween +import com.android.compose.animation.scene.ContentKey import com.android.compose.animation.scene.Edge import com.android.compose.animation.scene.TransitionBuilder +import com.android.compose.animation.scene.UserActionDistance +import com.android.compose.animation.scene.UserActionDistanceScope import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys import com.android.systemui.notifications.ui.composable.Notifications import com.android.systemui.notifications.ui.composable.NotificationsShade @@ -28,6 +31,10 @@ import kotlin.time.Duration.Companion.milliseconds fun TransitionBuilder.toNotificationsShadeTransition(durationScale: Double = 1.0) { spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt()) + distance = UserActionDistance { _, shadeContentKey, _ -> + calculateShadePanelTargetPositionY(shadeContentKey) + } + // Ensure the clock isn't clipped by the shade outline during the transition from lockscreen. sharedElement( ClockElementKeys.smallClockElementKey, @@ -43,4 +50,12 @@ fun TransitionBuilder.toNotificationsShadeTransition(durationScale: Double = 1.0 fractionRange(start = .5f) { fade(Notifications.Elements.NotificationScrim) } } +/** Returns the Y position of the bottom of the shade container panel within [shadeOverlayKey]. */ +fun UserActionDistanceScope.calculateShadePanelTargetPositionY(shadeOverlayKey: ContentKey): Float { + val marginTop = OverlayShade.Elements.Panel.targetOffset(shadeOverlayKey)?.y ?: 0f + val panelHeight = + OverlayShade.Elements.Panel.targetSize(shadeOverlayKey)?.height?.toFloat() ?: 0f + return marginTop + panelHeight +} + private val DefaultDuration = 300.milliseconds diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt index faccf14767b5..c9fbb4da9ffb 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt @@ -25,9 +25,8 @@ import kotlin.time.Duration.Companion.milliseconds fun TransitionBuilder.toQuickSettingsShadeTransition(durationScale: Double = 1.0) { spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt()) - distance = UserActionDistance { fromContent, _, _ -> - val fromContentSize = checkNotNull(fromContent.targetSize()) - fromContentSize.height.toFloat() * 2 / 3f + distance = UserActionDistance { _, shadeContentKey, _ -> + calculateShadePanelTargetPositionY(shadeContentKey) } translate(OverlayShade.Elements.Panel, Edge.Top) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt index ffdf509174d5..fc59d40ec443 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt @@ -20,10 +20,8 @@ package com.android.systemui.shade.ui.composable import androidx.compose.foundation.background import androidx.compose.foundation.clickable -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.Column import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Spacer @@ -44,59 +42,47 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass import androidx.compose.runtime.Composable import androidx.compose.runtime.ReadOnlyComposable -import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color -import androidx.compose.ui.input.nestedscroll.NestedScrollConnection -import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.dimensionResource -import androidx.compose.ui.unit.Velocity import androidx.compose.ui.unit.dp import com.android.compose.animation.scene.ContentScope import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.LowestZIndexContentPicker -import com.android.compose.gesture.effect.rememberOffsetOverscrollEffect import com.android.compose.windowsizeclass.LocalWindowSizeClass import com.android.systemui.res.R /** Renders a lightweight shade UI container, as an overlay. */ @Composable fun ContentScope.OverlayShade( + isShadeLayoutWide: Boolean, panelAlignment: Alignment, onScrimClicked: () -> Unit, modifier: Modifier = Modifier, + header: @Composable () -> Unit, content: @Composable () -> Unit, ) { - // TODO(b/384653288) This should be removed when b/378470603 is done. - val idleEffect = rememberOffsetOverscrollEffect(Orientation.Vertical) - Box( - modifier - .overscroll(idleEffect) - .nestedScroll( - remember { - object : NestedScrollConnection { - override suspend fun onPreFling(available: Velocity): Velocity { - return available - } - } - } - ) - .scrollable(rememberScrollableState { 0f }, Orientation.Vertical, idleEffect) - ) { + Box(modifier) { Scrim(onClicked = onScrimClicked) Box(modifier = Modifier.fillMaxSize().panelPadding(), contentAlignment = panelAlignment) { Panel( + isShadeLayoutWide = isShadeLayoutWide, modifier = - Modifier.element(OverlayShade.Elements.Panel) - .overscroll(verticalOverscrollEffect) + Modifier.overscroll(verticalOverscrollEffect) + .element(OverlayShade.Elements.Panel) .panelSize(), + header = header, content = content, ) } + + if (isShadeLayoutWide) { + header() + } } } @@ -113,7 +99,12 @@ private fun ContentScope.Scrim(onClicked: () -> Unit, modifier: Modifier = Modif } @Composable -private fun ContentScope.Panel(modifier: Modifier = Modifier, content: @Composable () -> Unit) { +private fun ContentScope.Panel( + isShadeLayoutWide: Boolean, + modifier: Modifier = Modifier, + header: @Composable () -> Unit, + content: @Composable () -> Unit, +) { Box(modifier = modifier.clip(OverlayShade.Shapes.RoundedCornerPanel)) { Spacer( modifier = @@ -125,17 +116,22 @@ private fun ContentScope.Panel(modifier: Modifier = Modifier, content: @Composab ) ) - // This content is intentionally rendered as a separate element from the background in order - // to allow for more flexibility when defining transitions. - content() + Column { + if (!isShadeLayoutWide) { + header() + } + + // This content is intentionally rendered as a separate element from the background in + // order to allow for more flexibility when defining transitions. + content() + } } } @Composable private fun Modifier.panelSize(): Modifier { - val widthSizeClass = LocalWindowSizeClass.current.widthSizeClass return this.then( - if (widthSizeClass == WindowWidthSizeClass.Compact) { + if (isFullWidthShade()) { Modifier.fillMaxWidth() } else { Modifier.width(dimensionResource(id = R.dimen.shade_panel_width)) @@ -144,6 +140,12 @@ private fun Modifier.panelSize(): Modifier { } @Composable +@ReadOnlyComposable +internal fun isFullWidthShade(): Boolean { + return LocalWindowSizeClass.current.widthSizeClass == WindowWidthSizeClass.Compact +} + +@Composable private fun Modifier.panelPadding(): Modifier { val widthSizeClass = LocalWindowSizeClass.current.widthSizeClass val systemBars = WindowInsets.systemBarsIgnoringVisibility diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt index 3131b539c6af..c5d28adce601 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt @@ -26,6 +26,7 @@ import androidx.compose.foundation.interaction.collectIsHoveredAsState import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.Spacer @@ -38,11 +39,11 @@ import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.ColorScheme import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -50,6 +51,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.TransformOrigin import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.layout.Layout +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.Constraints @@ -65,7 +67,6 @@ import com.android.compose.animation.scene.ValueKey import com.android.compose.animation.scene.animateElementFloatAsState import com.android.compose.animation.scene.content.state.TransitionState import com.android.compose.modifiers.thenIf -import com.android.compose.windowsizeclass.LocalWindowSizeClass import com.android.settingslib.Utils import com.android.systemui.battery.BatteryMeterView import com.android.systemui.battery.BatteryMeterViewController @@ -77,10 +78,15 @@ import com.android.systemui.lifecycle.rememberViewModel import com.android.systemui.privacy.OngoingPrivacyChip import com.android.systemui.res.R import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.shade.shared.flag.DualShade +import com.android.systemui.shade.ui.composable.ShadeHeader.Colors.chipBackground +import com.android.systemui.shade.ui.composable.ShadeHeader.Colors.chipHighlighted import com.android.systemui.shade.ui.composable.ShadeHeader.Colors.onScrimDim import com.android.systemui.shade.ui.composable.ShadeHeader.Dimensions.CollapsedHeight import com.android.systemui.shade.ui.composable.ShadeHeader.Values.ClockScale import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel +import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerStatusBarViewBinder +import com.android.systemui.statusbar.phone.NotificationIconContainer import com.android.systemui.statusbar.phone.StatusBarLocation import com.android.systemui.statusbar.phone.StatusIconContainer import com.android.systemui.statusbar.phone.ui.StatusBarIconController @@ -88,6 +94,7 @@ import com.android.systemui.statusbar.phone.ui.TintedIconManager import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernShadeCarrierGroupMobileView import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.ShadeCarrierGroupMobileIconViewModel import com.android.systemui.statusbar.policy.Clock +import kotlinx.coroutines.launch object ShadeHeader { object Elements { @@ -114,6 +121,12 @@ object ShadeHeader { val ColorScheme.onScrimDim: Color get() = Color.DarkGray + + val ColorScheme.chipBackground: Color + get() = Color.DarkGray + + val ColorScheme.chipHighlighted: Color + get() = Color.LightGray } object TestTags { @@ -121,6 +134,7 @@ object ShadeHeader { } } +/** The status bar that appears above the Shade scene on small screens */ @Composable fun ContentScope.CollapsedShadeHeader( viewModelFactory: ShadeHeaderViewModel.Factory, @@ -131,9 +145,6 @@ fun ContentScope.CollapsedShadeHeader( ) { val viewModel = rememberViewModel("CollapsedShadeHeader") { viewModelFactory.create() } - val cutoutWidth = LocalDisplayCutout.current.width() - val cutoutHeight = LocalDisplayCutout.current.height() - val cutoutTop = LocalDisplayCutout.current.top val cutoutLocation = LocalDisplayCutout.current.location val horizontalPadding = max(LocalScreenCornerRadius.current / 2f, Shade.Dimensions.HorizontalPadding) @@ -146,123 +157,73 @@ fun ContentScope.CollapsedShadeHeader( } } - val isLargeScreenLayout = - LocalWindowSizeClass.current.widthSizeClass == WindowWidthSizeClass.Medium || - LocalWindowSizeClass.current.widthSizeClass == WindowWidthSizeClass.Expanded + val isShadeLayoutWide = viewModel.isShadeLayoutWide val isPrivacyChipVisible by viewModel.isPrivacyChipVisible.collectAsStateWithLifecycle() // This layout assumes it is globally positioned at (0, 0) and is the // same size as the screen. - Layout( + CutoutAwareShadeHeader( modifier = modifier.sysuiResTag(ShadeHeader.TestTags.Root), - contents = - listOf( - { - Row(modifier = Modifier.padding(horizontal = horizontalPadding)) { - Clock( - scale = 1f, + startContent = { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(5.dp), + modifier = Modifier.padding(horizontal = horizontalPadding), + ) { + Clock(scale = 1f, viewModel = viewModel) + VariableDayDate( + viewModel = viewModel, + modifier = Modifier.element(ShadeHeader.Elements.CollapsedContentStart), + ) + } + }, + endContent = { + if (isPrivacyChipVisible) { + Box( + modifier = + Modifier.height(CollapsedHeight) + .fillMaxWidth() + .padding(horizontal = horizontalPadding) + ) { + PrivacyChip( + viewModel = viewModel, + modifier = Modifier.align(Alignment.CenterEnd), + ) + } + } else { + Row( + horizontalArrangement = Arrangement.End, + verticalAlignment = Alignment.CenterVertically, + modifier = + Modifier.element(ShadeHeader.Elements.CollapsedContentEnd) + .padding(horizontal = horizontalPadding), + ) { + if (isShadeLayoutWide) { + ShadeCarrierGroup(viewModel = viewModel) + } + SystemIconChip(viewModel = viewModel, isClickable = isShadeLayoutWide) { + StatusIcons( viewModel = viewModel, - modifier = Modifier.align(Alignment.CenterVertically), + createTintedIconManager = createTintedIconManager, + statusBarIconController = statusBarIconController, + useExpandedFormat = useExpandedTextFormat, + modifier = Modifier.padding(end = 6.dp).weight(1f, fill = false), ) - Spacer(modifier = Modifier.width(5.dp)) - VariableDayDate( + BatteryIcon( viewModel = viewModel, - modifier = - Modifier.element(ShadeHeader.Elements.CollapsedContentStart) - .align(Alignment.CenterVertically), + createBatteryMeterViewController = createBatteryMeterViewController, + useExpandedFormat = useExpandedTextFormat, + modifier = Modifier.padding(vertical = 8.dp), ) } - }, - { - if (isPrivacyChipVisible) { - Box( - modifier = - Modifier.height(CollapsedHeight) - .fillMaxWidth() - .padding(horizontal = horizontalPadding) - ) { - PrivacyChip( - viewModel = viewModel, - modifier = Modifier.align(Alignment.CenterEnd), - ) - } - } else { - Row( - horizontalArrangement = Arrangement.End, - modifier = - Modifier.element(ShadeHeader.Elements.CollapsedContentEnd) - .padding(horizontal = horizontalPadding), - ) { - if (isLargeScreenLayout) { - ShadeCarrierGroup( - viewModel = viewModel, - modifier = Modifier.align(Alignment.CenterVertically), - ) - } - SystemIconContainer( - viewModel = viewModel, - isClickable = isLargeScreenLayout, - modifier = Modifier.align(Alignment.CenterVertically), - ) { - StatusIcons( - viewModel = viewModel, - createTintedIconManager = createTintedIconManager, - statusBarIconController = statusBarIconController, - useExpandedFormat = useExpandedTextFormat, - modifier = - Modifier.align(Alignment.CenterVertically) - .padding(end = 6.dp) - .weight(1f, fill = false), - ) - BatteryIcon( - createBatteryMeterViewController = - createBatteryMeterViewController, - useExpandedFormat = useExpandedTextFormat, - modifier = Modifier.align(Alignment.CenterVertically), - ) - } - } - } - }, - ), - ) { measurables, constraints -> - check(constraints.hasBoundedWidth) - check(measurables.size == 2) - check(measurables[0].size == 1) - check(measurables[1].size == 1) - - val screenWidth = constraints.maxWidth - val cutoutWidthPx = cutoutWidth.roundToPx() - val height = max(cutoutHeight + (cutoutTop * 2), CollapsedHeight).roundToPx() - val childConstraints = Constraints.fixed((screenWidth - cutoutWidthPx) / 2, height) - - val startMeasurable = measurables[0][0] - val endMeasurable = measurables[1][0] - - val startPlaceable = startMeasurable.measure(childConstraints) - val endPlaceable = endMeasurable.measure(childConstraints) - - layout(screenWidth, height) { - when (cutoutLocation) { - CutoutLocation.NONE, - CutoutLocation.RIGHT -> { - startPlaceable.placeRelative(x = 0, y = 0) - endPlaceable.placeRelative(x = startPlaceable.width, y = 0) - } - CutoutLocation.CENTER -> { - startPlaceable.placeRelative(x = 0, y = 0) - endPlaceable.placeRelative(x = startPlaceable.width + cutoutWidthPx, y = 0) - } - CutoutLocation.LEFT -> { - startPlaceable.placeRelative(x = cutoutWidthPx, y = 0) - endPlaceable.placeRelative(x = startPlaceable.width + cutoutWidthPx, y = 0) } } - } - } + }, + ) } +/** The status bar that appears above the Quick Settings scene on small screens */ @Composable fun ContentScope.ExpandedShadeHeader( viewModelFactory: ShadeHeaderViewModel.Factory, @@ -310,27 +271,24 @@ fun ContentScope.ExpandedShadeHeader( } } Spacer(modifier = Modifier.width(5.dp)) - Row(modifier = Modifier.element(ShadeHeader.Elements.ExpandedContent)) { - VariableDayDate( - viewModel = viewModel, - modifier = Modifier.widthIn(max = 90.dp).align(Alignment.CenterVertically), - ) + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.element(ShadeHeader.Elements.ExpandedContent), + ) { + VariableDayDate(viewModel = viewModel, modifier = Modifier.widthIn(max = 90.dp)) Spacer(modifier = Modifier.weight(1f)) - SystemIconContainer(viewModel = viewModel, isClickable = false) { + SystemIconChip(viewModel = viewModel) { StatusIcons( viewModel = viewModel, createTintedIconManager = createTintedIconManager, statusBarIconController = statusBarIconController, useExpandedFormat = useExpandedFormat, - modifier = - Modifier.align(Alignment.CenterVertically) - .padding(end = 6.dp) - .weight(1f, fill = false), + modifier = Modifier.padding(end = 6.dp).weight(1f, fill = false), ) BatteryIcon( + viewModel = viewModel, useExpandedFormat = useExpandedFormat, createBatteryMeterViewController = createBatteryMeterViewController, - modifier = Modifier.align(Alignment.CenterVertically), ) } } @@ -338,8 +296,183 @@ fun ContentScope.ExpandedShadeHeader( } } +/** + * The status bar that appears above both the Notifications and Quick Settings shade overlays when + * overlay shade is enabled. + */ @Composable -private fun ContentScope.Clock(scale: Float, viewModel: ShadeHeaderViewModel, modifier: Modifier) { +fun ContentScope.OverlayShadeHeader( + viewModelFactory: ShadeHeaderViewModel.Factory, + createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager, + createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController, + statusBarIconController: StatusBarIconController, + notificationIconContainerStatusBarViewBinder: NotificationIconContainerStatusBarViewBinder, + modifier: Modifier = Modifier, +) { + val viewModel = rememberViewModel("OverlayShadeHeader") { viewModelFactory.create() } + + val horizontalPadding = + max(LocalScreenCornerRadius.current / 2f, Shade.Dimensions.HorizontalPadding) + + val isShadeLayoutWide = viewModel.isShadeLayoutWide + + val isPrivacyChipVisible by viewModel.isPrivacyChipVisible.collectAsStateWithLifecycle() + + // This layout assumes it is globally positioned at (0, 0) and is the + // same size as the screen. + CutoutAwareShadeHeader( + modifier = modifier.sysuiResTag(ShadeHeader.TestTags.Root), + startContent = { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.padding(horizontal = horizontalPadding), + ) { + if (isShadeLayoutWide) { + Clock( + scale = 1f, + viewModel = viewModel, + modifier = Modifier.padding(horizontal = 4.dp), + ) + Spacer(modifier = Modifier.width(5.dp)) + } + NotificationIconChip(viewModel = viewModel) { + if (isShadeLayoutWide) { + NotificationIcons( + viewModel = viewModel, + notificationIconContainerStatusBarViewBinder = + notificationIconContainerStatusBarViewBinder, + modifier = Modifier.width(IntrinsicSize.Min).height(20.dp), + ) + } else { + VariableDayDate(viewModel = viewModel) + } + } + } + }, + endContent = { + Row( + horizontalArrangement = Arrangement.End, + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.padding(horizontal = horizontalPadding), + ) { + SystemIconChip(viewModel = viewModel, isClickable = true, showBackground = true) { + StatusIcons( + viewModel = viewModel, + createTintedIconManager = createTintedIconManager, + statusBarIconController = statusBarIconController, + useExpandedFormat = false, + highlightable = true, + modifier = Modifier.padding(end = 6.dp).weight(1f, fill = false), + ) + BatteryIcon( + viewModel = viewModel, + createBatteryMeterViewController = createBatteryMeterViewController, + useExpandedFormat = false, + highlightable = true, + ) + } + if (isPrivacyChipVisible) { + Box( + modifier = + Modifier.height(CollapsedHeight) + .fillMaxWidth() + .padding(horizontal = horizontalPadding) + ) { + PrivacyChip( + viewModel = viewModel, + modifier = Modifier.align(Alignment.CenterEnd), + ) + } + } + } + }, + ) +} + +/** The header that appears at the top of the Quick Settings shade overlay. */ +@Composable +fun ContentScope.QuickSettingsOverlayHeader( + viewModelFactory: ShadeHeaderViewModel.Factory, + createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController, + modifier: Modifier = Modifier, +) { + val viewModel = rememberViewModel("QuickSettingsOverlayHeader") { viewModelFactory.create() } + + Row( + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + modifier = modifier.fillMaxWidth(), + ) { + ShadeCarrierGroup(viewModel = viewModel) + BatteryIcon( + viewModel = viewModel, + createBatteryMeterViewController = createBatteryMeterViewController, + useExpandedFormat = true, + ) + } +} + +/* + * Places startContent and endContent according to the location of the display cutout. + * Assumes it is globally positioned at (0, 0) and the same size as the screen. + */ +@Composable +private fun CutoutAwareShadeHeader( + modifier: Modifier = Modifier, + startContent: @Composable () -> Unit, + endContent: @Composable () -> Unit, +) { + val cutoutWidth = LocalDisplayCutout.current.width() + val cutoutHeight = LocalDisplayCutout.current.height() + val cutoutTop = LocalDisplayCutout.current.top + val cutoutLocation = LocalDisplayCutout.current.location + + Layout( + modifier = modifier.sysuiResTag(ShadeHeader.TestTags.Root), + contents = listOf(startContent, endContent), + ) { measurables, constraints -> + check(constraints.hasBoundedWidth) + check(measurables.size == 2) + check(measurables[0].size == 1) + check(measurables[1].size == 1) + + val screenWidth = constraints.maxWidth + val cutoutWidthPx = cutoutWidth.roundToPx() + val height = max(cutoutHeight + (cutoutTop * 2), CollapsedHeight).roundToPx() + val childConstraints = Constraints.fixed((screenWidth - cutoutWidthPx) / 2, height) + + val startMeasurable = measurables[0][0] + val endMeasurable = measurables[1][0] + + val startPlaceable = startMeasurable.measure(childConstraints) + val endPlaceable = endMeasurable.measure(childConstraints) + + layout(screenWidth, height) { + when (cutoutLocation) { + CutoutLocation.NONE, + CutoutLocation.RIGHT -> { + startPlaceable.placeRelative(x = 0, y = 0) + endPlaceable.placeRelative(x = startPlaceable.width, y = 0) + } + CutoutLocation.CENTER -> { + startPlaceable.placeRelative(x = 0, y = 0) + endPlaceable.placeRelative(x = startPlaceable.width + cutoutWidthPx, y = 0) + } + CutoutLocation.LEFT -> { + startPlaceable.placeRelative(x = cutoutWidthPx, y = 0) + endPlaceable.placeRelative(x = startPlaceable.width + cutoutWidthPx, y = 0) + } + } + } + } +} + +@Composable +private fun ContentScope.Clock( + scale: Float, + viewModel: ShadeHeaderViewModel, + modifier: Modifier = Modifier, +) { val layoutDirection = LocalLayoutDirection.current Element(key = ShadeHeader.Elements.Clock, modifier = modifier) { @@ -374,28 +507,31 @@ private fun ContentScope.Clock(scale: Float, viewModel: ShadeHeaderViewModel, mo @Composable private fun BatteryIcon( + viewModel: ShadeHeaderViewModel, createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController, useExpandedFormat: Boolean, + highlightable: Boolean = false, modifier: Modifier = Modifier, ) { + val localContext = LocalContext.current + val themedContext = + ContextThemeWrapper(localContext, R.style.Theme_SystemUI_QuickSettings_Header) + val primaryColor = + Utils.getColorAttrDefaultColor(themedContext, android.R.attr.textColorPrimary) + val inverseColor = + Utils.getColorAttrDefaultColor(themedContext, android.R.attr.textColorPrimaryInverse) + + val isHighlighted = viewModel.highlightQuickSettingsIcons + AndroidView( factory = { context -> val batteryIcon = BatteryMeterView(context, null) batteryIcon.setPercentShowMode(BatteryMeterView.MODE_ON) - val themedContext = - ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings_Header) - val fg = Utils.getColorAttrDefaultColor(themedContext, android.R.attr.textColorPrimary) - val bg = - Utils.getColorAttrDefaultColor( - themedContext, - android.R.attr.textColorPrimaryInverse, - ) - // [BatteryMeterView.updateColors] is an old method that was built to distinguish // between dual-tone colors and single-tone. The current icon is only single-tone, so // the final [fg] is the only one we actually need - batteryIcon.updateColors(fg, bg, fg) + batteryIcon.updateColors(primaryColor, inverseColor, primaryColor) val batteryMaterViewController = createBatteryMeterViewController(batteryIcon, StatusBarLocation.QS) @@ -414,6 +550,13 @@ private fun BatteryIcon( BatteryMeterView.MODE_ON } ) + if (highlightable) { + if (isHighlighted) { + batteryIcon.updateColors(primaryColor, inverseColor, inverseColor) + } else { + batteryIcon.updateColors(primaryColor, inverseColor, primaryColor) + } + } }, modifier = modifier, ) @@ -446,13 +589,51 @@ private fun ShadeCarrierGroup(viewModel: ShadeHeaderViewModel, modifier: Modifie } @Composable +private fun NotificationIcons( + viewModel: ShadeHeaderViewModel, + notificationIconContainerStatusBarViewBinder: NotificationIconContainerStatusBarViewBinder, + modifier: Modifier = Modifier, +) { + val scope = rememberCoroutineScope() + + val isHighlighted = viewModel.highlightNotificationIcons + + AndroidView( + factory = { context -> + NotificationIconContainer(context, null).also { view -> + view.setOverrideIconColor(true) + scope.launch { + notificationIconContainerStatusBarViewBinder.bindWhileAttached( + view = view, + displayId = context.displayId, + ) + } + } + }, + update = { it.setUseInverseOverrideIconColor(isHighlighted) }, + modifier = modifier, + ) +} + +@Composable private fun ContentScope.StatusIcons( viewModel: ShadeHeaderViewModel, createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager, statusBarIconController: StatusBarIconController, useExpandedFormat: Boolean, + highlightable: Boolean = false, modifier: Modifier = Modifier, ) { + val localContext = LocalContext.current + val themedContext = + ContextThemeWrapper(localContext, R.style.Theme_SystemUI_QuickSettings_Header) + val primaryColor = + Utils.getColorAttrDefaultColor(themedContext, android.R.attr.textColorPrimary) + val inverseColor = + Utils.getColorAttrDefaultColor(themedContext, android.R.attr.textColorPrimaryInverse) + + val isHighlighted = viewModel.highlightQuickSettingsIcons + val carrierIconSlots = listOf(stringResource(id = com.android.internal.R.string.status_bar_mobile)) val cameraSlot = stringResource(id = com.android.internal.R.string.status_bar_camera) @@ -466,19 +647,12 @@ private fun ContentScope.StatusIcons( val isLocationIndicationEnabled by viewModel.isLocationIndicationEnabled.collectAsStateWithLifecycle() + val iconContainer = remember { StatusIconContainer(themedContext, null) } + val iconManager = remember { createTintedIconManager(iconContainer, StatusBarLocation.QS) } + AndroidView( factory = { context -> - val themedContext = - ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings_Header) - val iconContainer = StatusIconContainer(themedContext, null) - val iconManager = createTintedIconManager(iconContainer, StatusBarLocation.QS) - iconManager.setTint( - Utils.getColorAttrDefaultColor(themedContext, android.R.attr.textColorPrimary), - Utils.getColorAttrDefaultColor( - themedContext, - android.R.attr.textColorPrimaryInverse, - ), - ) + iconManager.setTint(primaryColor, inverseColor) statusBarIconController.addIconGroup(iconManager) iconContainer @@ -511,35 +685,87 @@ private fun ContentScope.StatusIcons( iconContainer.removeIgnoredSlot(micSlot) iconContainer.removeIgnoredSlot(locationSlot) } + + if (highlightable) { + if (isHighlighted) { + iconManager.setTint(inverseColor, primaryColor) + } else { + iconManager.setTint(primaryColor, inverseColor) + } + } }, modifier = modifier, ) } @Composable -private fun SystemIconContainer( +private fun NotificationIconChip( viewModel: ShadeHeaderViewModel, - isClickable: Boolean, modifier: Modifier = Modifier, content: @Composable RowScope.() -> Unit, ) { val interactionSource = remember { MutableInteractionSource() } - val isHovered by interactionSource.collectIsHoveredAsState() + val backgroundColor = + if (viewModel.highlightNotificationIcons) MaterialTheme.colorScheme.chipHighlighted + else MaterialTheme.colorScheme.chipBackground + Box(modifier = modifier) { + Row( + modifier = + Modifier.align(Alignment.CenterStart) + .clickable( + interactionSource = interactionSource, + indication = null, + onClick = { viewModel.onNotificationIconChipClicked() }, + ) + .thenIf(DualShade.isEnabled) { + Modifier.graphicsLayer { + shape = RoundedCornerShape(25.dp) + clip = true + } + .background(backgroundColor) + .padding(horizontal = 8.dp, vertical = 4.dp) + } + ) { + content() + } + } +} + +@Composable +private fun SystemIconChip( + viewModel: ShadeHeaderViewModel, + isClickable: Boolean = false, + showBackground: Boolean = false, + modifier: Modifier = Modifier, + content: @Composable RowScope.() -> Unit, +) { + val interactionSource = remember { MutableInteractionSource() } + val isHovered by interactionSource.collectIsHoveredAsState() val hoverModifier = Modifier.clip(RoundedCornerShape(CollapsedHeight / 4)) .background(MaterialTheme.colorScheme.onScrimDim) + val backgroundColor = + if (viewModel.highlightQuickSettingsIcons) MaterialTheme.colorScheme.chipHighlighted + else MaterialTheme.colorScheme.chipBackground Row( + verticalAlignment = Alignment.CenterVertically, modifier = modifier - .height(CollapsedHeight) - .padding(vertical = CollapsedHeight / 4) + .thenIf(showBackground) { + Modifier.graphicsLayer { + shape = RoundedCornerShape(25.dp) + clip = true + } + .background(backgroundColor) + .padding(horizontal = 8.dp, vertical = 4.dp) + } .thenIf(isClickable) { Modifier.clickable( interactionSource = interactionSource, indication = null, - onClick = { viewModel.onSystemIconContainerClicked() }, + onClick = { viewModel.onSystemIconChipClicked() }, ) } .thenIf(isHovered) { hoverModifier }, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/VariableDayDate.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/VariableDayDate.kt index 931ff56e56cb..93eca86e15cf 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/VariableDayDate.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/VariableDayDate.kt @@ -6,17 +6,18 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.layout.Layout import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.android.systemui.shade.ui.composable.ShadeHeader.Colors.shadeHeaderText +import com.android.compose.theme.colorAttr import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel @Composable -fun VariableDayDate( - viewModel: ShadeHeaderViewModel, - modifier: Modifier = Modifier, -) { +fun VariableDayDate(viewModel: ShadeHeaderViewModel, modifier: Modifier = Modifier) { val longerText = viewModel.longerDateText.collectAsStateWithLifecycle() val shorterText = viewModel.shorterDateText.collectAsStateWithLifecycle() + val textColor = + if (viewModel.highlightNotificationIcons) colorAttr(android.R.attr.textColorPrimaryInverse) + else colorAttr(android.R.attr.textColorPrimary) + Layout( contents = listOf( @@ -24,7 +25,7 @@ fun VariableDayDate( Text( text = longerText.value, style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.shadeHeaderText, + color = textColor, maxLines = 1, ) }, @@ -32,7 +33,7 @@ fun VariableDayDate( Text( text = shorterText.value, style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.shadeHeaderText, + color = textColor, maxLines = 1, ) }, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt index f1cc71bc59af..c73656eb1ec5 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModule.kt @@ -16,9 +16,9 @@ package com.android.systemui.volume.panel.component.mediaoutput +import com.android.systemui.volume.panel.component.mediaoutput.domain.MediaOutputAvailabilityCriteria import com.android.systemui.volume.panel.component.mediaoutput.ui.composable.MediaOutputComponent import com.android.systemui.volume.panel.component.shared.model.VolumePanelComponents -import com.android.systemui.volume.panel.domain.AlwaysAvailableCriteria import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria import com.android.systemui.volume.panel.shared.model.VolumePanelUiComponent import dagger.Binds @@ -39,6 +39,6 @@ interface MediaOutputModule { @IntoMap @StringKey(VolumePanelComponents.MEDIA_OUTPUT) fun bindComponentAvailabilityCriteria( - criteria: AlwaysAvailableCriteria + criteria: MediaOutputAvailabilityCriteria ): ComponentAvailabilityCriteria } 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 bdd0da9ce4a4..4e10ff689b19 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 @@ -67,11 +67,10 @@ 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 -import com.android.systemui.haptics.slider.SeekableSliderTrackerConfig -import com.android.systemui.haptics.slider.SliderHapticFeedbackConfig import com.android.systemui.haptics.slider.compose.ui.SliderHapticsViewModel import com.android.systemui.lifecycle.rememberViewModel import com.android.systemui.res.R +import com.android.systemui.volume.haptics.ui.VolumeHapticsConfigsProvider import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.SliderState import kotlin.math.round import kotlinx.coroutines.flow.distinctUntilChanged @@ -103,6 +102,10 @@ fun VolumeSlider( } val value by valueState(state) + val interactionSource = remember { MutableInteractionSource() } + val hapticsViewModel: SliderHapticsViewModel? = + setUpHapticsViewModel(value, state.valueRange, interactionSource, hapticsViewModelFactory) + Column(modifier = modifier.animateContentSize(), verticalArrangement = Arrangement.Top) { Row( horizontalArrangement = Arrangement.spacedBy(12.dp), @@ -127,8 +130,14 @@ fun VolumeSlider( Slider( value = value, valueRange = state.valueRange, - onValueChange = onValueChange, - onValueChangeFinished = onValueChangeFinished, + onValueChange = { newValue -> + hapticsViewModel?.addVelocityDataPoint(newValue) + onValueChange(newValue) + }, + onValueChangeFinished = { + hapticsViewModel?.onValueChangeEnded() + onValueChangeFinished?.invoke() + }, enabled = state.isEnabled, modifier = Modifier.height(40.dp) @@ -210,41 +219,8 @@ private fun LegacyVolumeSlider( ) { val value by valueState(state) val interactionSource = remember { MutableInteractionSource() } - val sliderStepSize = 1f / (state.valueRange.endInclusive - state.valueRange.start) val hapticsViewModel: SliderHapticsViewModel? = - hapticsViewModelFactory?.let { - rememberViewModel(traceName = "SliderHapticsViewModel") { - it.create( - interactionSource, - state.valueRange, - Orientation.Horizontal, - SliderHapticFeedbackConfig( - lowerBookendScale = 0.2f, - progressBasedDragMinScale = 0.2f, - progressBasedDragMaxScale = 0.5f, - deltaProgressForDragThreshold = 0f, - additionalVelocityMaxBump = 0.2f, - maxVelocityToScale = 0.1f, /* slider progress(from 0 to 1) per sec */ - sliderStepSize = sliderStepSize, - ), - SeekableSliderTrackerConfig( - lowerBookendThreshold = 0f, - upperBookendThreshold = 1f, - ), - ) - } - } - var lastDiscreteStep by remember { mutableFloatStateOf(round(value)) } - LaunchedEffect(value) { - snapshotFlow { value } - .map { round(it) } - .filter { it != lastDiscreteStep } - .distinctUntilChanged() - .collect { discreteStep -> - lastDiscreteStep = discreteStep - hapticsViewModel?.onValueChange(discreteStep) - } - } + setUpHapticsViewModel(value, state.valueRange, interactionSource, hapticsViewModelFactory) PlatformSlider( modifier = @@ -357,3 +333,36 @@ private fun SliderIcon( content = { Icon(modifier = Modifier.size(24.dp), icon = icon) }, ) } + +@Composable +fun setUpHapticsViewModel( + value: Float, + valueRange: ClosedFloatingPointRange<Float>, + interactionSource: MutableInteractionSource, + hapticsViewModelFactory: SliderHapticsViewModel.Factory?, +): SliderHapticsViewModel? { + return hapticsViewModelFactory?.let { + rememberViewModel(traceName = "SliderHapticsViewModel") { + it.create( + interactionSource, + valueRange, + Orientation.Horizontal, + VolumeHapticsConfigsProvider.sliderHapticFeedbackConfig(valueRange), + VolumeHapticsConfigsProvider.seekableSliderTrackerConfig, + ) + } + .also { hapticsViewModel -> + var lastDiscreteStep by remember { mutableFloatStateOf(round(value)) } + LaunchedEffect(value) { + snapshotFlow { value } + .map { round(it) } + .filter { it != lastDiscreteStep } + .distinctUntilChanged() + .collect { discreteStep -> + lastDiscreteStep = discreteStep + hapticsViewModel.onValueChange(discreteStep) + } + } + } + } +} 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 916d85a80e77..70ff47baa7a9 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 @@ -92,6 +92,10 @@ internal class DraggableHandler( else -> null } ?: return NoOpDragController + if (result is UserActionResult.ShowOverlay) { + layoutImpl.hideOverlays(result.hideCurrentOverlays) + } + val swipeAnimation = createSwipeAnimation(swipes, result) return updateDragController(swipes, swipeAnimation) } @@ -123,7 +127,14 @@ internal class DraggableHandler( directionChangeSlop = layoutImpl.directionChangeSlop, ) - return createSwipeAnimation(layoutImpl, result, isUpOrLeft, orientation, gestureContext) + return createSwipeAnimation( + layoutImpl, + result, + isUpOrLeft, + orientation, + gestureContext, + layoutImpl.decayAnimationSpec, + ) } private fun resolveSwipeSource(startedPosition: Offset): SwipeSource.Resolved? { 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 8865a079733a..a5dba0f64583 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 @@ -60,7 +60,6 @@ import com.android.compose.animation.scene.transformation.PropertyTransformation 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 @@ -74,14 +73,6 @@ 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. */ @@ -285,7 +276,6 @@ internal class ElementNode( val element = layoutImpl.elements[key] ?: Element(key).also { layoutImpl.elements[key] = it } _element = element - addToRenderAuthority(element) if (!element.stateByContent.contains(content.key)) { val contents = buildList { layoutImpl.ancestors.fastForEach { add(it.inContent) } @@ -318,22 +308,9 @@ internal class ElementNode( removeNodeFromContentState() maybePruneMaps(layoutImpl, element, stateInContent) - removeFromRenderAuthority() _element = null } - private fun addToRenderAuthority(element: Element) { - val nestingDepth = layoutImpl.ancestors.size - element.renderAuthority[nestingDepth] = content.key - } - - private fun removeFromRenderAuthority() { - val nestingDepth = layoutImpl.ancestors.size - if (element.renderAuthority[nestingDepth] == content.key) { - element.renderAuthority.remove(nestingDepth) - } - } - private fun removeNodeFromContentState() { stateInContent.nodes.remove(this) } @@ -677,12 +654,8 @@ internal inline fun elementState( // Check if any ancestor runs a transition that has a transformation for the element states.fastForEachReversed { state -> if ( - state is TransitionState.Transition && - (state.transformationSpec.hasTransformation( - elementKey, - state.fromContent, - ) || - state.transformationSpec.hasTransformation(elementKey, state.toContent)) + isSharedElement(state, isInContent) || + hasTransformationForElement(state, elementKey) ) { return state } @@ -710,6 +683,21 @@ internal inline fun elementState( return null } +private inline fun isSharedElement( + state: TransitionState, + isInContent: (ContentKey) -> Boolean, +): Boolean { + return state is TransitionState.Transition && + isInContent(state.fromContent) && + isInContent(state.toContent) +} + +private fun hasTransformationForElement(state: TransitionState, elementKey: ElementKey): Boolean { + return state is TransitionState.Transition && + (state.transformationSpec.hasTransformation(elementKey, state.fromContent) || + state.transformationSpec.hasTransformation(elementKey, state.toContent)) +} + internal inline fun elementContentWhenIdle( layoutImpl: SceneTransitionLayoutImpl, currentState: TransitionState, @@ -964,13 +952,12 @@ private fun shouldPlaceElement( val transition = when (elementState) { is TransitionState.Idle -> { - return element.shouldBeRenderedBy(content) && - content == - elementContentWhenIdle( - layoutImpl, - elementState, - isInContent = { it in element.stateByContent }, - ) + return content == + elementContentWhenIdle( + layoutImpl, + elementState, + isInContent = { it in element.stateByContent }, + ) } is TransitionState.Transition -> elementState } 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 c10a48567b22..f4af5f055935 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 @@ -212,8 +212,8 @@ private fun shouldComposeMoveableElement( scenePicker.contentDuringTransition( element = elementKey, transition = transition, - fromContentZIndex = layoutImpl.content(transition.fromContent).zIndex, - toContentZIndex = layoutImpl.content(transition.toContent).zIndex, + fromContentZIndex = layoutImpl.content(transition.fromContent).globalZIndex, + toContentZIndex = layoutImpl.content(transition.toContent).globalZIndex, ) return pickedScene == content diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt index 621166e1823a..41279d338896 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt @@ -18,19 +18,26 @@ package com.android.compose.animation.scene import androidx.activity.BackEventCompat import androidx.activity.compose.PredictiveBackHandler +import androidx.compose.animation.core.AnimationSpec import androidx.compose.animation.core.snap import androidx.compose.foundation.gestures.Orientation import androidx.compose.runtime.Composable +import androidx.compose.ui.util.fastCoerceIn import com.android.compose.animation.scene.UserActionResult.ChangeScene import com.android.compose.animation.scene.UserActionResult.HideOverlay import com.android.compose.animation.scene.UserActionResult.ReplaceByOverlay import com.android.compose.animation.scene.UserActionResult.ShowOverlay -import com.android.compose.animation.scene.transition.animateProgress import com.android.mechanics.ProvidedGestureContext import com.android.mechanics.spec.InputDirection +import kotlin.coroutines.cancellation.CancellationException +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch @Composable internal fun PredictiveBackHandler( @@ -44,6 +51,10 @@ internal fun PredictiveBackHandler( return@PredictiveBackHandler } + if (result is ShowOverlay) { + layoutImpl.hideOverlays(result.hideCurrentOverlays) + } + val animation = createSwipeAnimation( layoutImpl, @@ -59,6 +70,7 @@ internal fun PredictiveBackHandler( distance = 1f, gestureContext = ProvidedGestureContext(dragOffset = 0f, direction = InputDirection.Max), + decayAnimationSpec = layoutImpl.decayAnimationSpec, ) animateProgress( @@ -89,3 +101,63 @@ private fun UserActionResult.copy( is ReplaceByOverlay -> copy(transitionKey = transitionKey) } } + +private suspend fun <T : ContentKey> animateProgress( + state: MutableSceneTransitionLayoutStateImpl, + animation: SwipeAnimation<T>, + progress: Flow<Float>, + commitSpec: AnimationSpec<Float>?, + cancelSpec: AnimationSpec<Float>?, + animationScope: CoroutineScope? = null, +) { + suspend fun animateOffset(targetContent: T, spec: AnimationSpec<Float>?) { + if (state.transitionState != animation.contentTransition || animation.isAnimatingOffset()) { + return + } + + animation.animateOffset( + initialVelocity = 0f, + targetContent = targetContent, + + // Important: we have to specify a spec that correctly animates *progress* (low + // visibility threshold) and not *offset* (higher visibility threshold). + spec = spec ?: animation.contentTransition.transformationSpec.progressSpec, + ) + } + + coroutineScope { + val collectionJob = launch { + try { + progress.collectLatest { progress -> + // Progress based animation should never overscroll given that the + // absoluteDistance exposed to overscroll builders is always 1f and will not + // lead to any noticeable transformation. + animation.dragOffset = progress.fastCoerceIn(0f, 1f) + } + + // Transition committed. + animateOffset(animation.toContent, commitSpec) + } catch (e: CancellationException) { + // Transition cancelled. + animateOffset(animation.fromContent, cancelSpec) + } + } + + // Start the transition. + animationScope?.launch { startTransition(state, animation, collectionJob) } + ?: startTransition(state, animation, collectionJob) + } +} + +private suspend fun <T : ContentKey> startTransition( + state: MutableSceneTransitionLayoutStateImpl, + animation: SwipeAnimation<T>, + progressCollectionJob: Job, +) { + state.startTransition(animation.contentTransition) + // The transition is done. Cancel the collection in case the transition was finished + // because it was interrupted by another transition. + if (progressCollectionJob.isActive) { + progressCollectionJob.cancel() + } +} 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 431a376d8eaf..72bb82bd41bb 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 @@ -17,6 +17,10 @@ package com.android.compose.animation.scene import androidx.annotation.FloatRange +import androidx.compose.animation.rememberSplineBasedDecay +import androidx.compose.foundation.LocalOverscrollFactory +import androidx.compose.foundation.OverscrollEffect +import androidx.compose.foundation.OverscrollFactory import androidx.compose.foundation.gestures.Orientation import androidx.compose.runtime.Composable import androidx.compose.runtime.SideEffect @@ -37,7 +41,6 @@ import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.LayoutDirection import com.android.compose.gesture.NestedScrollableBound -import com.android.compose.gesture.effect.ContentOverscrollEffect /** * [SceneTransitionLayout] is a container that automatically animates its content whenever its state @@ -62,7 +65,7 @@ fun SceneTransitionLayout( swipeSourceDetector: SwipeSourceDetector = DefaultEdgeDetector, swipeDetector: SwipeDetector = DefaultSwipeDetector, @FloatRange(from = 0.0, to = 0.5) transitionInterceptionThreshold: Float = 0.05f, - builder: SceneTransitionLayoutScope.() -> Unit, + builder: SceneTransitionLayoutScope<ContentScope>.() -> Unit, ) { SceneTransitionLayoutForTesting( state, @@ -75,20 +78,27 @@ fun SceneTransitionLayout( ) } -interface SceneTransitionLayoutScope { +interface SceneTransitionLayoutScope<out CS : ContentScope> { /** * Add a scene to this layout, identified by [key]. * * You can configure [userActions] so that swiping on this layout or navigating back will * transition to a different scene. * + * By default, [verticalOverscrollEffect][ContentScope.verticalOverscrollEffect] and + * [horizontalOverscrollEffect][ContentScope.horizontalOverscrollEffect] of this scene will be + * created using [LocalOverscrollFactory]. You can specify a non-null [effectFactory] to set up + * a custom factory that will be used by this scene and by any calls to + * rememberOverscrollEffect() inside the scene. + * * Important: scene order along the z-axis follows call order. Calling scene(A) followed by * scene(B) will mean that scene B renders after/above scene A. */ fun scene( key: SceneKey, userActions: Map<UserAction, UserActionResult> = emptyMap(), - content: @Composable ContentScope.() -> Unit, + effectFactory: OverscrollFactory? = null, + content: @Composable CS.() -> Unit, ) /** @@ -108,6 +118,12 @@ interface SceneTransitionLayoutScope { * to prevent swipes from reaching other scenes or overlays behind this one. Clicking this * protective layer will close the overlay. * + * By default, [verticalOverscrollEffect][ContentScope.verticalOverscrollEffect] and + * [horizontalOverscrollEffect][ContentScope.horizontalOverscrollEffect] of this overlay will be + * created using [LocalOverscrollFactory]. You can specify a non-null [effectFactory] to set up + * a custom factory that will be used by this content and by any calls to + * rememberOverscrollEffect() inside the content. + * * Important: overlays must be defined after all scenes. Overlay order along the z-axis follows * call order. Calling overlay(A) followed by overlay(B) will mean that overlay B renders * after/above overlay A. @@ -118,7 +134,8 @@ interface SceneTransitionLayoutScope { mapOf(Back to UserActionResult.HideOverlay(key)), alignment: Alignment = Alignment.Center, isModal: Boolean = true, - content: @Composable ContentScope.() -> Unit, + effectFactory: OverscrollFactory? = null, + content: @Composable CS.() -> Unit, ) } @@ -253,18 +270,6 @@ interface BaseContentScope : ElementStateScope { fun Modifier.disableSwipesWhenScrolling( bounds: NestedScrollableBound = NestedScrollableBound.Any ): 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, - ) } @Stable @@ -274,7 +279,7 @@ 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: + * You should use this effect exactly once with the `Modifier.overscroll()` modifier: * ```kotlin * @Composable * fun ContentScope.MyScene() { @@ -288,26 +293,9 @@ interface ContentScope : BaseContentScope { * } * ``` * - * 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 + val verticalOverscrollEffect: OverscrollEffect /** * The overscroll effect applied to the content in the horizontal direction. This can be used to @@ -315,7 +303,7 @@ interface ContentScope : BaseContentScope { * * @see verticalOverscrollEffect */ - val horizontalOverscrollEffect: ContentOverscrollEffect + val horizontalOverscrollEffect: OverscrollEffect /** * Animate some value at the content level. @@ -337,6 +325,29 @@ interface ContentScope : BaseContentScope { type: SharedValueType<T, *>, canOverflow: Boolean, ): AnimatedState<T> + + /** + * 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<ContentScope>.() -> Unit, + ) +} + +internal interface InternalContentScope : ContentScope { + + @Composable + fun NestedSceneTransitionLayoutForTesting( + state: SceneTransitionLayoutState, + modifier: Modifier, + onLayoutImpl: ((SceneTransitionLayoutImpl) -> Unit)?, + builder: SceneTransitionLayoutScope<InternalContentScope>.() -> Unit, + ) } /** @@ -607,8 +618,24 @@ sealed class UserActionResult( val overlay: OverlayKey, override val transitionKey: TransitionKey? = null, override val requiresFullDistanceSwipe: Boolean = false, + + /** Specify which overlays (if any) should be hidden when this user action is started. */ + val hideCurrentOverlays: HideCurrentOverlays = HideCurrentOverlays.None, ) : UserActionResult(transitionKey, requiresFullDistanceSwipe) { override fun toContent(currentScene: SceneKey): ContentKey = overlay + + sealed class HideCurrentOverlays { + /** Hide none of the current overlays. */ + object None : HideCurrentOverlays() + + /** Hide all current overlays. */ + object All : HideCurrentOverlays() + + /** Hide [overlays], for those in that set that are currently shown. */ + class Some(val overlays: Set<OverlayKey>) : HideCurrentOverlays() { + constructor(vararg overlays: OverlayKey) : this(overlays.toSet()) + } + } } /** A [UserActionResult] that hides [overlay]. */ @@ -714,12 +741,14 @@ internal fun SceneTransitionLayoutForTesting( sharedElementMap: MutableMap<ElementKey, Element> = remember { mutableMapOf() }, ancestors: List<Ancestor> = remember { emptyList() }, lookaheadScope: LookaheadScope? = null, - builder: SceneTransitionLayoutScope.() -> Unit, + builder: SceneTransitionLayoutScope<InternalContentScope>.() -> Unit, ) { val density = LocalDensity.current val directionChangeSlop = LocalViewConfiguration.current.touchSlop val layoutDirection = LocalLayoutDirection.current + val defaultEffectFactory = checkNotNull(LocalOverscrollFactory.current) val animationScope = rememberCoroutineScope() + val decayAnimationSpec = rememberSplineBasedDecay<Float>() val layoutImpl = remember { SceneTransitionLayoutImpl( state = state as MutableSceneTransitionLayoutStateImpl, @@ -734,13 +763,15 @@ internal fun SceneTransitionLayoutForTesting( ancestors = ancestors, lookaheadScope = lookaheadScope, directionChangeSlop = directionChangeSlop, + defaultEffectFactory = defaultEffectFactory, + decayAnimationSpec = decayAnimationSpec, ) .also { onLayoutImpl?.invoke(it) } } // TODO(b/317014852): Move this into the SideEffect {} again once STLImpl.scenes is not a // SnapshotStateMap anymore. - layoutImpl.updateContents(builder, layoutDirection) + layoutImpl.updateContents(builder, layoutDirection, defaultEffectFactory) SideEffect { if (state != layoutImpl.state) { @@ -773,6 +804,7 @@ internal fun SceneTransitionLayoutForTesting( layoutImpl.swipeSourceDetector = swipeSourceDetector layoutImpl.swipeDetector = swipeDetector layoutImpl.transitionInterceptionThreshold = transitionInterceptionThreshold + layoutImpl.decayAnimationSpec = decayAnimationSpec } layoutImpl.Content(modifier) 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 38ad0a80fd00..53996d25afdb 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 @@ -17,6 +17,8 @@ package com.android.compose.animation.scene import androidx.annotation.VisibleForTesting +import androidx.compose.animation.core.DecayAnimationSpec +import androidx.compose.foundation.OverscrollFactory import androidx.compose.foundation.clickable import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.interaction.MutableInteractionSource @@ -45,9 +47,12 @@ import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.util.fastAny +import androidx.compose.ui.util.fastFirstOrNull import androidx.compose.ui.util.fastForEach import androidx.compose.ui.util.fastForEachReversed import androidx.compose.ui.zIndex +import com.android.compose.animation.scene.UserActionResult.ShowOverlay.HideCurrentOverlays import com.android.compose.animation.scene.content.Content import com.android.compose.animation.scene.content.Overlay import com.android.compose.animation.scene.content.Scene @@ -78,7 +83,8 @@ internal class SceneTransitionLayoutImpl( internal var swipeSourceDetector: SwipeSourceDetector, internal var swipeDetector: SwipeDetector, internal var transitionInterceptionThreshold: Float, - builder: SceneTransitionLayoutScope.() -> Unit, + internal var decayAnimationSpec: DecayAnimationSpec<Float>, + builder: SceneTransitionLayoutScope<InternalContentScope>.() -> Unit, /** * The scope that should be used by *animations started by this layout only*, i.e. animations @@ -117,6 +123,7 @@ internal class SceneTransitionLayoutImpl( */ internal val ancestors: List<Ancestor> = emptyList(), lookaheadScope: LookaheadScope? = null, + defaultEffectFactory: OverscrollFactory, ) { /** @@ -198,7 +205,7 @@ internal class SceneTransitionLayoutImpl( private val nestedScrollConnection = object : NestedScrollConnection {} init { - updateContents(builder, layoutDirection) + updateContents(builder, layoutDirection, defaultEffectFactory) // DraggableHandlerImpl must wait for the scenes to be initialized, in order to access the // current scene (required for SwipeTransition). @@ -206,14 +213,14 @@ internal class SceneTransitionLayoutImpl( DraggableHandler( layoutImpl = this, orientation = Orientation.Horizontal, - gestureEffectProvider = { content(it).scope.horizontalOverscrollGestureEffect }, + gestureEffectProvider = { content(it).horizontalEffects.gestureEffect }, ) verticalDraggableHandler = DraggableHandler( layoutImpl = this, orientation = Orientation.Vertical, - gestureEffectProvider = { content(it).scope.verticalOverscrollGestureEffect }, + gestureEffectProvider = { content(it).verticalEffects.gestureEffect }, ) // Make sure that the state is created on the same thread (most probably the main thread) @@ -221,19 +228,30 @@ internal class SceneTransitionLayoutImpl( state.checkThread() } - internal fun scene(key: SceneKey): Scene { - return scenes[key] ?: error("Scene $key is not configured") + private fun sceneOrNull(key: SceneKey): Scene? { + return scenes[key] + ?: ancestors + .fastFirstOrNull { it.layoutImpl.scenes[key] != null } + ?.layoutImpl + ?.scenes + ?.get(key) } - internal fun contentOrNull(key: ContentKey): Content? { - return when (key) { - is SceneKey -> scenes[key] - is OverlayKey -> overlays[key] - } + private fun overlayOrNull(key: OverlayKey): Overlay? { + return overlays[key] + ?: ancestors + .fastFirstOrNull { it.layoutImpl.overlays[key] != null } + ?.layoutImpl + ?.overlays + ?.get(key) + } + + internal fun scene(key: SceneKey): Scene { + return sceneOrNull(key) ?: error("Scene $key is not configured") } internal fun overlay(key: OverlayKey): Overlay { - return overlays[key] ?: error("Overlay $key is not configured") + return overlayOrNull(key) ?: error("Overlay $key is not configured") } internal fun content(key: ContentKey): Content { @@ -243,6 +261,10 @@ internal class SceneTransitionLayoutImpl( } } + internal fun isAncestorContent(content: ContentKey): Boolean { + return ancestors.fastAny { it.inContent == content } + } + internal fun contentForUserActions(): Content { return findOverlayWithHighestZIndex() ?: scene(state.transitionState.currentScene) } @@ -266,8 +288,9 @@ internal class SceneTransitionLayoutImpl( } internal fun updateContents( - builder: SceneTransitionLayoutScope.() -> Unit, + builder: SceneTransitionLayoutScope<InternalContentScope>.() -> Unit, layoutDirection: LayoutDirection, + defaultEffectFactory: OverscrollFactory, ) { // Keep a reference of the current contents. After processing [builder], the contents that // were not configured will be removed. @@ -275,15 +298,18 @@ internal class SceneTransitionLayoutImpl( val overlaysToRemove = if (_overlays == null) mutableSetOf() else overlays.keys.toMutableSet() + val parentZIndex = + if (ancestors.isEmpty()) 0L else content(ancestors.last().inContent).globalZIndex // The incrementing zIndex of each scene. - var zIndex = 0f + var zIndex = 0 var overlaysDefined = false - object : SceneTransitionLayoutScope { + object : SceneTransitionLayoutScope<InternalContentScope> { override fun scene( key: SceneKey, userActions: Map<UserAction, UserActionResult>, - content: @Composable ContentScope.() -> Unit, + effectFactory: OverscrollFactory?, + content: @Composable InternalContentScope.() -> Unit, ) { require(!overlaysDefined) { "all scenes must be defined before overlays" } @@ -291,11 +317,16 @@ internal class SceneTransitionLayoutImpl( val resolvedUserActions = resolveUserActions(key, userActions, layoutDirection) val scene = scenes[key] + val globalZIndex = + Content.calculateGlobalZIndex(parentZIndex, ++zIndex, ancestors.size) + val factory = effectFactory ?: defaultEffectFactory if (scene != null) { // Update an existing scene. scene.content = content scene.userActions = resolvedUserActions - scene.zIndex = zIndex + scene.zIndex = zIndex.toFloat() + scene.globalZIndex = globalZIndex + scene.maybeUpdateEffects(factory) } else { // New scene. scenes[key] = @@ -304,11 +335,11 @@ internal class SceneTransitionLayoutImpl( this@SceneTransitionLayoutImpl, content, resolvedUserActions, - zIndex, + zIndex.toFloat(), + globalZIndex, + factory, ) } - - zIndex++ } override fun overlay( @@ -316,20 +347,26 @@ internal class SceneTransitionLayoutImpl( userActions: Map<UserAction, UserActionResult>, alignment: Alignment, isModal: Boolean, - content: @Composable (ContentScope.() -> Unit), + effectFactory: OverscrollFactory?, + content: @Composable (InternalContentScope.() -> Unit), ) { overlaysDefined = true overlaysToRemove.remove(key) val overlay = overlays[key] val resolvedUserActions = resolveUserActions(key, userActions, layoutDirection) + val globalZIndex = + Content.calculateGlobalZIndex(parentZIndex, ++zIndex, ancestors.size) + val factory = effectFactory ?: defaultEffectFactory if (overlay != null) { // Update an existing overlay. overlay.content = content - overlay.zIndex = zIndex + overlay.zIndex = zIndex.toFloat() + overlay.globalZIndex = globalZIndex overlay.userActions = resolvedUserActions overlay.alignment = alignment overlay.isModal = isModal + overlay.maybeUpdateEffects(factory) } else { // New overlay. overlays[key] = @@ -338,13 +375,13 @@ internal class SceneTransitionLayoutImpl( this@SceneTransitionLayoutImpl, content, resolvedUserActions, - zIndex, + zIndex.toFloat(), + globalZIndex, alignment, isModal, + factory, ) } - - zIndex++ } } .builder() @@ -537,13 +574,27 @@ internal class SceneTransitionLayoutImpl( .sortedBy { it.zIndex } } + internal fun hideOverlays(hide: HideCurrentOverlays) { + fun maybeHide(overlay: OverlayKey) { + if (state.canHideOverlay(overlay)) { + state.hideOverlay(overlay, animationScope = this.animationScope) + } + } + + when (hide) { + HideCurrentOverlays.None -> {} + HideCurrentOverlays.All -> HashSet(state.currentOverlays).forEach { maybeHide(it) } + is HideCurrentOverlays.Some -> hide.overlays.forEach { maybeHide(it) } + } + } + @VisibleForTesting internal fun setContentsAndLayoutTargetSizeForTest(size: IntSize) { lastSize = size (scenes.values + overlays.values).forEach { it.targetSize = size } } - internal fun overlaysOrNullForTest(): Map<OverlayKey, Overlay>? = _overlays + @VisibleForTesting internal fun overlaysOrNullForTest(): Map<OverlayKey, Overlay>? = _overlays } private data class LayoutElement(private val layoutImpl: SceneTransitionLayoutImpl) : 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 index ed3a5cac8184..ee3944893ced 100644 --- 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 @@ -20,52 +20,22 @@ 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 > 0 && - (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 scenePicker = elementKey.contentPicker - val pickedScene = - scenePicker.contentDuringTransition( + val contentPicker = elementKey.contentPicker + val pickedContent = + contentPicker.contentDuringTransition( element = elementKey, transition = transition, - fromContentZIndex = layoutImpl.content(transition.fromContent).zIndex, - toContentZIndex = layoutImpl.content(transition.toContent).zIndex, + fromContentZIndex = layoutImpl.content(transition.fromContent).globalZIndex, + toContentZIndex = layoutImpl.content(transition.toContent).globalZIndex, ) - return pickedScene == content + return pickedContent == content || layoutImpl.isAncestorContent(pickedContent) } internal fun isSharedElementEnabled( 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 3bd37ad018b0..cb0d33cf5205 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 @@ -21,6 +21,8 @@ package com.android.compose.animation.scene import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.AnimationSpec import androidx.compose.animation.core.AnimationVector1D +import androidx.compose.animation.core.DecayAnimationSpec +import androidx.compose.animation.core.calculateTargetValue import androidx.compose.foundation.gestures.Orientation import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.runtime.getValue @@ -42,6 +44,7 @@ internal fun createSwipeAnimation( orientation: Orientation, distance: Float, gestureContext: MutableDragOffsetGestureContext, + decayAnimationSpec: DecayAnimationSpec<Float>, ): SwipeAnimation<*> { return createSwipeAnimation( layoutState, @@ -53,6 +56,7 @@ internal fun createSwipeAnimation( error("Computing contentForUserActions requires a SceneTransitionLayoutImpl") }, gestureContext = gestureContext, + decayAnimationSpec = decayAnimationSpec, ) } @@ -62,6 +66,7 @@ internal fun createSwipeAnimation( isUpOrLeft: Boolean, orientation: Orientation, gestureContext: MutableDragOffsetGestureContext, + decayAnimationSpec: DecayAnimationSpec<Float>, distance: Float = DistanceUnspecified, ): SwipeAnimation<*> { var lastDistance = distance @@ -106,6 +111,7 @@ internal fun createSwipeAnimation( distance = ::distance, contentForUserActions = { layoutImpl.contentForUserActions().key }, gestureContext = gestureContext, + decayAnimationSpec = decayAnimationSpec, ) } @@ -117,6 +123,7 @@ private fun createSwipeAnimation( distance: (SwipeAnimation<*>) -> Float, contentForUserActions: () -> ContentKey, gestureContext: MutableDragOffsetGestureContext, + decayAnimationSpec: DecayAnimationSpec<Float>, ): SwipeAnimation<*> { fun <T : ContentKey> swipeAnimation(fromContent: T, toContent: T): SwipeAnimation<T> { return SwipeAnimation( @@ -128,6 +135,7 @@ private fun createSwipeAnimation( requiresFullDistanceSwipe = result.requiresFullDistanceSwipe, distance = distance, gestureContext = gestureContext, + decayAnimationSpec = decayAnimationSpec, ) } @@ -201,6 +209,7 @@ internal class SwipeAnimation<T : ContentKey>( private val distance: (SwipeAnimation<T>) -> Float, currentContent: T = fromContent, private val gestureContext: MutableDragOffsetGestureContext, + private val decayAnimationSpec: DecayAnimationSpec<Float>, ) : MutableDragOffsetGestureContext by gestureContext { /** The [TransitionState.Transition] whose implementation delegates to this [SwipeAnimation]. */ lateinit var contentTransition: TransitionState.Transition @@ -367,20 +376,10 @@ internal class SwipeAnimation<T : ContentKey>( check(isAnimatingOffset()) - val motionSpatialSpec = spec ?: layoutState.motionScheme.defaultSpatialSpec() - val velocityConsumed = CompletableDeferred<Float>() offsetAnimationRunnable.complete { - val result = - animatable.animateTo( - targetValue = targetOffset, - animationSpec = motionSpatialSpec, - initialVelocity = initialVelocity, - ) - - // We are no longer able to consume the velocity, the rest can be consumed by another - // component in the hierarchy. - velocityConsumed.complete(initialVelocity - result.endState.velocity) + val consumed = animateOffset(animatable, targetOffset, initialVelocity, spec) + velocityConsumed.complete(consumed) // Wait for overscroll to finish so that the transition is removed from the STLState // only after the overscroll is done, to avoid dropping frame right when the user lifts @@ -391,6 +390,53 @@ internal class SwipeAnimation<T : ContentKey>( return velocityConsumed.await() } + private suspend fun animateOffset( + animatable: Animatable<Float, AnimationVector1D>, + targetOffset: Float, + initialVelocity: Float, + spec: AnimationSpec<Float>?, + ): Float { + val initialOffset = animatable.value + val decayOffset = + decayAnimationSpec.calculateTargetValue( + initialVelocity = initialVelocity, + initialValue = initialOffset, + ) + + val willDecayReachBounds = + when { + targetOffset > initialOffset -> decayOffset >= targetOffset + targetOffset < initialOffset -> decayOffset <= targetOffset + else -> true + } + + if (willDecayReachBounds) { + val result = animatable.animateDecay(initialVelocity, decayAnimationSpec) + check(animatable.value == targetOffset) { + buildString { + appendLine( + "animatable.value = ${animatable.value} != $targetOffset = targetOffset" + ) + appendLine(" initialOffset=$initialOffset") + appendLine(" targetOffset=$targetOffset") + appendLine(" initialVelocity=$initialVelocity") + appendLine(" decayOffset=$decayOffset") + } + } + return initialVelocity - result.endState.velocity + } + + val motionSpatialSpec = spec ?: layoutState.motionScheme.defaultSpatialSpec() + animatable.animateTo( + targetValue = targetOffset, + animationSpec = motionSpatialSpec, + initialVelocity = initialVelocity, + ) + + // We consumed the whole velocity. + return initialVelocity + } + private fun canChangeContent(targetContent: ContentKey): Boolean { return when (val transition = contentTransition) { is TransitionState.Transition.ChangeScene -> diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt index a29c1bbe0a0c..badb8a80b982 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt @@ -210,8 +210,8 @@ interface ElementContentPicker { fun contentDuringTransition( element: ElementKey, transition: TransitionState.Transition, - fromContentZIndex: Float, - toContentZIndex: Float, + fromContentZIndex: Long, + toContentZIndex: Long, ): ContentKey /** @@ -279,8 +279,8 @@ object HighestZIndexContentPicker : ElementContentPicker { override fun contentDuringTransition( element: ElementKey, transition: TransitionState.Transition, - fromContentZIndex: Float, - toContentZIndex: Float, + fromContentZIndex: Long, + toContentZIndex: Long, ): ContentKey { return if (fromContentZIndex > toContentZIndex) { transition.fromContent @@ -300,8 +300,8 @@ object HighestZIndexContentPicker : ElementContentPicker { override fun contentDuringTransition( element: ElementKey, transition: TransitionState.Transition, - fromContentZIndex: Float, - toContentZIndex: Float, + fromContentZIndex: Long, + toContentZIndex: Long, ): ContentKey { return HighestZIndexContentPicker.contentDuringTransition( element, @@ -321,8 +321,8 @@ object LowestZIndexContentPicker : ElementContentPicker { override fun contentDuringTransition( element: ElementKey, transition: TransitionState.Transition, - fromContentZIndex: Float, - toContentZIndex: Float, + fromContentZIndex: Long, + toContentZIndex: Long, ): ContentKey { return if (fromContentZIndex < toContentZIndex) { transition.fromContent @@ -342,8 +342,8 @@ object LowestZIndexContentPicker : ElementContentPicker { override fun contentDuringTransition( element: ElementKey, transition: TransitionState.Transition, - fromContentZIndex: Float, - toContentZIndex: Float, + fromContentZIndex: Long, + toContentZIndex: Long, ): ContentKey { return LowestZIndexContentPicker.contentDuringTransition( element, @@ -375,8 +375,8 @@ class MovableElementContentPicker(override val contents: Set<ContentKey>) : override fun contentDuringTransition( element: ElementKey, transition: TransitionState.Transition, - fromContentZIndex: Float, - toContentZIndex: Float, + fromContentZIndex: Long, + toContentZIndex: Long, ): ContentKey { return when { transition.toContent in contents -> transition.toContent diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/UserActionDistanceScopeImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/UserActionDistanceScopeImpl.kt index 8457481b3e14..fffc7f988acf 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/UserActionDistanceScopeImpl.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/UserActionDistanceScopeImpl.kt @@ -36,7 +36,7 @@ internal class ElementStateScopeImpl(private val layoutImpl: SceneTransitionLayo } override fun ContentKey.targetSize(): IntSize? { - return layoutImpl.contentOrNull(this)?.targetSize.takeIf { it != IntSize.Zero } + return layoutImpl.content(this).targetSize.takeIf { it != IntSize.Zero } } } 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 6ccd498f7a04..b7daaf4075ed 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,16 +17,16 @@ package com.android.compose.animation.scene.content import android.annotation.SuppressLint -import androidx.compose.animation.core.AnimationSpec -import androidx.compose.animation.core.AnimationVector -import androidx.compose.animation.core.TwoWayConverter -import androidx.compose.foundation.gestures.Orientation +import androidx.compose.foundation.LocalOverscrollFactory +import androidx.compose.foundation.OverscrollEffect +import androidx.compose.foundation.OverscrollFactory import androidx.compose.foundation.layout.Box -import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.Stable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableLongStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue @@ -45,6 +45,7 @@ import com.android.compose.animation.scene.ElementContentScope import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.ElementScope import com.android.compose.animation.scene.ElementStateScope +import com.android.compose.animation.scene.InternalContentScope import com.android.compose.animation.scene.MovableElement import com.android.compose.animation.scene.MovableElementContentScope import com.android.compose.animation.scene.MovableElementKey @@ -58,45 +59,110 @@ 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.VisualEffect import com.android.compose.animation.scene.element import com.android.compose.animation.scene.modifiers.noResizeDuringTransitions import com.android.compose.gesture.NestedScrollControlState import com.android.compose.gesture.NestedScrollableBound -import com.android.compose.gesture.effect.OffsetOverscrollEffect import com.android.compose.gesture.nestedScrollController import com.android.compose.modifiers.thenIf import com.android.compose.ui.graphics.ContainerState import com.android.compose.ui.graphics.container +import kotlin.math.pow /** A content defined in a [SceneTransitionLayout], i.e. a scene or an overlay. */ @Stable internal sealed class Content( open val key: ContentKey, val layoutImpl: SceneTransitionLayoutImpl, - content: @Composable ContentScope.() -> Unit, + content: @Composable InternalContentScope.() -> Unit, actions: Map<UserAction.Resolved, UserActionResult>, zIndex: Float, + globalZIndex: Long, + effectFactory: OverscrollFactory, ) { private val nestedScrollControlState = NestedScrollControlState() internal val scope = ContentScopeImpl(layoutImpl, content = this, nestedScrollControlState) val containerState = ContainerState() + // Important: All fields in this class should be backed by State given that contents are updated + // directly during composition, outside of a SideEffect. var content by mutableStateOf(content) - var zIndex by mutableFloatStateOf(zIndex) var targetSize by mutableStateOf(IntSize.Zero) var userActions by mutableStateOf(actions) + var zIndex by mutableFloatStateOf(zIndex) + + /** + * The globalZIndex is a zIndex that indicates the z order of each content across any nested + * STLs. This is done by dividing the number range of a Long into chunks of three digits. As + * Long.MAX_VALUE is a bit larger than 1e18 we start the first level at 1e15 to give at least + * 1000 contents space. The first level of nesting depth will occupy the 3 highest digits and + * with each level we continue into the next three. Therefore the parent z order will have + * priority and their children have room to order themselves within the "less significant bits". + * + * As an example, imagine the following tree of nested scenes: + * ``` + * / \ + * A01 A02 -- nestingDepth 0 + * / \ | + * B01 B02 C01 -- nestingDepth 1 + * | + * D01 -- nestingDepth 2 + * ``` + * + * The zIndex values would be: + * ``` + * A01: 1e15 (1_000_000_000_000_000) + * A02: 2e15 (2_000_000_000_000_000) + * B01: 1.001e15 (1_001_000_000_000_000) + * B02: 1.002e15 (1_002_000_000_000_000) + * C01: 2.001e15 (2_001_000_000_000_000) + * D01: 1.002001e15 (1_002_001_000_000_000) + * ``` + * + * Therefore the order of zIndexes will correctly be: A01, B01, B02, D01, A02, C01, which + * corresponds to a Pre-order traversal of the tree. + * + * Since composition of the tree does not happen all at once we can't do a Pre-order traversal + * right away without allocating resources to build and manage the tree structure through all + * updates. Using this method we have stable zIndexes at time of composition of each content + * independently with the only drawback that contents per each STL are limited to 999 and + * nesting depth is limited to 6 (18 / 3). + */ + var globalZIndex by mutableLongStateOf(globalZIndex) + + companion object { + fun calculateGlobalZIndex( + parentGlobalZIndex: Long, + localZIndex: Int, + nestingDepth: Int, + ): Long { + require(nestingDepth in 0..5) { "NestingDepth of STLs can be at most 5." } + require(localZIndex in 1..999) { "A scene can have at most 999 contents." } + val offsetForDepth = 10.0.pow((5 - nestingDepth) * 3).toLong() + return parentGlobalZIndex + offsetForDepth * localZIndex + } + } + + private var lastFactory by mutableStateOf(effectFactory) + var verticalEffects by mutableStateOf(ContentEffects(effectFactory)) + private set + + var horizontalEffects by mutableStateOf(ContentEffects(effectFactory)) + private set @SuppressLint("NotConstructor") @Composable fun Content(modifier: Modifier = Modifier) { + // If this content has a custom factory, provide it to the content so that the factory is + // automatically used when calling rememberOverscrollEffect(). Box( modifier .zIndex(zIndex) .approachLayout( isMeasurementApproachInProgress = { layoutImpl.state.isTransitioning() } ) { measurable, constraints -> - // TODO(b/353679003): Use the ModifierNode API to set this *before* the approach + // TODO(b/353679003): Use the ModifierNode API to set this *before* the + // approach // pass is started. targetSize = lookaheadSize val placeable = measurable.measure(constraints) @@ -107,18 +173,33 @@ internal sealed class Content( } .testTag(key.testTag) ) { - scope.content() + CompositionLocalProvider(LocalOverscrollFactory provides lastFactory) { + scope.content() + } } } fun areSwipesAllowed(): Boolean = nestedScrollControlState.isOuterScrollAllowed + + fun maybeUpdateEffects(effectFactory: OverscrollFactory) { + if (effectFactory != lastFactory) { + lastFactory = effectFactory + verticalEffects = ContentEffects(effectFactory) + horizontalEffects = ContentEffects(effectFactory) + } + } +} + +internal class ContentEffects(factory: OverscrollFactory) { + val overscrollEffect = factory.createOverscrollEffect() + val gestureEffect = GestureEffect(overscrollEffect) } internal class ContentScopeImpl( private val layoutImpl: SceneTransitionLayoutImpl, private val content: Content, private val nestedScrollControlState: NestedScrollControlState, -) : ContentScope, ElementStateScope by layoutImpl.elementStateScope { +) : InternalContentScope, ElementStateScope by layoutImpl.elementStateScope { override val contentKey: ContentKey get() = content.key @@ -127,34 +208,11 @@ internal class ContentScopeImpl( override val lookaheadScope: LookaheadScope get() = layoutImpl.lookaheadScope - @OptIn(ExperimentalMaterial3ExpressiveApi::class) - private val animationSpatialSpec = - object : AnimationSpec<Float> { - override fun <V : AnimationVector> vectorize(converter: TwoWayConverter<Float, V>) = - layoutImpl.state.motionScheme.defaultSpatialSpec<Float>().vectorize(converter) - } - - private val _verticalOverscrollEffect = - OffsetOverscrollEffect( - orientation = Orientation.Vertical, - animationScope = layoutImpl.animationScope, - animationSpec = animationSpatialSpec, - ) - - private val _horizontalOverscrollEffect = - OffsetOverscrollEffect( - orientation = Orientation.Horizontal, - animationScope = layoutImpl.animationScope, - animationSpec = animationSpatialSpec, - ) - - val verticalOverscrollGestureEffect = GestureEffect(_verticalOverscrollEffect) - - val horizontalOverscrollGestureEffect = GestureEffect(_horizontalOverscrollEffect) + override val verticalOverscrollEffect: OverscrollEffect + get() = content.verticalEffects.overscrollEffect - override val verticalOverscrollEffect = VisualEffect(_verticalOverscrollEffect) - - override val horizontalOverscrollEffect = VisualEffect(_horizontalOverscrollEffect) + override val horizontalOverscrollEffect: OverscrollEffect + get() = content.horizontalEffects.overscrollEffect override fun Modifier.element(key: ElementKey): Modifier { return element(layoutImpl, content, key) @@ -208,7 +266,17 @@ internal class ContentScopeImpl( override fun NestedSceneTransitionLayout( state: SceneTransitionLayoutState, modifier: Modifier, - builder: SceneTransitionLayoutScope.() -> Unit, + builder: SceneTransitionLayoutScope<ContentScope>.() -> Unit, + ) { + NestedSceneTransitionLayoutForTesting(state, modifier, null, builder) + } + + @Composable + override fun NestedSceneTransitionLayoutForTesting( + state: SceneTransitionLayoutState, + modifier: Modifier, + onLayoutImpl: ((SceneTransitionLayoutImpl) -> Unit)?, + builder: SceneTransitionLayoutScope<InternalContentScope>.() -> Unit, ) { val ancestors = remember(layoutImpl, contentKey, layoutImpl.ancestors) { @@ -217,7 +285,7 @@ internal class ContentScopeImpl( SceneTransitionLayoutForTesting( state, modifier, - onLayoutImpl = null, + onLayoutImpl = onLayoutImpl, builder = builder, sharedElementMap = layoutImpl.elements, ancestors = ancestors, diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Overlay.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Overlay.kt index d4de559cef43..4fbca430bc4b 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Overlay.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Overlay.kt @@ -16,13 +16,14 @@ package com.android.compose.animation.scene.content +import androidx.compose.foundation.OverscrollFactory import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment -import com.android.compose.animation.scene.ContentScope +import com.android.compose.animation.scene.InternalContentScope import com.android.compose.animation.scene.OverlayKey import com.android.compose.animation.scene.SceneTransitionLayoutImpl import com.android.compose.animation.scene.UserAction @@ -33,12 +34,14 @@ import com.android.compose.animation.scene.UserActionResult internal class Overlay( override val key: OverlayKey, layoutImpl: SceneTransitionLayoutImpl, - content: @Composable ContentScope.() -> Unit, + content: @Composable InternalContentScope.() -> Unit, actions: Map<UserAction.Resolved, UserActionResult>, zIndex: Float, + globalZIndex: Long, alignment: Alignment, isModal: Boolean, -) : Content(key, layoutImpl, content, actions, zIndex) { + effectFactory: OverscrollFactory, +) : Content(key, layoutImpl, content, actions, zIndex, globalZIndex, effectFactory) { var alignment by mutableStateOf(alignment) var isModal by mutableStateOf(isModal) diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Scene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Scene.kt index 4a7a94d6e177..7f57798fb1b3 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Scene.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Scene.kt @@ -16,9 +16,10 @@ package com.android.compose.animation.scene.content +import androidx.compose.foundation.OverscrollFactory import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable -import com.android.compose.animation.scene.ContentScope +import com.android.compose.animation.scene.InternalContentScope import com.android.compose.animation.scene.SceneKey import com.android.compose.animation.scene.SceneTransitionLayoutImpl import com.android.compose.animation.scene.UserAction @@ -29,10 +30,12 @@ import com.android.compose.animation.scene.UserActionResult internal class Scene( override val key: SceneKey, layoutImpl: SceneTransitionLayoutImpl, - content: @Composable ContentScope.() -> Unit, + content: @Composable InternalContentScope.() -> Unit, actions: Map<UserAction.Resolved, UserActionResult>, zIndex: Float, -) : Content(key, layoutImpl, content, actions, zIndex) { + globalZIndex: Long, + effectFactory: OverscrollFactory, +) : Content(key, layoutImpl, content, actions, zIndex, globalZIndex, effectFactory) { override fun toString(): String { return "Scene(key=$key)" } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/effect/GestureEffect.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/effect/GestureEffect.kt index 2db45aa3dd58..a537c8764b0a 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/effect/GestureEffect.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/effect/GestureEffect.kt @@ -16,14 +16,14 @@ package com.android.compose.animation.scene.effect +import androidx.compose.foundation.OverscrollEffect import androidx.compose.ui.geometry.Offset import androidx.compose.ui.input.nestedscroll.NestedScrollSource import androidx.compose.ui.unit.Velocity -import com.android.compose.gesture.effect.ContentOverscrollEffect /** An overscroll effect that ensures only a single fling animation is triggered. */ -internal class GestureEffect(private val delegate: ContentOverscrollEffect) : - ContentOverscrollEffect by delegate { +internal class GestureEffect(private val delegate: OverscrollEffect) : + OverscrollEffect by delegate { private var shouldFling = false override fun applyToScroll( @@ -51,25 +51,3 @@ internal class GestureEffect(private val delegate: ContentOverscrollEffect) : 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/transition/Seek.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/Seek.kt deleted file mode 100644 index 819cec712808..000000000000 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/Seek.kt +++ /dev/null @@ -1,203 +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.compose.animation.scene.transition - -import androidx.annotation.FloatRange -import androidx.compose.animation.core.AnimationSpec -import androidx.compose.foundation.gestures.Orientation -import androidx.compose.ui.util.fastCoerceIn -import com.android.compose.animation.scene.ContentKey -import com.android.compose.animation.scene.MutableSceneTransitionLayoutState -import com.android.compose.animation.scene.MutableSceneTransitionLayoutStateImpl -import com.android.compose.animation.scene.OverlayKey -import com.android.compose.animation.scene.SceneKey -import com.android.compose.animation.scene.SwipeAnimation -import com.android.compose.animation.scene.TransitionKey -import com.android.compose.animation.scene.UserActionResult -import com.android.compose.animation.scene.createSwipeAnimation -import com.android.mechanics.ProvidedGestureContext -import com.android.mechanics.spec.InputDirection -import kotlin.coroutines.cancellation.CancellationException -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Job -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.launch - -/** - * Seek to the given [scene] using [progress]. - * - * This will start a transition from the - * [current scene][MutableSceneTransitionLayoutState.currentScene] to [scene], driven by the - * progress in [progress]. Once [progress] stops emitting, we will animate progress to 1f (using - * [animationSpec]) if it stopped normally or to 0f if it stopped with a - * [kotlin.coroutines.cancellation.CancellationException]. - */ -suspend fun MutableSceneTransitionLayoutState.seekToScene( - scene: SceneKey, - @FloatRange(0.0, 1.0) progress: Flow<Float>, - transitionKey: TransitionKey? = null, - animationSpec: AnimationSpec<Float>? = null, -) { - require(scene != currentScene) { - "seekToScene($scene) has to be called with a different scene than the current scene" - } - - seek(UserActionResult.ChangeScene(scene, transitionKey), progress, animationSpec) -} - -/** - * Seek to show the given [overlay] using [progress]. - * - * This will start a transition to show [overlay] from the - * [current scene][MutableSceneTransitionLayoutState.currentScene], driven by the progress in - * [progress]. Once [progress] stops emitting, we will animate progress to 1f (using - * [animationSpec]) if it stopped normally or to 0f if it stopped with a - * [kotlin.coroutines.cancellation.CancellationException]. - */ -suspend fun MutableSceneTransitionLayoutState.seekToShowOverlay( - overlay: OverlayKey, - @FloatRange(0.0, 1.0) progress: Flow<Float>, - transitionKey: TransitionKey? = null, - animationSpec: AnimationSpec<Float>? = null, -) { - require(overlay in currentOverlays) { - "seekToShowOverlay($overlay) can be called only when the overlay is in currentOverlays" - } - - seek(UserActionResult.ShowOverlay(overlay, transitionKey), progress, animationSpec) -} - -/** - * Seek to hide the given [overlay] using [progress]. - * - * This will start a transition to hide [overlay] to the - * [current scene][MutableSceneTransitionLayoutState.currentScene], driven by the progress in - * [progress]. Once [progress] stops emitting, we will animate progress to 1f (using - * [animationSpec]) if it stopped normally or to 0f if it stopped with a - * [kotlin.coroutines.cancellation.CancellationException]. - */ -suspend fun MutableSceneTransitionLayoutState.seekToHideOverlay( - overlay: OverlayKey, - @FloatRange(0.0, 1.0) progress: Flow<Float>, - transitionKey: TransitionKey? = null, - animationSpec: AnimationSpec<Float>? = null, -) { - require(overlay !in currentOverlays) { - "seekToHideOverlay($overlay) can be called only when the overlay is *not* in " + - "currentOverlays" - } - - seek(UserActionResult.HideOverlay(overlay, transitionKey), progress, animationSpec) -} - -private suspend fun MutableSceneTransitionLayoutState.seek( - result: UserActionResult, - progress: Flow<Float>, - animationSpec: AnimationSpec<Float>?, -) { - val layoutState = - when (this) { - is MutableSceneTransitionLayoutStateImpl -> this - } - - val swipeAnimation = - createSwipeAnimation( - layoutState = layoutState, - result = result, - - // We are animating progress, so distance is always 1f. - distance = 1f, - - // The orientation and isUpOrLeft don't matter here given that they are only used during - // overscroll, which is disabled for progress-based transitions. - orientation = Orientation.Horizontal, - isUpOrLeft = false, - // There is no gesture information available here - animateProgress - // will set the progress as the dragOffset. - gestureContext = ProvidedGestureContext(0f, InputDirection.Max), - ) - - animateProgress( - state = layoutState, - animation = swipeAnimation, - progress = progress, - commitSpec = animationSpec, - cancelSpec = animationSpec, - ) -} - -internal suspend fun <T : ContentKey> animateProgress( - state: MutableSceneTransitionLayoutStateImpl, - animation: SwipeAnimation<T>, - progress: Flow<Float>, - commitSpec: AnimationSpec<Float>?, - cancelSpec: AnimationSpec<Float>?, - animationScope: CoroutineScope? = null, -) { - suspend fun animateOffset(targetContent: T, spec: AnimationSpec<Float>?) { - if (state.transitionState != animation.contentTransition || animation.isAnimatingOffset()) { - return - } - - animation.animateOffset( - initialVelocity = 0f, - targetContent = targetContent, - - // Important: we have to specify a spec that correctly animates *progress* (low - // visibility threshold) and not *offset* (higher visibility threshold). - spec = spec ?: animation.contentTransition.transformationSpec.progressSpec, - ) - } - - coroutineScope { - val collectionJob = launch { - try { - progress.collectLatest { progress -> - // Progress based animation should never overscroll given that the - // absoluteDistance exposed to overscroll builders is always 1f and will not - // lead to any noticeable transformation. - animation.dragOffset = progress.fastCoerceIn(0f, 1f) - } - - // Transition committed. - animateOffset(animation.toContent, commitSpec) - } catch (e: CancellationException) { - // Transition cancelled. - animateOffset(animation.fromContent, cancelSpec) - } - } - - // Start the transition. - animationScope?.launch { startTransition(state, animation, collectionJob) } - ?: startTransition(state, animation, collectionJob) - } -} - -private suspend fun <T : ContentKey> startTransition( - state: MutableSceneTransitionLayoutStateImpl, - animation: SwipeAnimation<T>, - progressCollectionJob: Job, -) { - state.startTransition(animation.contentTransition) - // The transition is done. Cancel the collection in case the transition was finished - // because it was interrupted by another transition. - if (progressCollectionJob.isActive) { - progressCollectionJob.cancel() - } -} 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 deleted file mode 100644 index 1b5341b8048a..000000000000 --- a/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/IntIndexedMap.kt +++ /dev/null @@ -1,73 +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.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/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt index 059112abb1b1..969003cb92f3 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 @@ -14,11 +14,17 @@ * limitations under the License. */ +@file:OptIn(ExperimentalMaterial3ExpressiveApi::class) + package com.android.compose.animation.scene +import androidx.compose.animation.SplineBasedFloatDecayAnimationSpec import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.generateDecayAnimationSpec import androidx.compose.animation.core.spring import androidx.compose.foundation.overscroll +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi +import androidx.compose.material3.MotionScheme import androidx.compose.material3.Text import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset @@ -40,6 +46,7 @@ import com.android.compose.animation.scene.content.state.TransitionState.Compani import com.android.compose.animation.scene.content.state.TransitionState.Transition import com.android.compose.animation.scene.subjects.assertThat import com.android.compose.gesture.NestedDraggable +import com.android.compose.gesture.effect.OffsetOverscrollEffectFactory import com.android.compose.test.MonotonicClockTestScope import com.android.compose.test.runMonotonicClockTest import com.android.mechanics.spec.InputDirection @@ -67,25 +74,28 @@ class DraggableHandlerTest { canChangeScene = { canChangeScene(it) }, ) + val defaultEffectFactory = + OffsetOverscrollEffectFactory(testScope, MotionScheme.standard().defaultSpatialSpec()) + var layoutDirection = LayoutDirection.Rtl set(value) { field = value - layoutImpl.updateContents(scenesBuilder, layoutDirection) + layoutImpl.updateContents(scenesBuilder, layoutDirection, defaultEffectFactory) } var mutableUserActionsA = mapOf(Swipe.Up to SceneB, Swipe.Down to SceneC) set(value) { field = value - layoutImpl.updateContents(scenesBuilder, layoutDirection) + layoutImpl.updateContents(scenesBuilder, layoutDirection, defaultEffectFactory) } var mutableUserActionsB = mapOf(Swipe.Up to SceneC, Swipe.Down to SceneA) set(value) { field = value - layoutImpl.updateContents(scenesBuilder, layoutDirection) + layoutImpl.updateContents(scenesBuilder, layoutDirection, defaultEffectFactory) } - private val scenesBuilder: SceneTransitionLayoutScope.() -> Unit = { + private val scenesBuilder: SceneTransitionLayoutScope<ContentScope>.() -> Unit = { scene(key = SceneA, userActions = mutableUserActionsA) { Text("SceneA") } scene(key = SceneB, userActions = mutableUserActionsB) { Text("SceneB") } scene( @@ -111,10 +121,11 @@ class DraggableHandlerTest { val transitionInterceptionThreshold = 0.05f val directionChangeSlop = 10f + private val density = Density(1f) private val layoutImpl = SceneTransitionLayoutImpl( state = layoutState, - density = Density(1f), + density = density, layoutDirection = LayoutDirection.Ltr, swipeSourceDetector = DefaultEdgeDetector, swipeDetector = DefaultSwipeDetector, @@ -125,6 +136,9 @@ class DraggableHandlerTest { // work well with advanceUntilIdle(), which is used by some tests. animationScope = testScope, directionChangeSlop = directionChangeSlop, + defaultEffectFactory = defaultEffectFactory, + decayAnimationSpec = + SplineBasedFloatDecayAnimationSpec(density).generateDecayAnimationSpec(), ) .apply { setContentsAndLayoutTargetSizeForTest(LAYOUT_SIZE) } 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 8d0af9b57f2a..f625add0648b 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 @@ -18,6 +18,7 @@ package com.android.compose.animation.scene import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.tween +import androidx.compose.foundation.LocalOverscrollFactory import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.gestures.rememberScrollableState import androidx.compose.foundation.gestures.scrollable @@ -32,6 +33,7 @@ import androidx.compose.foundation.overscroll import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.PagerState import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.SideEffect import androidx.compose.runtime.getValue @@ -72,6 +74,7 @@ import com.android.compose.animation.scene.TestScenes.SceneB import com.android.compose.animation.scene.TestScenes.SceneC import com.android.compose.animation.scene.subjects.assertThat import com.android.compose.gesture.effect.OffsetOverscrollEffect +import com.android.compose.gesture.effect.rememberOffsetOverscrollEffectFactory import com.android.compose.test.assertSizeIsEqualTo import com.android.compose.test.setContentAndCreateMainScope import com.android.compose.test.transition @@ -668,16 +671,20 @@ class ElementTest { 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) - ) + CompositionLocalProvider( + LocalOverscrollFactory provides rememberOffsetOverscrollEffectFactory() + ) { + 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) + ) + } } } } @@ -724,20 +731,24 @@ class ElementTest { 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) - ) + CompositionLocalProvider( + LocalOverscrollFactory provides rememberOffsetOverscrollEffectFactory() + ) { + 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) + ) + } } } } @@ -820,16 +831,20 @@ class ElementTest { 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() - ) + CompositionLocalProvider( + LocalOverscrollFactory provides rememberOffsetOverscrollEffectFactory() + ) { + 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() + ) + } } } } @@ -875,27 +890,31 @@ class ElementTest { rule.setContent { density = LocalDensity.current touchSlop = LocalViewConfiguration.current.touchSlop - SceneTransitionLayout( - state = state, - modifier = Modifier.size(layoutWidth, layoutHeight), + CompositionLocalProvider( + LocalOverscrollFactory provides rememberOffsetOverscrollEffectFactory() ) { - scene(SceneA, userActions = mapOf(Swipe.Down to SceneB)) { - Box( - Modifier - // A scrollable that does not consume the scroll gesture - .scrollable( - state = rememberScrollableState(consumeScrollDelta = { 0f }), - orientation = Orientation.Vertical, - ) - .fillMaxSize() - ) - } - scene(SceneB) { - Spacer( - Modifier.overscroll(verticalOverscrollEffect) - .element(TestElements.Foo) - .fillMaxSize() - ) + SceneTransitionLayout( + state = state, + modifier = Modifier.size(layoutWidth, layoutHeight), + ) { + scene(SceneA, userActions = mapOf(Swipe.Down to SceneB)) { + Box( + Modifier + // A scrollable that does not consume the scroll gesture + .scrollable( + state = rememberScrollableState(consumeScrollDelta = { 0f }), + orientation = Orientation.Vertical, + ) + .fillMaxSize() + ) + } + scene(SceneB) { + Spacer( + Modifier.overscroll(verticalOverscrollEffect) + .element(TestElements.Foo) + .fillMaxSize() + ) + } } } } diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementContentPickerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementContentPickerTest.kt index c8e7e6592e17..fba0aa88ef7a 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementContentPickerTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementContentPickerTest.kt @@ -33,8 +33,8 @@ class MovableElementContentPickerTest { picker.contentDuringTransition( TestElements.Foo, transition(from = TestScenes.SceneA, to = TestScenes.SceneB), - fromContentZIndex = 0f, - toContentZIndex = 1f, + fromContentZIndex = 0, + toContentZIndex = 1, ) ) .isEqualTo(TestScenes.SceneB) @@ -47,8 +47,8 @@ class MovableElementContentPickerTest { picker.contentDuringTransition( TestElements.Foo, transition(from = TestScenes.SceneA, to = TestScenes.SceneB), - fromContentZIndex = 0f, - toContentZIndex = 1f, + fromContentZIndex = 0, + toContentZIndex = 1, ) ) .isEqualTo(TestScenes.SceneA) @@ -61,8 +61,8 @@ class MovableElementContentPickerTest { picker.contentDuringTransition( TestElements.Foo, transition(from = TestScenes.SceneA, to = TestScenes.SceneB), - fromContentZIndex = 0f, - toContentZIndex = 1f, + fromContentZIndex = 0, + toContentZIndex = 1, ) } } diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt index 8d718c1418e0..e023936eb448 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt @@ -166,14 +166,14 @@ class MovableElementTest { override fun contentDuringTransition( element: ElementKey, transition: TransitionState.Transition, - fromContentZIndex: Float, - toContentZIndex: Float, + fromContentZIndex: Long, + toContentZIndex: Long, ): ContentKey { transition as TransitionState.Transition.ChangeScene assertThat(transition).hasFromScene(SceneA) assertThat(transition).hasToScene(SceneB) - assertThat(fromContentZIndex).isEqualTo(0) - assertThat(toContentZIndex).isEqualTo(1) + assertThat(fromContentZIndex).isEqualTo(1_000_000_000_000_000) + assertThat(toContentZIndex).isEqualTo(2_000_000_000_000_000) // Compose Foo in Scene A if progress < 0.65f, otherwise compose it // in Scene B. @@ -362,8 +362,8 @@ class MovableElementTest { override fun contentDuringTransition( element: ElementKey, transition: TransitionState.Transition, - fromContentZIndex: Float, - toContentZIndex: Float, + fromContentZIndex: Long, + toContentZIndex: Long, ): ContentKey { return SceneA } diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt index 50bfbfe6d29c..04c762f43907 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt @@ -18,21 +18,30 @@ package com.android.compose.animation.scene import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.tween +import androidx.compose.foundation.LocalOverscrollFactory +import androidx.compose.foundation.OverscrollEffect +import androidx.compose.foundation.OverscrollFactory import androidx.compose.foundation.ScrollState import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.size +import androidx.compose.foundation.rememberOverscrollEffect import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.SideEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.input.nestedscroll.NestedScrollSource +import androidx.compose.ui.platform.LocalViewConfiguration import androidx.compose.ui.platform.testTag import androidx.compose.ui.test.SemanticsMatcher import androidx.compose.ui.test.assertIsDisplayed @@ -46,11 +55,15 @@ import androidx.compose.ui.test.onRoot import androidx.compose.ui.test.performTouchInput import androidx.compose.ui.test.swipe import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.Velocity import androidx.compose.ui.unit.dp import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.compose.animation.scene.TestOverlays.OverlayA import com.android.compose.animation.scene.TestOverlays.OverlayB +import com.android.compose.animation.scene.TestOverlays.OverlayC +import com.android.compose.animation.scene.TestOverlays.OverlayD import com.android.compose.animation.scene.TestScenes.SceneA +import com.android.compose.animation.scene.UserActionResult.ShowOverlay import com.android.compose.animation.scene.subjects.assertThat import com.android.compose.test.assertSizeIsEqualTo import com.android.compose.test.setContentAndCreateMainScope @@ -821,4 +834,124 @@ class OverlayTest { assertThat(state.transitionState).isIdle() assertThat(state.transitionState).hasCurrentOverlays(/* empty */ ) } + + @Test + fun showOverlay_hideAllOverlays() { + val state = + rule.runOnUiThread { + MutableSceneTransitionLayoutStateForTests( + SceneA, + initialOverlays = setOf(OverlayA, OverlayB, OverlayC), + // We don't allow overlay C to be hidden. + canHideOverlay = { it != OverlayC }, + ) + } + + var touchSlop = 0f + rule.setContent { + touchSlop = LocalViewConfiguration.current.touchSlop + SceneTransitionLayout(state) { + scene(SceneA) { Box(Modifier.fillMaxSize()) } + overlay(OverlayA) { Box(Modifier.fillMaxSize()) } + overlay(OverlayB) { Box(Modifier.fillMaxSize()) } + overlay( + OverlayC, + mapOf( + Swipe.Down to + ShowOverlay( + OverlayD, + hideCurrentOverlays = ShowOverlay.HideCurrentOverlays.All, + ) + ), + ) { + Box(Modifier.fillMaxSize()) + } + overlay(OverlayD) { Box(Modifier.fillMaxSize()) } + } + } + + assertThat(state.transitionState).hasCurrentOverlays(OverlayA, OverlayB, OverlayC) + + rule.onRoot().performTouchInput { + down(center) + moveBy(Offset(0f, touchSlop)) + } + + // We closed all overlay, but C can not be hidden. + val transition = assertThat(state.transitionState).isShowOrHideOverlayTransition() + assertThat(transition).hasCurrentScene(SceneA) + assertThat(transition).hasCurrentOverlays(OverlayC) + assertThat(transition).hasProgress(0f) + assertThat(transition).hasOverlay(OverlayD) + + rule.onRoot().performTouchInput { moveBy(Offset(0f, bottom / 2f)) } + assertThat(transition).hasProgress(0.5f) + + rule.onRoot().performTouchInput { up() } + rule.waitForIdle() + assertThat(state.transitionState).isIdle() + assertThat(state.transitionState).hasCurrentScene(SceneA) + assertThat(state.transitionState).hasCurrentOverlays(OverlayC, OverlayD) + } + + @Test + fun effectFactory() { + val effects = mutableSetOf<OverscrollEffect>() + var customFactory by mutableStateOf<OverscrollFactory?>(null) + rule.setContent { + CompositionLocalProvider(LocalOverscrollFactory provides DefaultEffectFactory) { + SceneTransitionLayout( + remember { MutableSceneTransitionLayoutStateForTests(SceneA) } + ) { + scene(SceneA, effectFactory = customFactory) { + val effect = checkNotNull(rememberOverscrollEffect()) + SideEffect { + effects.add(effect) + effects.add(horizontalOverscrollEffect) + effects.add(verticalOverscrollEffect) + } + } + } + } + } + + assertThat(effects.size).isEqualTo(3) + effects.forEach { assertThat(it).isInstanceOf(DefaultEffect::class.java) } + + effects.clear() + customFactory = CustomEffectFactory + rule.waitForIdle() + + assertThat(effects.size).isEqualTo(3) + effects.forEach { assertThat(it).isInstanceOf(CustomEffect::class.java) } + } + + private abstract class NoOpEffect : OverscrollEffect { + override val isInProgress: Boolean = false + + override suspend fun applyToFling( + velocity: Velocity, + performFling: suspend (Velocity) -> Velocity, + ) { + performFling(velocity) + } + + override fun applyToScroll( + delta: Offset, + source: NestedScrollSource, + performScroll: (Offset) -> Offset, + ): Offset = performScroll(delta) + } + + private class DefaultEffect : NoOpEffect() + + private class CustomEffect : NoOpEffect() + + private data object DefaultEffectFactory : OverscrollFactory { + override fun createOverscrollEffect(): OverscrollEffect = DefaultEffect() + } + + private data object CustomEffectFactory : OverscrollFactory { + override fun createOverscrollEffect(): OverscrollEffect = CustomEffect() + } } diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt index 9f15ebd69657..2bf235846b32 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt @@ -32,9 +32,11 @@ import androidx.compose.ui.unit.dp import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.compose.animation.scene.TestOverlays.OverlayA import com.android.compose.animation.scene.TestOverlays.OverlayB +import com.android.compose.animation.scene.TestOverlays.OverlayC 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.UserActionResult.ShowOverlay import com.android.compose.animation.scene.subjects.assertThat import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.CoroutineScope @@ -273,6 +275,57 @@ class PredictiveBackHandlerTest { rule.onNode(hasTestTag(OverlayB.testTag)).assertDoesNotExist() } + @Test + fun showOverlay_hideSomeOverlays() { + val state = + rule.runOnUiThread { + MutableSceneTransitionLayoutStateForTests( + SceneA, + initialOverlays = setOf(OverlayA, OverlayB), + ) + } + + rule.setContent { + SceneTransitionLayout(state) { + scene(SceneA) { Box(Modifier.fillMaxSize()) } + overlay(OverlayA) { Box(Modifier.fillMaxSize()) } + overlay( + OverlayB, + mapOf( + Back to + ShowOverlay( + OverlayC, + hideCurrentOverlays = ShowOverlay.HideCurrentOverlays.Some(OverlayA), + ) + ), + ) { + Box(Modifier.fillMaxSize()) + } + overlay(OverlayC) { Box(Modifier.fillMaxSize()) } + } + } + + assertThat(state.transitionState).hasCurrentOverlays(OverlayA, OverlayB) + + val dispatcher = rule.activity.onBackPressedDispatcher + rule.runOnUiThread { dispatcher.dispatchOnBackStarted(backEvent()) } + + val transition = assertThat(state.transitionState).isShowOrHideOverlayTransition() + assertThat(transition).hasCurrentScene(SceneA) + assertThat(transition).hasCurrentOverlays(OverlayB) + assertThat(transition).hasProgress(0f) + assertThat(transition).hasOverlay(OverlayC) + + rule.runOnUiThread { dispatcher.dispatchOnBackProgressed(backEvent(progress = 0.5f)) } + assertThat(transition).hasProgress(0.5f) + + rule.runOnUiThread { dispatcher.onBackPressed() } + rule.waitForIdle() + assertThat(state.transitionState).isIdle() + assertThat(state.transitionState).hasCurrentScene(SceneA) + assertThat(state.transitionState).hasCurrentOverlays(OverlayB, OverlayC) + } + private fun backEvent(progress: Float = 0f): BackEventCompat { return BackEventCompat( touchX = 0f, 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 c5e4061e834a..26f3c259dca9 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 @@ -24,18 +24,14 @@ 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.subjects.assertThat -import com.android.compose.animation.scene.transition.seekToScene import com.android.compose.test.TestSceneTransition import com.android.compose.test.runMonotonicClockTest import com.android.compose.test.transition import com.google.common.truth.Truth.assertThat -import kotlin.coroutines.cancellation.CancellationException import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.cancelAndJoin -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.flow.consumeAsFlow import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runCurrent @@ -282,77 +278,6 @@ class SceneTransitionLayoutStateTest { } @Test - fun seekToScene() = runMonotonicClockTest { - val state = MutableSceneTransitionLayoutStateForTests(SceneA) - val progress = Channel<Float>() - - val job = - launch(start = CoroutineStart.UNDISPATCHED) { - state.seekToScene(SceneB, progress.consumeAsFlow()) - } - - val transition = assertThat(state.transitionState).isSceneTransition() - assertThat(transition).hasFromScene(SceneA) - assertThat(transition).hasToScene(SceneB) - assertThat(transition).hasProgress(0f) - - // Change progress. - progress.send(0.4f) - assertThat(transition).hasProgress(0.4f) - - // Close the channel normally to confirm the transition. - progress.close() - job.join() - assertThat(state.transitionState).isIdle() - assertThat(state.transitionState).hasCurrentScene(SceneB) - } - - @Test - fun seekToScene_cancelled() = runMonotonicClockTest { - val state = MutableSceneTransitionLayoutStateForTests(SceneA) - val progress = Channel<Float>() - - val job = - launch(start = CoroutineStart.UNDISPATCHED) { - state.seekToScene(SceneB, progress.consumeAsFlow()) - } - - val transition = assertThat(state.transitionState).isSceneTransition() - assertThat(transition).hasFromScene(SceneA) - assertThat(transition).hasToScene(SceneB) - assertThat(transition).hasProgress(0f) - - // Change progress. - progress.send(0.4f) - assertThat(transition).hasProgress(0.4f) - - // Close the channel with a CancellationException to cancel the transition. - progress.close(CancellationException()) - job.join() - assertThat(state.transitionState).isIdle() - assertThat(state.transitionState).hasCurrentScene(SceneA) - } - - @Test - fun seekToScene_interrupted() = runMonotonicClockTest { - val state = MutableSceneTransitionLayoutStateForTests(SceneA) - val progress = Channel<Float>() - - val job = - launch(start = CoroutineStart.UNDISPATCHED) { - state.seekToScene(SceneB, progress.consumeAsFlow()) - } - - assertThat(state.transitionState).isSceneTransition() - - // Start a new transition, interrupting the seek transition. - state.setTargetScene(SceneB, animationScope = this) - - // The previous job is cancelled and does not infinitely collect the progress. - job.join() - } - - @Test fun replacedTransitionIsRemovedFromFinishedTransitions() = runTest { val state = MutableSceneTransitionLayoutStateForTests(SceneA) diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/subjects/TransitionStateSubject.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/subjects/TransitionStateSubject.kt index 8db7dbca2474..0bd51cd9822d 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/subjects/TransitionStateSubject.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/subjects/TransitionStateSubject.kt @@ -129,22 +129,22 @@ abstract class BaseTransitionSubject<T : TransitionState.Transition>( check("currentOverlays").that(actual.currentOverlays).containsExactlyElementsIn(overlays) } - fun hasProgress(progress: Float, tolerance: Float = 0f) { + fun hasProgress(progress: Float, tolerance: Float = 0.01f) { check("progress").that(actual.progress).isWithin(tolerance).of(progress) } - fun hasProgressVelocity(progressVelocity: Float, tolerance: Float = 0f) { + fun hasProgressVelocity(progressVelocity: Float, tolerance: Float = 0.01f) { check("progressVelocity") .that(actual.progressVelocity) .isWithin(tolerance) .of(progressVelocity) } - fun hasPreviewProgress(progress: Float, tolerance: Float = 0f) { + fun hasPreviewProgress(progress: Float, tolerance: Float = 0.01f) { check("previewProgress").that(actual.previewProgress).isWithin(tolerance).of(progress) } - fun hasPreviewProgressVelocity(progressVelocity: Float, tolerance: Float = 0f) { + fun hasPreviewProgressVelocity(progressVelocity: Float, tolerance: Float = 0.01f) { check("previewProgressVelocity") .that(actual.previewProgressVelocity) .isWithin(tolerance) diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedSceneTransitionLayoutTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedSceneTransitionLayoutTest.kt new file mode 100644 index 000000000000..0042f5018957 --- /dev/null +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedSceneTransitionLayoutTest.kt @@ -0,0 +1,76 @@ +/* + * 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.ui.Modifier +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.compose.animation.scene.MutableSceneTransitionLayoutStateForTests +import com.android.compose.animation.scene.SceneTransitionLayoutForTesting +import com.android.compose.animation.scene.SceneTransitionLayoutImpl +import com.android.compose.animation.scene.TestScenes +import com.google.common.truth.Truth.assertThat +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class NestedSceneTransitionLayoutTest { + @get:Rule val rule = createComposeRule() + + @Test + fun nestedStls_testZIndex() { + var nullableLayoutImpl: SceneTransitionLayoutImpl? = null + + rule.setContent { + SceneTransitionLayoutForTesting( + state = MutableSceneTransitionLayoutStateForTests(TestScenes.SceneA) + ) { + scene(TestScenes.SceneA) { + NestedSceneTransitionLayoutForTesting( + MutableSceneTransitionLayoutStateForTests(TestScenes.SceneD), + Modifier, + onLayoutImpl = null, + ) { + scene(TestScenes.SceneC) {} + scene(TestScenes.SceneD) { + NestedSceneTransitionLayoutForTesting( + MutableSceneTransitionLayoutStateForTests(TestScenes.SceneE), + Modifier, + onLayoutImpl = { nullableLayoutImpl = it }, + ) { + scene(TestScenes.SceneE) {} + } + } + } + } + scene(TestScenes.SceneB) {} + } + + assertThat(nullableLayoutImpl?.content(TestScenes.SceneA)?.globalZIndex) + .isEqualTo(1_000_000_000_000_000) + assertThat(nullableLayoutImpl?.content(TestScenes.SceneB)?.globalZIndex) + .isEqualTo(2_000_000_000_000_000) + assertThat(nullableLayoutImpl?.content(TestScenes.SceneC)?.globalZIndex) + .isEqualTo(1_001_000_000_000_000) + assertThat(nullableLayoutImpl?.content(TestScenes.SceneD)?.globalZIndex) + .isEqualTo(1_002_000_000_000_000) + assertThat(nullableLayoutImpl?.content(TestScenes.SceneE)?.globalZIndex) + .isEqualTo(1_002_001_000_000_000) + } + } +} 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 index 83dd6d3eec28..f44ae90746d5 100644 --- 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 @@ -38,6 +38,7 @@ 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.ElementKey import com.android.compose.animation.scene.MutableSceneTransitionLayoutState import com.android.compose.animation.scene.MutableSceneTransitionLayoutStateForTests import com.android.compose.animation.scene.SceneKey @@ -77,11 +78,14 @@ class NestedSharedElementTest { ) @Composable - private fun ContentScope.SharedElement(element: SharedElement) { + private fun ContentScope.SharedElement( + element: SharedElement, + key: ElementKey = TestElements.Foo, + ) { Box(Modifier.fillMaxSize()) { Box( Modifier.offset(element.x, element.y) - .element(TestElements.Foo) + .element(key) .size(element.width, element.height) .background(element.color) .alpha(element.alpha) @@ -165,9 +169,9 @@ class NestedSharedElementTest { ) { before { onElement(TestElements.Foo).assertElementVariant(elementVariant1) } atAllFrames(4) { - onElement(TestElements.Foo, TestScenes.SceneB).assertIsNotDisplayed() + onElement(TestElements.Foo, TestScenes.SceneA).assertIsNotDisplayed() - onElement(TestElements.Foo, TestScenes.SceneA) + onElement(TestElements.Foo, TestScenes.SceneB) .assertBetweenElementVariants(elementVariant1, elementVariant2, this) } after { onElement(TestElements.Foo).assertElementVariant(elementVariant2) } @@ -175,6 +179,29 @@ class NestedSharedElementTest { } @Test + fun fromParentSTLtoNestedSTL_contentPickerLowestZOrder() { + rule.testTransition( + fromSceneContent = { SharedElement(elementVariant1, TestElements.LowZIndex) }, + toSceneContent = { + NestedSceneTransitionLayout(nestedState, modifier = Modifier) { + scene(Scenes.NestedSceneA) { + SharedElement(elementVariant2, TestElements.LowZIndex) + } + } + }, + ) { + before { onElement(TestElements.LowZIndex).assertElementVariant(elementVariant1) } + atAllFrames(4) { + onElement(TestElements.LowZIndex, TestScenes.SceneB).assertIsNotDisplayed() + + onElement(TestElements.LowZIndex, TestScenes.SceneA) + .assertBetweenElementVariants(elementVariant1, elementVariant2, this) + } + after { onElement(TestElements.LowZIndex).assertElementVariant(elementVariant2) } + } + } + + @Test fun nestedSharedElementTransition_fromParentSTLtoNestedNestedSTL() { rule.testTransition( fromSceneContent = contentWithSharedElement, @@ -182,9 +209,9 @@ class NestedSharedElementTest { ) { before { onElement(TestElements.Foo).assertElementVariant(elementVariant1) } atAllFrames(4) { - onElement(TestElements.Foo, TestScenes.SceneB).assertIsNotDisplayed() + onElement(TestElements.Foo, TestScenes.SceneA).assertIsNotDisplayed() - onElement(TestElements.Foo, TestScenes.SceneA) + onElement(TestElements.Foo, TestScenes.SceneB) .assertBetweenElementVariants(elementVariant1, elementVariant4, this) } after { onElement(TestElements.Foo).assertElementVariant(elementVariant4) } 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 deleted file mode 100644 index d7a9b9007be0..000000000000 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/ui/util/IntIndexMapTest.kt +++ /dev/null @@ -1,92 +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.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/TestValues.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestValues.kt index 95ef2ce821e1..c6af0d4846e0 100644 --- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestValues.kt +++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestValues.kt @@ -27,18 +27,22 @@ object TestScenes { val SceneB = SceneKey("SceneB") val SceneC = SceneKey("SceneC") val SceneD = SceneKey("SceneD") + val SceneE = SceneKey("SceneE") } /** Overlay keys that can be reused by tests. */ object TestOverlays { val OverlayA = OverlayKey("OverlayA") val OverlayB = OverlayKey("OverlayB") + val OverlayC = OverlayKey("OverlayC") + val OverlayD = OverlayKey("OverlayD") } /** Element keys that can be reused by tests. */ object TestElements { val Foo = ElementKey("Foo") val Bar = ElementKey("Bar") + val LowZIndex = ElementKey("LowZIndex", contentPicker = LowestZIndexContentPicker) } /** Value keys that can be reused by tests. */ diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt index f6ff3268fca4..36029177d4f6 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt @@ -17,6 +17,7 @@ import android.content.Context import android.content.res.Resources import android.graphics.Typeface import android.view.LayoutInflater +import com.android.systemui.animation.GSFAxes import com.android.systemui.customization.R import com.android.systemui.log.core.MessageBuffer import com.android.systemui.plugins.clocks.ClockController @@ -107,18 +108,18 @@ class DefaultClockProvider( // TODO(b/364681643): Variations for retargetted DIGITAL_CLOCK_FLEX val LEGACY_FLEX_LS_VARIATION = listOf( - ClockFontAxisSetting("wght", 600f), - ClockFontAxisSetting("wdth", 100f), - ClockFontAxisSetting("ROND", 100f), - ClockFontAxisSetting("slnt", 0f), + ClockFontAxisSetting(GSFAxes.WEIGHT, 600f), + ClockFontAxisSetting(GSFAxes.WIDTH, 100f), + ClockFontAxisSetting(GSFAxes.ROUND, 100f), + ClockFontAxisSetting(GSFAxes.SLANT, 0f), ) val LEGACY_FLEX_AOD_VARIATION = listOf( - ClockFontAxisSetting("wght", 74f), - ClockFontAxisSetting("wdth", 43f), - ClockFontAxisSetting("ROND", 100f), - ClockFontAxisSetting("slnt", 0f), + ClockFontAxisSetting(GSFAxes.WEIGHT, 74f), + ClockFontAxisSetting(GSFAxes.WIDTH, 43f), + ClockFontAxisSetting(GSFAxes.ROUND, 100f), + ClockFontAxisSetting(GSFAxes.SLANT, 0f), ) val FLEX_TYPEFACE by lazy { 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 e69fa994931d..cc3769e0a568 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt @@ -16,6 +16,7 @@ package com.android.systemui.shared.clocks +import com.android.systemui.animation.GSFAxes import com.android.systemui.customization.R import com.android.systemui.plugins.clocks.AlarmData import com.android.systemui.plugins.clocks.AxisType @@ -122,16 +123,16 @@ class FlexClockController(private val clockCtx: ClockContext) : ClockController val FONT_AXES = listOf( ClockFontAxis( - key = "wght", + key = GSFAxes.WEIGHT, type = AxisType.Float, - minValue = 1f, + minValue = 25f, currentValue = 400f, maxValue = 1000f, name = "Weight", description = "Glyph Weight", ), ClockFontAxis( - key = "wdth", + key = GSFAxes.WIDTH, type = AxisType.Float, minValue = 25f, currentValue = 100f, @@ -140,7 +141,7 @@ class FlexClockController(private val clockCtx: ClockContext) : ClockController description = "Glyph Width", ), ClockFontAxis( - key = "ROND", + key = GSFAxes.ROUND, type = AxisType.Boolean, minValue = 0f, currentValue = 0f, @@ -149,7 +150,7 @@ class FlexClockController(private val clockCtx: ClockContext) : ClockController description = "Glyph Roundness", ), ClockFontAxis( - key = "slnt", + key = GSFAxes.SLANT, type = AxisType.Boolean, minValue = 0f, currentValue = 0f, 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 827bd6898310..e2bbe0fef3c0 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 @@ -21,6 +21,7 @@ import android.view.Gravity import android.view.View import android.view.ViewGroup.LayoutParams.MATCH_PARENT import android.widget.FrameLayout +import com.android.systemui.animation.GSFAxes import com.android.systemui.customization.R import com.android.systemui.plugins.clocks.AlarmData import com.android.systemui.plugins.clocks.ClockAnimations @@ -125,7 +126,19 @@ class FlexClockFaceController(clockCtx: ClockContext, private val isLargeClock: layerController.faceEvents.onThemeChanged(theme) } - override fun onFontAxesChanged(axes: List<ClockFontAxisSetting>) { + override fun onFontAxesChanged(settings: List<ClockFontAxisSetting>) { + var axes = settings + if (!isLargeClock) { + axes = + axes.map { axis -> + if (axis.key == GSFAxes.WIDTH && axis.value > SMALL_CLOCK_MAX_WDTH) { + axis.copy(value = SMALL_CLOCK_MAX_WDTH) + } else { + axis + } + } + } + layerController.events.onFontAxesChanged(axes) } @@ -236,6 +249,7 @@ class FlexClockFaceController(clockCtx: ClockContext, private val isLargeClock: } companion object { + val SMALL_CLOCK_MAX_WDTH = 120f val SMALL_LAYER_CONFIG = LayerConfig( timespec = DigitalTimespec.TIME_FULL_FORMAT, diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt index c40bb9a5ebea..3eb519186a3e 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt @@ -139,13 +139,49 @@ class FlexClockView(clockCtx: ClockContext) : FrameLayout(clockCtx.context) { digitalClockTextViewMap.forEach { (_, textView) -> textView.refreshText() } } + override fun setVisibility(visibility: Int) { + if (visibility != this.visibility) { + logger.d({ "setVisibility(${str1 ?: int1})" }) { + int1 = visibility + str1 = + when (visibility) { + VISIBLE -> "VISIBLE" + INVISIBLE -> "INVISIBLE" + GONE -> "GONE" + else -> null + } + } + } + + super.setVisibility(visibility) + } + + private var loggedAlpha = 1000f + + override fun setAlpha(alpha: Float) { + val delta = if (alpha <= 0f || alpha >= 1f) 0.001f else 0.5f + if (abs(loggedAlpha - alpha) >= delta) { + loggedAlpha = alpha + logger.d({ "setAlpha($double1)" }) { double1 = alpha.toDouble() } + } + super.setAlpha(alpha) + } + + private val isDrawn: Boolean + get() = (mPrivateFlags and 0x20 /* PFLAG_DRAWN */) > 0 + override fun invalidate() { - logger.d("invalidate()") + if (isDrawn && visibility == VISIBLE) { + logger.d("invalidate()") + } + super.invalidate() } override fun requestLayout() { - logger.d("requestLayout()") + if (!isLayoutRequested()) { + logger.d("requestLayout()") + } super.requestLayout() } diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt index 2b0825f39243..fbd5887c5b54 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt @@ -34,6 +34,7 @@ import android.view.View.MeasureSpec.EXACTLY import android.view.animation.Interpolator import android.widget.TextView import com.android.internal.annotations.VisibleForTesting +import com.android.systemui.animation.GSFAxes import com.android.systemui.animation.TextAnimator import com.android.systemui.customization.R import com.android.systemui.log.core.Logger @@ -44,6 +45,7 @@ import com.android.systemui.shared.clocks.DimensionParser import com.android.systemui.shared.clocks.FontTextStyle import com.android.systemui.shared.clocks.LogUtil import java.lang.Thread +import kotlin.math.abs import kotlin.math.max import kotlin.math.min @@ -206,7 +208,10 @@ open class SimpleDigitalClockTextView(clockCtx: ClockContext, attrs: AttributeSe } override fun onDraw(canvas: Canvas) { - logger.d({ "onDraw(); ls: $str1" }) { str1 = textAnimator.textInterpolator.shapedText } + logger.d({ "onDraw(${str1?.replace("\n", "\\n")})" }) { + str1 = textAnimator.textInterpolator.shapedText + } + val translation = getLocalTranslation() canvas.translate(translation.x.toFloat(), translation.y.toFloat()) digitTranslateAnimator?.let { @@ -221,8 +226,42 @@ open class SimpleDigitalClockTextView(clockCtx: ClockContext, attrs: AttributeSe canvas.translate(-translation.x.toFloat(), -translation.y.toFloat()) } + override fun setVisibility(visibility: Int) { + if (visibility != this.visibility) { + logger.d({ "setVisibility(${str1 ?: int1})" }) { + int1 = visibility + str1 = + when (visibility) { + VISIBLE -> "VISIBLE" + INVISIBLE -> "INVISIBLE" + GONE -> "GONE" + else -> null + } + } + } + + super.setVisibility(visibility) + } + + private var loggedAlpha = 1000f + + override fun setAlpha(alpha: Float) { + val delta = if (alpha <= 0f || alpha >= 1f) 0.001f else 0.5f + if (abs(loggedAlpha - alpha) >= delta) { + loggedAlpha = alpha + logger.d({ "setAlpha($double1)" }) { double1 = alpha.toDouble() } + } + super.setAlpha(alpha) + } + + private val isDrawn: Boolean + get() = (mPrivateFlags and 0x20 /* PFLAG_DRAWN */) > 0 + override fun invalidate() { - logger.d("invalidate()") + if (isDrawn && visibility == VISIBLE) { + logger.d("invalidate()") + } + super.invalidate() (parent as? FlexClockView)?.invalidate() } @@ -490,22 +529,22 @@ open class SimpleDigitalClockTextView(clockCtx: ClockContext, attrs: AttributeSe Paint().also { it.xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_OUT) } val AOD_COLOR = Color.WHITE - val OPTICAL_SIZE_AXIS = ClockFontAxisSetting("opsz", 144f) + val OPTICAL_SIZE_AXIS = ClockFontAxisSetting(GSFAxes.OPTICAL_SIZE, 144f) val DEFAULT_LS_VARIATION = listOf( OPTICAL_SIZE_AXIS, - ClockFontAxisSetting("wght", 400f), - ClockFontAxisSetting("wdth", 100f), - ClockFontAxisSetting("ROND", 0f), - ClockFontAxisSetting("slnt", 0f), + ClockFontAxisSetting(GSFAxes.WEIGHT, 400f), + ClockFontAxisSetting(GSFAxes.WIDTH, 100f), + ClockFontAxisSetting(GSFAxes.ROUND, 0f), + ClockFontAxisSetting(GSFAxes.SLANT, 0f), ) val DEFAULT_AOD_VARIATION = listOf( OPTICAL_SIZE_AXIS, - ClockFontAxisSetting("wght", 200f), - ClockFontAxisSetting("wdth", 100f), - ClockFontAxisSetting("ROND", 0f), - ClockFontAxisSetting("slnt", 0f), + ClockFontAxisSetting(GSFAxes.WEIGHT, 200f), + ClockFontAxisSetting(GSFAxes.WIDTH, 100f), + ClockFontAxisSetting(GSFAxes.ROUND, 0f), + ClockFontAxisSetting(GSFAxes.SLANT, 0f), ) } } diff --git a/packages/SystemUI/docs/scene.md b/packages/SystemUI/docs/scene.md index bf15b4feadfb..aa3ac08b972c 100644 --- a/packages/SystemUI/docs/scene.md +++ b/packages/SystemUI/docs/scene.md @@ -68,12 +68,9 @@ file evaluates to `true`. 1. Set a collection of **aconfig flags** to `true` by running the following commands: ```console - $ adb shell device_config override systemui com.android.systemui.keyguard_bottom_area_refactor true - $ adb shell device_config override systemui com.android.systemui.keyguard_wm_state_refactor true - $ adb shell device_config override systemui com.android.systemui.migrate_clocks_to_blueprint true - $ adb shell device_config override systemui com.android.systemui.notification_avalanche_throttle_hun true - $ adb shell device_config override systemui com.android.systemui.predictive_back_sysui true - $ adb shell device_config override systemui com.android.systemui.scene_container true + $ adb shell aflags enable com.android.systemui.keyguard_wm_state_refactor --immediate + $ adb shell aflags enable com.android.systemui.notification_avalanche_throttle_hun --immediate + $ adb shell aflags enable com.android.systemui.scene_container --immediate ``` 2. **Restart** System UI by issuing the following command: ```console @@ -87,19 +84,16 @@ file evaluates to `true`. NOTE: this will be removed proper to the actual release of the framework. - *(b)* Turn on logging and look for the logging statements in `logcat`: - ```console - - # Turn on logging from the framework: + *(b)* look in logcat, see the "Checking if the framework is enabled" section + below. - $ adb shell cmd statusbar echo -b SceneFramework:verbose ### Checking if the framework is enabled Look for the log statements from the framework: ```console -$ adb logcat -v time SceneFramework:* *:S +$ adb logcat -s SceneFramework ``` ### Disabling the framework @@ -107,7 +101,7 @@ $ adb logcat -v time SceneFramework:* *:S To **disable** the framework, simply turn off the main aconfig flag: ```console -$ adb shell device_config put systemui com.android.systemui.scene_container false +$ adb shell aflags unset com.android.systemui.scene_container --immediate ``` ## Defining a scene diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt index 1841d2ea6132..d2b61c0ab745 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt @@ -783,7 +783,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { underTest.reinflateViewFlipper(onViewInflatedCallback) verify(viewFlipperController).clearViews() verify(viewFlipperController) - .asynchronouslyInflateView(any(), any(), onViewInflatedCallbackArgumentCaptor.capture()) + .getSecurityView(any(), any(), onViewInflatedCallbackArgumentCaptor.capture()) onViewInflatedCallbackArgumentCaptor.value.onViewInflated(inputViewController) verify(view).updateSecurityViewFlipper() } diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java index 7bb6ef1c8895..23e07b185a2e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java @@ -20,6 +20,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; @@ -129,23 +130,40 @@ public class KeyguardSecurityViewFlipperControllerTest extends SysuiTestCase { } @Test - public void asynchronouslyInflateView() { - mKeyguardSecurityViewFlipperController.asynchronouslyInflateView(SecurityMode.PIN, - mKeyguardSecurityCallback, null); - verify(mAsyncLayoutInflater).inflate(anyInt(), eq(mView), any( - AsyncLayoutInflater.OnInflateFinishedListener.class)); + public void asynchronouslyInflateView_setNeedsInput() { + mKeyguardSecurityViewFlipperController.clearViews(); + ArgumentCaptor<AsyncLayoutInflater.OnInflateFinishedListener> argumentCaptor = + ArgumentCaptor.forClass(AsyncLayoutInflater.OnInflateFinishedListener.class); + mKeyguardSecurityViewFlipperController.getSecurityView(SecurityMode.PIN, + mKeyguardSecurityCallback, controller -> {}); + verify(mAsyncLayoutInflater).inflate(anyInt(), eq(mView), argumentCaptor.capture()); + argumentCaptor.getValue().onInflateFinished( + LayoutInflater.from(getContext()).inflate(R.layout.keyguard_pin_view, null), + R.layout.keyguard_pin_view, mView); } @Test - public void asynchronouslyInflateView_setNeedsInput() { + public void getSecurityView_multipleInvocations_callsAsyncInflateOnce() { + mKeyguardSecurityViewFlipperController.clearViews(); + // Make 2 calls to get security view + var callback1 = mock(KeyguardSecurityViewFlipperController.OnViewInflatedCallback.class); + var callback2 = mock(KeyguardSecurityViewFlipperController.OnViewInflatedCallback.class); + mKeyguardSecurityViewFlipperController.getSecurityView(SecurityMode.PIN, + mKeyguardSecurityCallback, callback1); + mKeyguardSecurityViewFlipperController.getSecurityView(SecurityMode.PIN, + mKeyguardSecurityCallback, callback2); + + // Verify inflation is called once... ArgumentCaptor<AsyncLayoutInflater.OnInflateFinishedListener> argumentCaptor = ArgumentCaptor.forClass(AsyncLayoutInflater.OnInflateFinishedListener.class); - mKeyguardSecurityViewFlipperController.asynchronouslyInflateView(SecurityMode.PIN, - mKeyguardSecurityCallback, null); verify(mAsyncLayoutInflater).inflate(anyInt(), eq(mView), argumentCaptor.capture()); argumentCaptor.getValue().onInflateFinished( - LayoutInflater.from(getContext()).inflate(R.layout.keyguard_password_view, null), - R.layout.keyguard_password_view, mView); + LayoutInflater.from(getContext()).inflate(R.layout.keyguard_pin_view, null), + R.layout.keyguard_pin_view, mView); + + // ... and both callbacks get invoked + verify(callback1).onViewInflated(mKeyguardInputViewController); + verify(callback2).onViewInflated(mKeyguardInputViewController); } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/FontVariationUtilsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/FontVariationUtilsTest.kt index b0f81c012cca..f44769d522eb 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/FontVariationUtilsTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/FontVariationUtilsTest.kt @@ -7,11 +7,6 @@ import junit.framework.Assert import org.junit.Test import org.junit.runner.RunWith -private const val TAG_WGHT = "wght" -private const val TAG_WDTH = "wdth" -private const val TAG_OPSZ = "opsz" -private const val TAG_ROND = "ROND" - @RunWith(AndroidJUnit4::class) @SmallTest class FontVariationUtilsTest : SysuiTestCase() { @@ -23,19 +18,22 @@ class FontVariationUtilsTest : SysuiTestCase() { weight = 100, width = 100, opticalSize = -1, - roundness = 100 + roundness = 100, ) - Assert.assertEquals("'$TAG_WGHT' 100, '$TAG_WDTH' 100, '$TAG_ROND' 100", initFvar) + Assert.assertEquals( + "'${GSFAxes.WEIGHT}' 100, '${GSFAxes.WIDTH}' 100, '${GSFAxes.ROUND}' 100", + initFvar, + ) val updatedFvar = fontVariationUtils.updateFontVariation( weight = 200, width = 100, opticalSize = 0, - roundness = 100 + roundness = 100, ) Assert.assertEquals( - "'$TAG_WGHT' 200, '$TAG_WDTH' 100, '$TAG_OPSZ' 0, '$TAG_ROND' 100", - updatedFvar + "'${GSFAxes.WEIGHT}' 200, '${GSFAxes.WIDTH}' 100, '${GSFAxes.OPTICAL_SIZE}' 0, '${GSFAxes.ROUND}' 100", + updatedFvar, ) } @@ -46,14 +44,14 @@ class FontVariationUtilsTest : SysuiTestCase() { weight = 100, width = 100, opticalSize = 0, - roundness = 100 + roundness = 100, ) val updatedFvar1 = fontVariationUtils.updateFontVariation( weight = 100, width = 100, opticalSize = 0, - roundness = 100 + roundness = 100, ) Assert.assertEquals("", updatedFvar1) val updatedFvar2 = fontVariationUtils.updateFontVariation() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/data/repository/ConfigurationRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/data/repository/ConfigurationRepositoryImplTest.kt index 3f4d3f8ba12a..205f94434970 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/data/repository/ConfigurationRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/data/repository/ConfigurationRepositoryImplTest.kt @@ -40,6 +40,7 @@ import org.mockito.Mock import org.mockito.Mockito.mock import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations +import org.mockito.kotlin.clearInvocations @ExperimentalCoroutinesApi @SmallTest @@ -65,11 +66,13 @@ class ConfigurationRepositoryImplTest : SysuiTestCase() { testScope.backgroundScope, displayUtils, ) + testScope.runCurrent() } @Test fun onAnyConfigurationChange_updatesOnUiModeChanged() = testScope.runTest { + clearInvocations(configurationController) val lastAnyConfigurationChange by collectLastValue(underTest.onAnyConfigurationChange) assertThat(lastAnyConfigurationChange).isNull() @@ -85,6 +88,7 @@ class ConfigurationRepositoryImplTest : SysuiTestCase() { @Test fun onAnyConfigurationChange_updatesOnThemeChanged() = testScope.runTest { + clearInvocations(configurationController) val lastAnyConfigurationChange by collectLastValue(underTest.onAnyConfigurationChange) assertThat(lastAnyConfigurationChange).isNull() @@ -101,7 +105,7 @@ class ConfigurationRepositoryImplTest : SysuiTestCase() { fun onMovedToDisplays_updatesOnMovedToDisplay() = testScope.runTest { val lastOnMovedToDisplay by collectLastValue(underTest.onMovedToDisplay) - assertThat(lastOnMovedToDisplay).isNull() + assertThat(lastOnMovedToDisplay).isEqualTo(Display.DEFAULT_DISPLAY) val configurationCallback = withArgCaptor { verify(configurationController).addCallback(capture()) @@ -115,6 +119,7 @@ class ConfigurationRepositoryImplTest : SysuiTestCase() { @Test fun onAnyConfigurationChange_updatesOnConfigChanged() = testScope.runTest { + clearInvocations(configurationController) val lastAnyConfigurationChange by collectLastValue(underTest.onAnyConfigurationChange) assertThat(lastAnyConfigurationChange).isNull() @@ -130,6 +135,7 @@ class ConfigurationRepositoryImplTest : SysuiTestCase() { @Test fun onResolutionScale_updatesOnConfigurationChange() = testScope.runTest { + clearInvocations(configurationController) val scaleForResolution by collectLastValue(underTest.scaleForResolution) assertThat(scaleForResolution).isEqualTo(displaySizeRatio) @@ -149,6 +155,7 @@ class ConfigurationRepositoryImplTest : SysuiTestCase() { @Test fun onResolutionScale_nullMaxResolution() = testScope.runTest { + clearInvocations(configurationController) val scaleForResolution by collectLastValue(underTest.scaleForResolution) runCurrent() @@ -203,7 +210,7 @@ class ConfigurationRepositoryImplTest : SysuiTestCase() { anyInt(), anyInt(), anyInt(), - anyInt() + anyInt(), ) ) .thenReturn(ratio) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt index 1f5e30ca4a0d..0d410cff5ff6 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt @@ -88,6 +88,34 @@ class CommunalPrefsRepositoryImplTest : SysuiTestCase() { } @Test + fun isHubOnboardingDismissedValue_byDefault_isFalse() = + testScope.runTest { + val isHubOnboardingDismissed by + collectLastValue(underTest.isHubOnboardingDismissed(MAIN_USER)) + assertThat(isHubOnboardingDismissed).isFalse() + } + + @Test + fun isHubOnboardingDismissedValue_onSet_isTrue() = + testScope.runTest { + val isHubOnboardingDismissed by + collectLastValue(underTest.isHubOnboardingDismissed(MAIN_USER)) + + underTest.setHubOnboardingDismissed(MAIN_USER) + assertThat(isHubOnboardingDismissed).isTrue() + } + + @Test + fun isHubOnboardingDismissedValue_onSetForDifferentUser_isStillFalse() = + testScope.runTest { + val isHubOnboardingDismissed by + collectLastValue(underTest.isHubOnboardingDismissed(MAIN_USER)) + + underTest.setHubOnboardingDismissed(SECONDARY_USER) + assertThat(isHubOnboardingDismissed).isFalse() + } + + @Test fun getSharedPreferences_whenFileRestored() = testScope.runTest { val isCtaDismissed by collectLastValue(underTest.isCtaDismissed(MAIN_USER)) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalPrefsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalPrefsInteractorTest.kt index 9a92f76f90c6..1fef6932ecca 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalPrefsInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalPrefsInteractorTest.kt @@ -74,6 +74,40 @@ class CommunalPrefsInteractorTest : SysuiTestCase() { assertThat(isCtaDismissed).isFalse() } + @Test + fun setHubOnboardingDismissed_currentUser() = + testScope.runTest { + setSelectedUser(MAIN_USER) + val isHubOnboardingDismissed by collectLastValue(underTest.isHubOnboardingDismissed) + + assertThat(isHubOnboardingDismissed).isFalse() + underTest.setHubOnboardingDismissed(MAIN_USER) + assertThat(isHubOnboardingDismissed).isTrue() + } + + @Test + fun setHubOnboardingDismissed_anotherUser() = + testScope.runTest { + setSelectedUser(MAIN_USER) + val isHubOnboardingDismissed by collectLastValue(underTest.isHubOnboardingDismissed) + + assertThat(isHubOnboardingDismissed).isFalse() + underTest.setHubOnboardingDismissed(SECONDARY_USER) + assertThat(isHubOnboardingDismissed).isFalse() + } + + @Test + fun isHubOnboardingDismissed_userSwitch() = + testScope.runTest { + setSelectedUser(MAIN_USER) + underTest.setHubOnboardingDismissed(MAIN_USER) + val isHubOnboardingDismissed by collectLastValue(underTest.isHubOnboardingDismissed) + + assertThat(isHubOnboardingDismissed).isTrue() + setSelectedUser(SECONDARY_USER) + assertThat(isHubOnboardingDismissed).isFalse() + } + private suspend fun setSelectedUser(user: UserInfo) { with(kosmos.fakeUserRepository) { setUserInfos(listOf(user)) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/HubOnboardingInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/HubOnboardingInteractorTest.kt new file mode 100644 index 000000000000..ef25dabb4c7f --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/HubOnboardingInteractorTest.kt @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.communal.domain.interactor + +import android.content.pm.UserInfo +import android.content.pm.UserInfo.FLAG_MAIN +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.compose.animation.scene.ObservableTransitionState +import com.android.compose.animation.scene.SceneKey +import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2 +import com.android.systemui.SysuiTestCase +import com.android.systemui.communal.data.repository.fakeCommunalPrefsRepository +import com.android.systemui.kosmos.collectLastValue +import com.android.systemui.kosmos.runTest +import com.android.systemui.kosmos.useUnconfinedTestDispatcher +import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.settings.fakeUserTracker +import com.android.systemui.testKosmos +import com.android.systemui.user.data.repository.fakeUserRepository +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.flow.MutableStateFlow +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class HubOnboardingInteractorTest : SysuiTestCase() { + private val kosmos = testKosmos().useUnconfinedTestDispatcher() + private val sceneInteractor = kosmos.sceneInteractor + + private val underTest: HubOnboardingInteractor by lazy { kosmos.hubOnboardingInteractor } + + @Test + @EnableFlags(FLAG_GLANCEABLE_HUB_V2) + fun setHubOnboardingDismissed() = + kosmos.runTest { + setSelectedUser(MAIN_USER) + val isHubOnboardingDismissed by + collectLastValue(fakeCommunalPrefsRepository.isHubOnboardingDismissed(MAIN_USER)) + + underTest.setHubOnboardingDismissed() + + assertThat(isHubOnboardingDismissed).isTrue() + } + + @Test + @EnableFlags(FLAG_GLANCEABLE_HUB_V2) + fun shouldShowHubOnboarding_falseWhenDismissed() = + kosmos.runTest { + setSelectedUser(MAIN_USER) + val shouldShowHubOnboarding by collectLastValue(underTest.shouldShowHubOnboarding) + + fakeCommunalPrefsRepository.setHubOnboardingDismissed(MAIN_USER) + + assertThat(shouldShowHubOnboarding).isFalse() + } + + @Test + @EnableFlags(FLAG_GLANCEABLE_HUB_V2) + fun shouldShowHubOnboarding_falseWhenNotIdleOnCommunal() = + kosmos.runTest { + setSelectedUser(MAIN_USER) + val shouldShowHubOnboarding by collectLastValue(underTest.shouldShowHubOnboarding) + + assertThat(shouldShowHubOnboarding).isFalse() + } + + @Test + @EnableFlags(FLAG_GLANCEABLE_HUB_V2) + fun shouldShowHubOnboarding_trueWhenIdleOnCommunal() = + kosmos.runTest { + setSelectedUser(MAIN_USER) + val shouldShowHubOnboarding by collectLastValue(underTest.shouldShowHubOnboarding) + + // Change to Communal scene. + setIdleScene(Scenes.Communal) + + assertThat(shouldShowHubOnboarding).isFalse() + } + + @Test + @DisableFlags(FLAG_GLANCEABLE_HUB_V2) + fun shouldShowHubOnboarding_falseWhenFlagDisabled() = + kosmos.runTest { + setSelectedUser(MAIN_USER) + val shouldShowHubOnboarding by collectLastValue(underTest.shouldShowHubOnboarding) + + // Change to Communal scene. + setIdleScene(Scenes.Communal) + + assertThat(shouldShowHubOnboarding).isFalse() + } + + private fun setIdleScene(scene: SceneKey) { + sceneInteractor.changeScene(scene, "test") + val transitionState = + MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(scene)) + sceneInteractor.setTransitionState(transitionState) + } + + private suspend fun setSelectedUser(user: UserInfo) { + with(kosmos.fakeUserRepository) { + setUserInfos(listOf(user)) + setSelectedUserInfo(user) + } + kosmos.fakeUserTracker.set(userInfos = listOf(user), selectedUserIndex = 0) + } + + companion object { + val MAIN_USER = UserInfo(0, "main", FLAG_MAIN) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/posturing/domain/interactor/PosturingInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/posturing/domain/interactor/PosturingInteractorTest.kt new file mode 100644 index 000000000000..0df8834618d5 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/posturing/domain/interactor/PosturingInteractorTest.kt @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.communal.posturing.domain.interactor + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.communal.posturing.data.repository.fake +import com.android.systemui.communal.posturing.data.repository.posturingRepository +import com.android.systemui.communal.posturing.shared.model.PosturedState +import com.android.systemui.kosmos.collectLastValue +import com.android.systemui.kosmos.runTest +import com.android.systemui.kosmos.useUnconfinedTestDispatcher +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class PosturingInteractorTest : SysuiTestCase() { + + private val kosmos = testKosmos().useUnconfinedTestDispatcher() + + private val underTest by lazy { kosmos.posturingInteractor } + + @Test + fun testNoDebugOverride() = + kosmos.runTest { + val postured by collectLastValue(underTest.postured) + assertThat(postured).isFalse() + + posturingRepository.fake.setPosturedState(PosturedState.Postured(1f)) + assertThat(postured).isTrue() + } + + @Test + fun testOverriddenByDebugValue() = + kosmos.runTest { + val postured by collectLastValue(underTest.postured) + assertThat(postured).isFalse() + + underTest.setValueForDebug(PosturedState.NotPostured) + posturingRepository.fake.setPosturedState(PosturedState.Postured(1f)) + + // Repository value is overridden by debug value + assertThat(postured).isFalse() + + underTest.setValueForDebug(PosturedState.Unknown) + assertThat(postured).isTrue() + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/HubOnboardingViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/HubOnboardingViewModelTest.kt new file mode 100644 index 000000000000..712d26275000 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/HubOnboardingViewModelTest.kt @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.communal.ui.viewmodel + +import android.content.pm.UserInfo +import android.content.pm.UserInfo.FLAG_MAIN +import android.platform.test.annotations.EnableFlags +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2 +import com.android.systemui.SysuiTestCase +import com.android.systemui.communal.data.repository.fakeCommunalPrefsRepository +import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED +import com.android.systemui.flags.fakeFeatureFlagsClassic +import com.android.systemui.kosmos.collectLastValue +import com.android.systemui.kosmos.runTest +import com.android.systemui.kosmos.testScope +import com.android.systemui.kosmos.useUnconfinedTestDispatcher +import com.android.systemui.lifecycle.activateIn +import com.android.systemui.settings.fakeUserTracker +import com.android.systemui.testKosmos +import com.android.systemui.user.data.repository.fakeUserRepository +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@EnableFlags(FLAG_GLANCEABLE_HUB_V2) +@RunWith(AndroidJUnit4::class) +class HubOnboardingViewModelTest : SysuiTestCase() { + private val kosmos = testKosmos().useUnconfinedTestDispatcher() + private val underTest: HubOnboardingViewModel by lazy { kosmos.hubOnboardingViewModel } + + @Before + fun setUp() { + kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true) + underTest.activateIn(kosmos.testScope) + } + + @Test + fun onDismissed_setsDismissedTrue() = + kosmos.runTest { + setSelectedUser(MAIN_USER) + + val isHubOnboardingDismissed by + collectLastValue(fakeCommunalPrefsRepository.isHubOnboardingDismissed(MAIN_USER)) + + underTest.onDismissed() + + assertThat(isHubOnboardingDismissed).isTrue() + } + + private suspend fun setSelectedUser(user: UserInfo) { + with(kosmos.fakeUserRepository) { + setUserInfos(listOf(user)) + setSelectedUserInfo(user) + } + kosmos.fakeUserTracker.set(userInfos = listOf(user), selectedUserIndex = 0) + } + + companion object { + val MAIN_USER = UserInfo(0, "main", FLAG_MAIN) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/ui/TemperatureControlBehaviorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/ui/TemperatureControlBehaviorTest.kt index b3f458821cba..70570f9323f1 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/ui/TemperatureControlBehaviorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/ui/TemperatureControlBehaviorTest.kt @@ -12,13 +12,14 @@ import android.view.LayoutInflater import android.view.ViewGroup 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.controls.ControlsMetricsLogger import com.android.systemui.controls.controller.ControlInfo import com.android.systemui.controls.controller.ControlsController +import com.android.systemui.res.R import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.time.FakeSystemClock +import com.android.systemui.utils.SafeIconLoader import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -32,6 +33,7 @@ class TemperatureControlBehaviorTest : SysuiTestCase() { @Mock lateinit var controlsMetricsLogger: ControlsMetricsLogger @Mock lateinit var controlActionCoordinator: ControlActionCoordinator @Mock lateinit var controlsController: ControlsController + @Mock lateinit var safeIconLoader: SafeIconLoader private val fakeSystemClock = FakeSystemClock() private val underTest = TemperatureControlBehavior() @@ -53,6 +55,7 @@ class TemperatureControlBehaviorTest : SysuiTestCase() { controlsMetricsLogger, 0, 0, + safeIconLoader, ) } @@ -61,12 +64,7 @@ class TemperatureControlBehaviorTest : SysuiTestCase() { val controlWithState = ControlWithState( ComponentName("test.pkg", "TestClass"), - ControlInfo( - "test_id", - "test title", - "test subtitle", - DeviceTypes.TYPE_AC_UNIT, - ), + ControlInfo("test_id", "test title", "test subtitle", DeviceTypes.TYPE_AC_UNIT), Control.StatefulBuilder( "", PendingIntent.getActivity( @@ -87,11 +85,11 @@ class TemperatureControlBehaviorTest : SysuiTestCase() { ), 0, 0, - 0 + 0, ) ) .setStatus(Control.STATUS_OK) - .build() + .build(), ) viewHolder.bindData(controlWithState, false) underTest.initialize(viewHolder) 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 3bf59f34db76..cd05980385e0 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 @@ -50,6 +50,7 @@ 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.ShortcutKey import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory +import com.android.systemui.keyboard.shortcut.shortcutHelperAccessibilityShortcutsSource import com.android.systemui.keyboard.shortcut.shortcutHelperAppCategoriesShortcutsSource import com.android.systemui.keyboard.shortcut.shortcutHelperCurrentAppShortcutsSource import com.android.systemui.keyboard.shortcut.shortcutHelperInputShortcutsSource @@ -88,6 +89,7 @@ class DefaultShortcutCategoriesRepositoryTest : SysuiTestCase() { it.shortcutHelperAppCategoriesShortcutsSource = fakeAppCategoriesSource it.shortcutHelperInputShortcutsSource = FakeKeyboardShortcutGroupsSource() it.shortcutHelperCurrentAppShortcutsSource = FakeKeyboardShortcutGroupsSource() + it.shortcutHelperAccessibilityShortcutsSource = FakeKeyboardShortcutGroupsSource() } private val repo = kosmos.defaultShortcutCategoriesRepository @@ -284,14 +286,20 @@ class DefaultShortcutCategoriesRepositoryTest : SysuiTestCase() { 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 } + 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 } + 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() 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 8f0bc640f0eb..61490986f4a9 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 @@ -38,6 +38,7 @@ import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.InputMethodEditor import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.MultiTasking import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.System +import com.android.systemui.keyboard.shortcut.shortcutHelperAccessibilityShortcutsSource import com.android.systemui.keyboard.shortcut.shortcutHelperAppCategoriesShortcutsSource import com.android.systemui.keyboard.shortcut.shortcutHelperCategoriesInteractor import com.android.systemui.keyboard.shortcut.shortcutHelperCurrentAppShortcutsSource @@ -76,6 +77,7 @@ class ShortcutHelperCategoriesInteractorTest : SysuiTestCase() { it.shortcutHelperMultiTaskingShortcutsSource = multitaskingShortcutsSource it.shortcutHelperAppCategoriesShortcutsSource = FakeKeyboardShortcutGroupsSource() it.shortcutHelperCurrentAppShortcutsSource = FakeKeyboardShortcutGroupsSource() + it.shortcutHelperAccessibilityShortcutsSource = FakeKeyboardShortcutGroupsSource() it.userTracker = FakeUserTracker(onCreateCurrentUserContext = { mockUserContext }) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarterTest.kt index 000024f9b814..7a343351ef64 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarterTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarterTest.kt @@ -26,6 +26,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.keyboard.shortcut.data.source.FakeKeyboardShortcutGroupsSource import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts import com.android.systemui.keyboard.shortcut.shortcutCustomizationDialogStarterFactory +import com.android.systemui.keyboard.shortcut.shortcutHelperAccessibilityShortcutsSource import com.android.systemui.keyboard.shortcut.shortcutHelperAppCategoriesShortcutsSource import com.android.systemui.keyboard.shortcut.shortcutHelperCurrentAppShortcutsSource import com.android.systemui.keyboard.shortcut.shortcutHelperInputShortcutsSource @@ -66,6 +67,7 @@ class ShortcutHelperDialogStarterTest : SysuiTestCase() { it.testDispatcher = UnconfinedTestDispatcher() it.shortcutHelperSystemShortcutsSource = fakeSystemSource it.shortcutHelperMultiTaskingShortcutsSource = fakeMultiTaskingSource + it.shortcutHelperAccessibilityShortcutsSource = FakeKeyboardShortcutGroupsSource() it.shortcutHelperAppCategoriesShortcutsSource = FakeKeyboardShortcutGroupsSource() it.shortcutHelperInputShortcutsSource = FakeKeyboardShortcutGroupsSource() it.shortcutHelperCurrentAppShortcutsSource = FakeKeyboardShortcutGroupsSource() diff --git a/packages/SystemUI/multivalentTests/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 3fc46b973959..cf38072912e9 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt @@ -45,6 +45,7 @@ import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType. import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.System import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory import com.android.systemui.keyboard.shortcut.shared.model.shortcut +import com.android.systemui.keyboard.shortcut.shortcutHelperAccessibilityShortcutsSource import com.android.systemui.keyboard.shortcut.shortcutHelperAppCategoriesShortcutsSource import com.android.systemui.keyboard.shortcut.shortcutHelperCurrentAppShortcutsSource import com.android.systemui.keyboard.shortcut.shortcutHelperInputShortcutsSource @@ -95,6 +96,7 @@ class ShortcutHelperViewModelTest : SysuiTestCase() { it.shortcutHelperMultiTaskingShortcutsSource = fakeMultiTaskingSource it.shortcutHelperAppCategoriesShortcutsSource = FakeKeyboardShortcutGroupsSource() it.shortcutHelperInputShortcutsSource = FakeKeyboardShortcutGroupsSource() + it.shortcutHelperAccessibilityShortcutsSource = FakeKeyboardShortcutGroupsSource() it.shortcutHelperCurrentAppShortcutsSource = fakeCurrentAppsSource it.userTracker = FakeUserTracker(onCreateCurrentUserContext = { mockUserContext }) } @@ -112,9 +114,12 @@ class ShortcutHelperViewModelTest : SysuiTestCase() { 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(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) } @@ -278,11 +283,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) @@ -318,23 +323,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"), + ), ), ) } @@ -420,9 +425,8 @@ class ShortcutHelperViewModelTest : SysuiTestCase() { @Test fun shortcutsUiState_shouldShowResetButton_isTrueWhenThereAreCustomShortcuts() = testScope.runTest { - whenever( - inputManager.getCustomInputGestures(/* filter= */ InputGestureData.Filter.KEY) - ).thenReturn(listOf(allAppsInputGestureData)) + whenever(inputManager.getCustomInputGestures(/* filter= */ InputGestureData.Filter.KEY)) + .thenReturn(listOf(allAppsInputGestureData)) val uiState by collectLastValue(viewModel.shortcutsUiState) testHelper.showFromActivity() @@ -433,7 +437,7 @@ class ShortcutHelperViewModelTest : SysuiTestCase() { @Test fun shortcutsUiState_searchQuery_isResetAfterHelperIsClosedAndReOpened() = - testScope.runTest{ + testScope.runTest { val uiState by collectLastValue(viewModel.shortcutsUiState) openHelperAndSearchForFooString() @@ -443,7 +447,7 @@ class ShortcutHelperViewModelTest : SysuiTestCase() { assertThat((uiState as? ShortcutsUiState.Active)?.searchQuery).isEqualTo("") } - private fun openHelperAndSearchForFooString(){ + private fun openHelperAndSearchForFooString() { testHelper.showFromActivity() viewModel.onSearchQueryChanged("foo") } 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 deleted file mode 100644 index ac06a3b9293c..000000000000 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfigTest.kt +++ /dev/null @@ -1,155 +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.keyguard.data.quickaffordance - -import android.platform.test.annotations.DisableFlags -import android.platform.test.annotations.EnableFlags -import android.platform.test.flag.junit.FlagsParameterization -import androidx.test.filters.SmallTest -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.setCommunalV2Available -import com.android.systemui.communal.domain.interactor.setCommunalV2Enabled -import com.android.systemui.communal.shared.model.CommunalScenes -import com.android.systemui.flags.parameterizeSceneContainerFlag -import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository -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.scene.data.repository.sceneContainerRepository -import com.android.systemui.scene.shared.model.Scenes -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.MockitoAnnotations -import platform.test.runner.parameterized.ParameterizedAndroidJunit4 -import platform.test.runner.parameterized.Parameters - -@SmallTest -@EnableFlags(Flags.FLAG_GLANCEABLE_HUB_V2) -@RunWith(ParameterizedAndroidJunit4::class) -class GlanceableHubQuickAffordanceConfigTest(flags: FlagsParameterization?) : SysuiTestCase() { - private val kosmos = testKosmos() - private val Kosmos.underTest by Kosmos.Fixture { glanceableHubQuickAffordanceConfig } - - init { - mSetFlagsRule.setFlagsParameterization(flags!!) - } - - @Before - fun setUp() { - MockitoAnnotations.initMocks(this) - - // Access the class immediately so that flows are instantiated. - // GlanceableHubQuickAffordanceConfig accesses StateFlow.value directly so we need the flows - // to start flowing before runCurrent is called in the tests. - kosmos.underTest - } - - @Test - fun lockscreenState_whenGlanceableHubEnabled_returnsVisible() = - kosmos.runTest { - kosmos.setCommunalV2Available(true) - runCurrent() - - val lockScreenState by collectLastValue(underTest.lockScreenState) - - assertThat(lockScreenState) - .isInstanceOf(KeyguardQuickAffordanceConfig.LockScreenState.Visible::class.java) - } - - @Test - fun lockscreenState_whenGlanceableHubDisabled_returnsHidden() = - kosmos.runTest { - setCommunalV2Enabled(false) - val lockScreenState by collectLastValue(underTest.lockScreenState) - runCurrent() - - assertThat(lockScreenState) - .isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden) - } - - @Test - fun lockscreenState_whenGlanceableHubNotAvailable_returnsHidden() = - kosmos.runTest { - // Hub is enabled, but not available. - setCommunalV2Enabled(true) - fakeKeyguardRepository.setKeyguardShowing(false) - val lockScreenState by collectLastValue(underTest.lockScreenState) - runCurrent() - - assertThat(lockScreenState) - .isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden) - } - - @Test - fun pickerScreenState_whenGlanceableHubEnabled_returnsDefault() = - kosmos.runTest { - setCommunalV2Enabled(true) - runCurrent() - - assertThat(underTest.getPickerScreenState()) - .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.Default()) - } - - @Test - fun pickerScreenState_whenGlanceableHubDisabled_returnsDisabled() = - kosmos.runTest { - setCommunalV2Enabled(false) - runCurrent() - - assertThat( - underTest.getPickerScreenState() - is KeyguardQuickAffordanceConfig.PickerScreenState.Disabled - ) - } - - @Test - @DisableFlags(Flags.FLAG_SCENE_CONTAINER) - fun onTriggered_changesSceneToCommunal() = - kosmos.runTest { - underTest.onTriggered(expandable = null) - runCurrent() - - assertThat(kosmos.communalSceneRepository.currentScene.value) - .isEqualTo(CommunalScenes.Communal) - } - - @Test - @EnableFlags(Flags.FLAG_SCENE_CONTAINER) - fun testTransitionToGlanceableHub_sceneContainer() = - kosmos.runTest { - underTest.onTriggered(expandable = null) - runCurrent() - - assertThat(kosmos.sceneContainerRepository.currentScene.value) - .isEqualTo(Scenes.Communal) - } - - companion object { - @JvmStatic - @Parameters(name = "{0}") - fun getParams(): List<FlagsParameterization> { - return parameterizeSceneContainerFlag() - } - } -} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt index 7c694b4fc95b..810ca4960a57 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt @@ -29,6 +29,7 @@ import com.android.systemui.keyguard.ui.view.KeyguardRootView import com.android.systemui.keyguard.ui.view.layout.sections.AccessibilityActionsSection import com.android.systemui.keyguard.ui.view.layout.sections.AodBurnInSection import com.android.systemui.keyguard.ui.view.layout.sections.AodNotificationIconsSection +import com.android.systemui.keyguard.ui.view.layout.sections.AodPromotedNotificationSection import com.android.systemui.keyguard.ui.view.layout.sections.ClockSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultDeviceEntrySection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection @@ -68,6 +69,7 @@ class DefaultKeyguardBlueprintTest : SysuiTestCase() { @Mock private lateinit var defaultStatusBarViewSection: DefaultStatusBarSection @Mock private lateinit var defaultNSSLSection: DefaultNotificationStackScrollLayoutSection @Mock private lateinit var splitShadeGuidelines: SplitShadeGuidelines + @Mock private lateinit var aodPromotedNotificationSection: AodPromotedNotificationSection @Mock private lateinit var aodNotificationIconsSection: AodNotificationIconsSection @Mock private lateinit var aodBurnInSection: AodBurnInSection @Mock private lateinit var clockSection: ClockSection @@ -90,6 +92,7 @@ class DefaultKeyguardBlueprintTest : SysuiTestCase() { defaultSettingsPopupMenuSection, defaultStatusBarViewSection, defaultNSSLSection, + aodPromotedNotificationSection, aodNotificationIconsSection, aodBurnInSection, clockSection, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelTest.kt index ade7614ae853..286f8bfd63a2 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelTest.kt @@ -80,7 +80,7 @@ class AlternateBouncerToPrimaryBouncerTransitionViewModelTest : SysuiTestCase() fun notifications_areFullyVisible_whenShadeIsOpen() = testScope.runTest { val values by collectValues(underTest.notificationAlpha) - kosmos.bouncerWindowBlurTestUtil.shadeExpanded(true) + kosmos.keyguardWindowBlurTestUtil.shadeExpanded(true) keyguardTransitionRepository.sendTransitionSteps( listOf( @@ -100,9 +100,9 @@ class AlternateBouncerToPrimaryBouncerTransitionViewModelTest : SysuiTestCase() fun blurRadiusGoesToMaximumWhenShadeIsExpanded() = testScope.runTest { val values by collectValues(underTest.windowBlurRadius) - kosmos.bouncerWindowBlurTestUtil.shadeExpanded(true) + kosmos.keyguardWindowBlurTestUtil.shadeExpanded(true) - kosmos.bouncerWindowBlurTestUtil.assertTransitionToBlurRadius( + kosmos.keyguardWindowBlurTestUtil.assertTransitionToBlurRadius( transitionProgress = listOf(0f, 0f, 0.1f, 0.2f, 0.3f, 1f), startValue = kosmos.blurConfig.maxBlurRadiusPx, endValue = kosmos.blurConfig.maxBlurRadiusPx, @@ -119,12 +119,12 @@ class AlternateBouncerToPrimaryBouncerTransitionViewModelTest : SysuiTestCase() testScope.runTest { val values by collectValues(underTest.notificationBlurRadius) - kosmos.bouncerWindowBlurTestUtil.shadeExpanded(true) + kosmos.keyguardWindowBlurTestUtil.shadeExpanded(true) - kosmos.bouncerWindowBlurTestUtil.assertTransitionToBlurRadius( + kosmos.keyguardWindowBlurTestUtil.assertTransitionToBlurRadius( transitionProgress = listOf(0f, 0f, 0.1f, 0.2f, 0.3f, 1f), - startValue = kosmos.blurConfig.maxBlurRadiusPx / 3.0f, - endValue = kosmos.blurConfig.maxBlurRadiusPx / 3.0f, + startValue = kosmos.blurConfig.maxBlurRadiusPx, + endValue = kosmos.blurConfig.maxBlurRadiusPx, transitionFactory = ::step, actualValuesProvider = { values }, checkInterpolatedValues = false, @@ -135,9 +135,9 @@ class AlternateBouncerToPrimaryBouncerTransitionViewModelTest : SysuiTestCase() fun blurRadiusGoesFromMinToMaxWhenShadeIsNotExpanded() = testScope.runTest { val values by collectValues(underTest.windowBlurRadius) - kosmos.bouncerWindowBlurTestUtil.shadeExpanded(false) + kosmos.keyguardWindowBlurTestUtil.shadeExpanded(false) - kosmos.bouncerWindowBlurTestUtil.assertTransitionToBlurRadius( + kosmos.keyguardWindowBlurTestUtil.assertTransitionToBlurRadius( transitionProgress = listOf(0f, 0f, 0.1f, 0.2f, 0.3f, 1f), startValue = kosmos.blurConfig.minBlurRadiusPx, endValue = kosmos.blurConfig.maxBlurRadiusPx, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModelTest.kt index d782d1e2612c..01191cca87a1 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModelTest.kt @@ -51,7 +51,7 @@ class AodToPrimaryBouncerTransitionViewModelTest : SysuiTestCase() { testScope.runTest { val values by collectValues(underTest.windowBlurRadius) - kosmos.bouncerWindowBlurTestUtil.assertTransitionToBlurRadius( + kosmos.keyguardWindowBlurTestUtil.assertTransitionToBlurRadius( transitionProgress = listOf(0.0f, 0.0f, 0.3f, 0.4f, 0.5f, 1.0f), startValue = kosmos.blurConfig.maxBlurRadiusPx, endValue = kosmos.blurConfig.maxBlurRadiusPx, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModelTest.kt index 4d58f7ab118e..28410d9e3be1 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModelTest.kt @@ -81,7 +81,7 @@ class DozingToPrimaryBouncerTransitionViewModelTest : SysuiTestCase() { testScope.runTest { val values by collectValues(underTest.windowBlurRadius) - kosmos.bouncerWindowBlurTestUtil.assertTransitionToBlurRadius( + kosmos.keyguardWindowBlurTestUtil.assertTransitionToBlurRadius( transitionProgress = listOf(0.0f, 0.2f, 0.3f, 0.65f, 0.7f, 1.0f), startValue = kosmos.blurConfig.minBlurRadiusPx, endValue = kosmos.blurConfig.maxBlurRadiusPx, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModelTest.kt index 88fd333b34a8..abf8b39ce5af 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModelTest.kt @@ -45,7 +45,7 @@ class GlanceableHubToPrimaryBouncerTransitionViewModelTest : SysuiTestCase() { testScope.runTest { val values by collectValues(underTest.windowBlurRadius) - kosmos.bouncerWindowBlurTestUtil.assertTransitionToBlurRadius( + kosmos.keyguardWindowBlurTestUtil.assertTransitionToBlurRadius( transitionProgress = listOf(0.0f, 0.2f, 0.3f, 0.65f, 0.7f, 1.0f), startValue = kosmos.blurConfig.maxBlurRadiusPx, endValue = kosmos.blurConfig.maxBlurRadiusPx, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardMediaViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardMediaViewModelTest.kt new file mode 100644 index 000000000000..38829da69c28 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardMediaViewModelTest.kt @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.keyguard.data.repository.keyguardRepository +import com.android.systemui.kosmos.runTest +import com.android.systemui.kosmos.testScope +import com.android.systemui.kosmos.useUnconfinedTestDispatcher +import com.android.systemui.lifecycle.activateIn +import com.android.systemui.media.controls.data.repository.mediaFilterRepository +import com.android.systemui.media.controls.shared.model.MediaData +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class KeyguardMediaViewModelTest : SysuiTestCase() { + private val kosmos = testKosmos().useUnconfinedTestDispatcher() + + private val underTest = kosmos.keyguardMediaViewModelFactory.create() + + @Before + fun setUp() { + underTest.activateIn(kosmos.testScope) + } + + @Test + fun onDozing_noActiveMedia_mediaIsHidden() = + kosmos.runTest { + keyguardRepository.setIsDozing(true) + + assertThat(underTest.isMediaVisible).isFalse() + } + + @Test + fun onDozing_activeMediaExists_mediaIsHidden() = + kosmos.runTest { + val userMedia = MediaData(active = true) + + mediaFilterRepository.addSelectedUserMediaEntry(userMedia) + keyguardRepository.setIsDozing(true) + + assertThat(underTest.isMediaVisible).isFalse() + } + + @Test + fun onDeviceAwake_activeMediaExists_mediaIsVisible() = + kosmos.runTest { + val userMedia = MediaData(active = true) + + mediaFilterRepository.addSelectedUserMediaEntry(userMedia) + keyguardRepository.setIsDozing(false) + + assertThat(underTest.isMediaVisible).isTrue() + } + + @Test + fun onDeviceAwake_noActiveMedia_mediaIsHidden() = + kosmos.runTest { + keyguardRepository.setIsDozing(false) + + assertThat(underTest.isMediaVisible).isFalse() + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerWindowBlurTestUtilKosmos.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardWindowBlurTestUtilKosmos.kt index cde853140de2..ef07786284c9 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerWindowBlurTestUtilKosmos.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardWindowBlurTestUtilKosmos.kt @@ -33,9 +33,9 @@ import com.android.systemui.shade.shadeTestUtil import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.TestScope -val Kosmos.bouncerWindowBlurTestUtil by +val Kosmos.keyguardWindowBlurTestUtil by Kosmos.Fixture { - BouncerWindowBlurTestUtil( + KeyguardWindowBlurTestUtil( shadeTestUtil = shadeTestUtil, fakeKeyguardTransitionRepository = fakeKeyguardTransitionRepository, fakeKeyguardRepository = fakeKeyguardRepository, @@ -43,7 +43,7 @@ val Kosmos.bouncerWindowBlurTestUtil by ) } -class BouncerWindowBlurTestUtil( +class KeyguardWindowBlurTestUtil( private val shadeTestUtil: ShadeTestUtil, private val fakeKeyguardTransitionRepository: FakeKeyguardTransitionRepository, private val fakeKeyguardRepository: FakeKeyguardRepository, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt index 914094fa39df..60a19a4c7d07 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt @@ -159,9 +159,9 @@ class LockscreenToPrimaryBouncerTransitionViewModelTest(flags: FlagsParameteriza fun blurRadiusIsMaxWhenShadeIsExpanded() = testScope.runTest { val values by collectValues(underTest.windowBlurRadius) - kosmos.bouncerWindowBlurTestUtil.shadeExpanded(true) + kosmos.keyguardWindowBlurTestUtil.shadeExpanded(true) - kosmos.bouncerWindowBlurTestUtil.assertTransitionToBlurRadius( + kosmos.keyguardWindowBlurTestUtil.assertTransitionToBlurRadius( transitionProgress = listOf(0.0f, 0.2f, 0.3f, 0.65f, 0.7f, 1.0f), startValue = kosmos.blurConfig.maxBlurRadiusPx, endValue = kosmos.blurConfig.maxBlurRadiusPx, @@ -176,9 +176,9 @@ class LockscreenToPrimaryBouncerTransitionViewModelTest(flags: FlagsParameteriza fun blurRadiusGoesFromMinToMaxWhenShadeIsNotExpanded() = testScope.runTest { val values by collectValues(underTest.windowBlurRadius) - kosmos.bouncerWindowBlurTestUtil.shadeExpanded(false) + kosmos.keyguardWindowBlurTestUtil.shadeExpanded(false) - kosmos.bouncerWindowBlurTestUtil.assertTransitionToBlurRadius( + kosmos.keyguardWindowBlurTestUtil.assertTransitionToBlurRadius( transitionProgress = listOf(0.0f, 0.2f, 0.3f, 0.65f, 0.7f, 1.0f), startValue = kosmos.blurConfig.minBlurRadiusPx, endValue = kosmos.blurConfig.maxBlurRadiusPx, @@ -193,13 +193,13 @@ class LockscreenToPrimaryBouncerTransitionViewModelTest(flags: FlagsParameteriza fun notificationBlur_isNonZero_whenShadeIsExpanded() = testScope.runTest { val values by collectValues(underTest.notificationBlurRadius) - kosmos.bouncerWindowBlurTestUtil.shadeExpanded(true) + kosmos.keyguardWindowBlurTestUtil.shadeExpanded(true) runCurrent() - kosmos.bouncerWindowBlurTestUtil.assertTransitionToBlurRadius( + kosmos.keyguardWindowBlurTestUtil.assertTransitionToBlurRadius( transitionProgress = listOf(0f, 0f, 0.1f, 0.2f, 0.3f, 1f), - startValue = kosmos.blurConfig.maxBlurRadiusPx / 3.0f, - endValue = kosmos.blurConfig.maxBlurRadiusPx / 3.0f, + startValue = kosmos.blurConfig.maxBlurRadiusPx, + endValue = kosmos.blurConfig.maxBlurRadiusPx, transitionFactory = ::step, actualValuesProvider = { values }, checkInterpolatedValues = false, @@ -212,10 +212,10 @@ class LockscreenToPrimaryBouncerTransitionViewModelTest(flags: FlagsParameteriza fun notifications_areFullyVisible_whenShadeIsExpanded() = testScope.runTest { val values by collectValues(underTest.notificationAlpha) - kosmos.bouncerWindowBlurTestUtil.shadeExpanded(true) + kosmos.keyguardWindowBlurTestUtil.shadeExpanded(true) runCurrent() - kosmos.bouncerWindowBlurTestUtil.assertTransitionToBlurRadius( + kosmos.keyguardWindowBlurTestUtil.assertTransitionToBlurRadius( transitionProgress = listOf(0f, 0f, 0.1f, 0.2f, 0.3f, 1f), startValue = 1.0f, endValue = 1.0f, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToPrimaryBouncerTransitionViewModelTest.kt index e11e3076c6c3..6db876756d3a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToPrimaryBouncerTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToPrimaryBouncerTransitionViewModelTest.kt @@ -45,7 +45,7 @@ class OccludedToPrimaryBouncerTransitionViewModelTest : SysuiTestCase() { testScope.runTest { val values by collectValues(underTest.windowBlurRadius) - kosmos.bouncerWindowBlurTestUtil.assertTransitionToBlurRadius( + kosmos.keyguardWindowBlurTestUtil.assertTransitionToBlurRadius( transitionProgress = listOf(0.0f, 0.2f, 0.3f, 0.65f, 0.7f, 1.0f), startValue = kosmos.blurConfig.maxBlurRadiusPx, endValue = kosmos.blurConfig.maxBlurRadiusPx, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModelTest.kt index 6895794df3dd..aa1e7ae9d509 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModelTest.kt @@ -145,7 +145,7 @@ class PrimaryBouncerToAodTransitionViewModelTest : SysuiTestCase() { testScope.runTest { val values by collectValues(underTest.windowBlurRadius) - kosmos.bouncerWindowBlurTestUtil.assertTransitionToBlurRadius( + kosmos.keyguardWindowBlurTestUtil.assertTransitionToBlurRadius( transitionProgress = listOf(0.0f, 0.2f, 0.3f, 0.65f, 0.7f, 1.0f), startValue = kosmos.blurConfig.maxBlurRadiusPx, endValue = kosmos.blurConfig.minBlurRadiusPx, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToDozingTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToDozingTransitionViewModelTest.kt index 4013c700673f..766816b1ac26 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToDozingTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToDozingTransitionViewModelTest.kt @@ -128,7 +128,7 @@ class PrimaryBouncerToDozingTransitionViewModelTest : SysuiTestCase() { testScope.runTest { val values by collectValues(underTest.windowBlurRadius) - kosmos.bouncerWindowBlurTestUtil.assertTransitionToBlurRadius( + kosmos.keyguardWindowBlurTestUtil.assertTransitionToBlurRadius( transitionProgress = listOf(0.0f, 0.2f, 0.3f, 0.65f, 0.7f, 1.0f), startValue = kosmos.blurConfig.maxBlurRadiusPx, endValue = kosmos.blurConfig.minBlurRadiusPx, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGlanceableHubTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGlanceableHubTransitionViewModelTest.kt index f3f4c89688c9..9cfcce43d13d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGlanceableHubTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGlanceableHubTransitionViewModelTest.kt @@ -45,7 +45,7 @@ class PrimaryBouncerToGlanceableHubTransitionViewModelTest : SysuiTestCase() { testScope.runTest { val values by collectValues(underTest.windowBlurRadius) - kosmos.bouncerWindowBlurTestUtil.assertTransitionToBlurRadius( + kosmos.keyguardWindowBlurTestUtil.assertTransitionToBlurRadius( transitionProgress = listOf(0.0f, 0.2f, 0.3f, 0.65f, 0.7f, 1.0f), startValue = kosmos.blurConfig.minBlurRadiusPx, endValue = kosmos.blurConfig.minBlurRadiusPx, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt index bae49fa14999..0db0c5fe8482 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt @@ -116,9 +116,9 @@ class PrimaryBouncerToLockscreenTransitionViewModelTest : SysuiTestCase() { fun blurRadiusGoesFromMaxToMinWhenShadeIsNotExpanded() = testScope.runTest { val values by collectValues(underTest.windowBlurRadius) - kosmos.bouncerWindowBlurTestUtil.shadeExpanded(false) + kosmos.keyguardWindowBlurTestUtil.shadeExpanded(false) - kosmos.bouncerWindowBlurTestUtil.assertTransitionToBlurRadius( + kosmos.keyguardWindowBlurTestUtil.assertTransitionToBlurRadius( transitionProgress = listOf(0.0f, 0.2f, 0.3f, 0.65f, 0.7f, 1.0f), startValue = kosmos.blurConfig.maxBlurRadiusPx, endValue = kosmos.blurConfig.minBlurRadiusPx, @@ -131,9 +131,9 @@ class PrimaryBouncerToLockscreenTransitionViewModelTest : SysuiTestCase() { fun blurRadiusRemainsAtMaxWhenShadeIsExpanded() = testScope.runTest { val values by collectValues(underTest.windowBlurRadius) - kosmos.bouncerWindowBlurTestUtil.shadeExpanded(true) + kosmos.keyguardWindowBlurTestUtil.shadeExpanded(true) - kosmos.bouncerWindowBlurTestUtil.assertTransitionToBlurRadius( + kosmos.keyguardWindowBlurTestUtil.assertTransitionToBlurRadius( transitionProgress = listOf(0.0f, 0.2f, 0.3f, 0.65f, 0.7f, 1.0f), startValue = kosmos.blurConfig.maxBlurRadiusPx, endValue = kosmos.blurConfig.maxBlurRadiusPx, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToOccludedTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToOccludedTransitionViewModelTest.kt index 8a2fc56e11e6..b0b4af5fea5b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToOccludedTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToOccludedTransitionViewModelTest.kt @@ -45,7 +45,7 @@ class PrimaryBouncerToOccludedTransitionViewModelTest : SysuiTestCase() { testScope.runTest { val values by collectValues(underTest.windowBlurRadius) - kosmos.bouncerWindowBlurTestUtil.assertTransitionToBlurRadius( + kosmos.keyguardWindowBlurTestUtil.assertTransitionToBlurRadius( transitionProgress = listOf(0.0f, 0.2f, 0.3f, 0.65f, 0.7f, 1.0f), startValue = kosmos.blurConfig.minBlurRadiusPx, endValue = kosmos.blurConfig.minBlurRadiusPx, 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 555ba56e087f..647603c418ee 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 @@ -17,8 +17,9 @@ package com.android.systemui.navigationbar.views; import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT; -import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN; -import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_SHOWN; +import static android.app.StatusBarManager.NAVIGATION_HINT_IME_VISIBLE; +import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_BUTTON_VISIBLE; +import static android.inputmethodservice.InputMethodService.BACK_DISPOSITION_ADJUST_NOTHING; import static android.inputmethodservice.InputMethodService.BACK_DISPOSITION_DEFAULT; import static android.inputmethodservice.InputMethodService.IME_VISIBLE; import static android.view.Display.DEFAULT_DISPLAY; @@ -30,6 +31,9 @@ import static com.android.systemui.assist.AssistManager.INVOCATION_TYPE_HOME_BUT import static com.android.systemui.navigationbar.views.NavigationBar.NavBarActionEvent.NAVBAR_ASSIST_LONGPRESS; import static com.android.systemui.navigationbar.views.buttons.KeyButtonView.NavBarButtonEvent.NAVBAR_IME_SWITCHER_BUTTON_LONGPRESS; import static com.android.systemui.navigationbar.views.buttons.KeyButtonView.NavBarButtonEvent.NAVBAR_IME_SWITCHER_BUTTON_TAP; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_ALT_BACK; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_VISIBLE; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_BUTTON_VISIBLE; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING; import static com.google.common.truth.Truth.assertThat; @@ -487,6 +491,69 @@ public class NavigationBarTest extends SysuiTestCase { verify(mUserTracker).addCallback(any(UserTracker.Callback.class), any(Executor.class)); } + /** + * Verifies that the SysUI state is updated correctly when given a new IME window status with + * IME visible and IME Switcher button visible. + */ + @Test + public void testSetImeWindowStatusSysuiState_ImeVisibleImeSwitcherButtonVisible() { + doNothing().when(mNavigationBar).checkNavBarModes(); + + mNavigationBar.setImeWindowStatus(DEFAULT_DISPLAY, IME_VISIBLE, + BACK_DISPOSITION_DEFAULT, true /* showImeSwitcher */); + verify(mMockSysUiState).setFlag(eq(SYSUI_STATE_IME_VISIBLE), eq(true)); + verify(mMockSysUiState).setFlag(eq(SYSUI_STATE_IME_SWITCHER_BUTTON_VISIBLE), eq(true)); + verify(mMockSysUiState).setFlag(eq(SYSUI_STATE_IME_ALT_BACK), eq(true)); + } + + /** + * Verifies that the SysUI state is updated correctly when given a new IME window status with + * IME visible and IME Switcher button not visible. + */ + @Test + public void testSetImeWindowStatusSysuiState_ImeVisibleImeSwitcherButtonNotVisible() { + doNothing().when(mNavigationBar).checkNavBarModes(); + + mNavigationBar.setImeWindowStatus(DEFAULT_DISPLAY, IME_VISIBLE, + BACK_DISPOSITION_DEFAULT, false /* showImeSwitcher */); + verify(mMockSysUiState).setFlag(eq(SYSUI_STATE_IME_VISIBLE), eq(true)); + verify(mMockSysUiState).setFlag(eq(SYSUI_STATE_IME_SWITCHER_BUTTON_VISIBLE), eq(false)); + verify(mMockSysUiState).setFlag(eq(SYSUI_STATE_IME_ALT_BACK), eq(true)); + } + + /** + * Verifies that the SysUI state is updated correctly when given a new IME window status with + * IME not visible and IME Switcher button visible. + */ + @Test + public void testSetImeWindowStatusSysuiState_ImeNotVisibleImeSwitcherButtonVisible() { + doNothing().when(mNavigationBar).checkNavBarModes(); + // Set initial state for later reset to be able to take place. + mNavigationBar.setImeWindowStatus(DEFAULT_DISPLAY, IME_VISIBLE, + BACK_DISPOSITION_DEFAULT, true /* showImeSwitcher */); + + mNavigationBar.setImeWindowStatus(DEFAULT_DISPLAY, 0 /* vis */, + BACK_DISPOSITION_DEFAULT, true /* showImeSwitcher */); + verify(mMockSysUiState).setFlag(eq(SYSUI_STATE_IME_VISIBLE), eq(false)); + verify(mMockSysUiState).setFlag(eq(SYSUI_STATE_IME_SWITCHER_BUTTON_VISIBLE), eq(false)); + verify(mMockSysUiState).setFlag(eq(SYSUI_STATE_IME_ALT_BACK), eq(false)); + } + + /** + * Verifies that the SysUI state is updated correctly when given a new IME window status with + * IME visible and back disposition adjust nothing. + */ + @Test + public void testSetImeWindowStatusSysuiState_ImeVisibleBackDispositionAdjustNothing() { + doNothing().when(mNavigationBar).checkNavBarModes(); + + mNavigationBar.setImeWindowStatus(DEFAULT_DISPLAY, IME_VISIBLE, + BACK_DISPOSITION_ADJUST_NOTHING, true /* showImeSwitcher */); + verify(mMockSysUiState).setFlag(eq(SYSUI_STATE_IME_VISIBLE), eq(true)); + verify(mMockSysUiState).setFlag(eq(SYSUI_STATE_IME_SWITCHER_BUTTON_VISIBLE), eq(true)); + verify(mMockSysUiState).setFlag(eq(SYSUI_STATE_IME_ALT_BACK), eq(false)); + } + @Test public void testSetImeWindowStatusWhenImeSwitchOnDisplay() { // Create default & external NavBar fragment. @@ -505,26 +572,26 @@ public class NavigationBarTest extends SysuiTestCase { BACK_DISPOSITION_DEFAULT, true); // Verify IME window state will be updated in default NavBar & external NavBar state reset. - assertEquals(NAVIGATION_HINT_BACK_ALT | NAVIGATION_HINT_IME_SHOWN - | NAVIGATION_HINT_IME_SWITCHER_SHOWN, + assertEquals(NAVIGATION_HINT_BACK_ALT | NAVIGATION_HINT_IME_VISIBLE + | NAVIGATION_HINT_IME_SWITCHER_BUTTON_VISIBLE, defaultNavBar.getNavigationIconHints()); assertFalse((externalNavBar.getNavigationIconHints() & NAVIGATION_HINT_BACK_ALT) != 0); - assertFalse((externalNavBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SHOWN) != 0); - assertFalse((externalNavBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SWITCHER_SHOWN) - != 0); + assertFalse((externalNavBar.getNavigationIconHints() & NAVIGATION_HINT_IME_VISIBLE) != 0); + assertFalse((externalNavBar.getNavigationIconHints() + & NAVIGATION_HINT_IME_SWITCHER_BUTTON_VISIBLE) != 0); externalNavBar.setImeWindowStatus(EXTERNAL_DISPLAY_ID, IME_VISIBLE, BACK_DISPOSITION_DEFAULT, true); defaultNavBar.setImeWindowStatus(DEFAULT_DISPLAY, 0 /* vis */, BACK_DISPOSITION_DEFAULT, false); // Verify IME window state will be updated in external NavBar & default NavBar state reset. - assertEquals(NAVIGATION_HINT_BACK_ALT | NAVIGATION_HINT_IME_SHOWN - | NAVIGATION_HINT_IME_SWITCHER_SHOWN, + assertEquals(NAVIGATION_HINT_BACK_ALT | NAVIGATION_HINT_IME_VISIBLE + | NAVIGATION_HINT_IME_SWITCHER_BUTTON_VISIBLE, externalNavBar.getNavigationIconHints()); assertFalse((defaultNavBar.getNavigationIconHints() & NAVIGATION_HINT_BACK_ALT) != 0); - assertFalse((defaultNavBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SHOWN) != 0); - assertFalse((defaultNavBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SWITCHER_SHOWN) - != 0); + assertFalse((defaultNavBar.getNavigationIconHints() & NAVIGATION_HINT_IME_VISIBLE) != 0); + assertFalse((defaultNavBar.getNavigationIconHints() + & NAVIGATION_HINT_IME_SWITCHER_BUTTON_VISIBLE) != 0); } @Test @@ -541,9 +608,9 @@ public class NavigationBarTest extends SysuiTestCase { mNavigationBar.setImeWindowStatus(DEFAULT_DISPLAY, IME_VISIBLE, BACK_DISPOSITION_DEFAULT, true); assertTrue((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_BACK_ALT) != 0); - assertTrue((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SHOWN) != 0); - assertTrue((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SWITCHER_SHOWN) - != 0); + assertTrue((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_IME_VISIBLE) != 0); + assertTrue((mNavigationBar.getNavigationIconHints() + & NAVIGATION_HINT_IME_SWITCHER_BUTTON_VISIBLE) != 0); // Verify navbar didn't alter and showing back icon when the keyguard is showing without // requesting IME insets visible. @@ -551,9 +618,9 @@ public class NavigationBarTest extends SysuiTestCase { mNavigationBar.setImeWindowStatus(DEFAULT_DISPLAY, IME_VISIBLE, BACK_DISPOSITION_DEFAULT, true); assertFalse((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_BACK_ALT) != 0); - assertFalse((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SHOWN) != 0); - assertFalse((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SWITCHER_SHOWN) - != 0); + assertFalse((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_IME_VISIBLE) != 0); + assertFalse((mNavigationBar.getNavigationIconHints() + & NAVIGATION_HINT_IME_SWITCHER_BUTTON_VISIBLE) != 0); // Verify navbar altered and showing back icon when the keyguard is showing and // requesting IME insets visible. @@ -562,9 +629,9 @@ public class NavigationBarTest extends SysuiTestCase { mNavigationBar.setImeWindowStatus(DEFAULT_DISPLAY, IME_VISIBLE, BACK_DISPOSITION_DEFAULT, true); assertTrue((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_BACK_ALT) != 0); - assertTrue((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SHOWN) != 0); - assertTrue((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SWITCHER_SHOWN) - != 0); + assertTrue((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_IME_VISIBLE) != 0); + assertTrue((mNavigationBar.getNavigationIconHints() + & NAVIGATION_HINT_IME_SWITCHER_BUTTON_VISIBLE) != 0); } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeUserActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeUserActionsViewModelTest.kt deleted file mode 100644 index 46b02e92a4f9..000000000000 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeUserActionsViewModelTest.kt +++ /dev/null @@ -1,161 +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.notifications.ui.viewmodel - -import android.testing.TestableLooper -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.filters.SmallTest -import com.android.compose.animation.scene.Swipe -import com.android.compose.animation.scene.UserActionResult -import com.android.systemui.SysuiTestCase -import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository -import com.android.systemui.authentication.shared.model.AuthenticationMethodModel -import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository -import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor -import com.android.systemui.flags.EnableSceneContainer -import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository -import com.android.systemui.keyguard.domain.interactor.keyguardEnabledInteractor -import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus -import com.android.systemui.kosmos.testScope -import com.android.systemui.lifecycle.activateIn -import com.android.systemui.scene.domain.interactor.sceneInteractor -import com.android.systemui.scene.domain.resolver.homeSceneFamilyResolver -import com.android.systemui.scene.shared.model.SceneFamilies -import com.android.systemui.scene.shared.model.Scenes -import com.android.systemui.shade.ui.viewmodel.notificationsShadeUserActionsViewModel -import com.android.systemui.testKosmos -import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.TestScope -import kotlinx.coroutines.test.runCurrent -import kotlinx.coroutines.test.runTest -import org.junit.Test -import org.junit.runner.RunWith - -@OptIn(ExperimentalCoroutinesApi::class) -@SmallTest -@RunWith(AndroidJUnit4::class) -@TestableLooper.RunWithLooper -@EnableSceneContainer -class NotificationsShadeUserActionsViewModelTest : SysuiTestCase() { - - private val kosmos = testKosmos() - private val testScope = kosmos.testScope - private val sceneInteractor by lazy { kosmos.sceneInteractor } - private val deviceUnlockedInteractor by lazy { kosmos.deviceUnlockedInteractor } - - private val underTest by lazy { kosmos.notificationsShadeUserActionsViewModel } - - @Test - fun upTransitionSceneKey_deviceLocked_lockscreen() = - testScope.runTest { - val actions by collectLastValue(underTest.actions) - lockDevice() - underTest.activateIn(this) - - assertThat((actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene) - .isEqualTo(SceneFamilies.Home) - assertThat(actions?.get(Swipe.Down)).isNull() - assertThat(kosmos.homeSceneFamilyResolver.resolvedScene.value) - .isEqualTo(Scenes.Lockscreen) - } - - @Test - fun upTransitionSceneKey_deviceLocked_keyguardDisabled_gone() = - testScope.runTest { - val actions by collectLastValue(underTest.actions) - lockDevice() - kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(false) - underTest.activateIn(this) - - assertThat((actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene) - .isEqualTo(SceneFamilies.Home) - assertThat(kosmos.homeSceneFamilyResolver.resolvedScene.value).isEqualTo(Scenes.Gone) - } - - @Test - fun upTransitionSceneKey_deviceUnlocked_gone() = - testScope.runTest { - val actions by collectLastValue(underTest.actions) - lockDevice() - unlockDevice() - underTest.activateIn(this) - - assertThat((actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene) - .isEqualTo(SceneFamilies.Home) - assertThat(actions?.get(Swipe.Down)).isNull() - assertThat(sceneInteractor.currentScene.value).isEqualTo(Scenes.Gone) - } - - @Test - fun upTransitionSceneKey_authMethodSwipe_lockscreenNotDismissed_goesToLockscreen() = - testScope.runTest { - val actions by collectLastValue(underTest.actions) - kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true) - kosmos.fakeAuthenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.None - ) - sceneInteractor.changeScene(Scenes.Lockscreen, "reason") - underTest.activateIn(this) - - assertThat((actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene) - .isEqualTo(SceneFamilies.Home) - assertThat(kosmos.homeSceneFamilyResolver.resolvedScene.value) - .isEqualTo(Scenes.Lockscreen) - } - - @Test - fun upTransitionSceneKey_authMethodSwipe_lockscreenDismissed_goesToGone() = - testScope.runTest { - val deviceUnlockStatus by collectLastValue(deviceUnlockedInteractor.deviceUnlockStatus) - val actions by collectLastValue(underTest.actions) - kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true) - kosmos.fakeAuthenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.None - ) - assertThat(deviceUnlockStatus?.isUnlocked).isTrue() - sceneInteractor // force the lazy; this will kick off StateFlows - runCurrent() - sceneInteractor.changeScene(Scenes.Gone, "reason") - underTest.activateIn(this) - - assertThat((actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene) - .isEqualTo(SceneFamilies.Home) - assertThat(kosmos.homeSceneFamilyResolver.resolvedScene.value).isEqualTo(Scenes.Gone) - } - - private fun TestScope.lockDevice() { - val deviceUnlockStatus by collectLastValue(deviceUnlockedInteractor.deviceUnlockStatus) - - kosmos.fakeAuthenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - assertThat(deviceUnlockStatus?.isUnlocked).isFalse() - sceneInteractor.changeScene(Scenes.Lockscreen, "reason") - runCurrent() - } - - private fun TestScope.unlockDevice() { - val deviceUnlockStatus by collectLastValue(deviceUnlockedInteractor.deviceUnlockStatus) - - kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( - SuccessFingerprintAuthenticationStatus(0, true) - ) - assertThat(deviceUnlockStatus?.isUnlocked).isTrue() - sceneInteractor.changeScene(Scenes.Gone, "reason") - runCurrent() - } -} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt index 3ae7a1672821..3966e1e44de7 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt @@ -21,8 +21,10 @@ import com.android.systemui.privacy.PrivacyDialogController import com.android.systemui.privacy.PrivacyDialogControllerV2 import com.android.systemui.privacy.PrivacyItemController import com.android.systemui.privacy.logging.PrivacyLogger +import com.android.systemui.shade.data.repository.shadeDialogContextInteractor import com.android.systemui.statusbar.phone.StatusIconContainer import com.android.systemui.statusbar.policy.DeviceProvisionedController +import com.android.systemui.testKosmos import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor @@ -49,6 +51,7 @@ private fun <T> any(): T = Mockito.any<T>() @RunWith(AndroidJUnit4::class) class HeaderPrivacyIconsControllerTest : SysuiTestCase() { + private val kosmos = testKosmos() @Mock private lateinit var privacyItemController: PrivacyItemController @Mock @@ -113,7 +116,8 @@ class HeaderPrivacyIconsControllerTest : SysuiTestCase() { broadcastDispatcher, safetyCenterManager, deviceProvisionedController, - featureFlags + featureFlags, + kosmos.shadeDialogContextInteractor, ) backgroundExecutor.runAllReady() 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 f005375a2ef9..7bb28dbabd5e 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 @@ -49,6 +49,7 @@ import com.android.systemui.statusbar.policy.data.repository.zenModeRepository import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogDelegate import com.android.systemui.statusbar.policy.ui.dialog.modesDialogEventLogger +import com.android.systemui.statusbar.policy.ui.dialog.viewmodel.modesDialogViewModel import com.android.systemui.testKosmos import com.android.systemui.util.mockito.any import com.android.systemui.util.settings.FakeSettings @@ -146,6 +147,7 @@ class ModesTileTest : SysuiTestCase() { tileDataInteractor, mapper, userActionInteractor, + kosmos.modesDialogViewModel, ) underTest.initialize() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt index 3d014b6822b4..a82a7de75cc0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt @@ -34,7 +34,7 @@ import com.android.systemui.scene.data.repository.WindowRootViewVisibilityReposi import com.android.systemui.statusbar.NotificationPresenter import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs -import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor +import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor import com.android.systemui.statusbar.notification.headsup.HeadsUpManager import com.android.systemui.statusbar.notification.init.NotificationsController import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor @@ -70,8 +70,7 @@ class WindowRootViewVisibilityInteractorTest : SysuiTestCase() { private val notificationsController = mock<NotificationsController>() private val powerInteractor = PowerInteractorFactory.create().powerInteractor private val activeNotificationsRepository = kosmos.activeNotificationListRepository - private val activeNotificationsInteractor = - ActiveNotificationsInteractor(activeNotificationsRepository, testDispatcher) + private val activeNotificationsInteractor = kosmos.activeNotificationsInteractor private val underTest = WindowRootViewVisibilityInteractor( diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt index 27e9f07af168..3d5daf6cf9c2 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt @@ -318,7 +318,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { val displayId = 1 setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = displayId)) val onSaved = { _: Uri? -> } - focusedDisplayRepository.emit(displayId) + focusedDisplayRepository.setDisplayId(displayId) screenshotExecutor.executeScreenshots( createScreenshotRequest( @@ -345,7 +345,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { display(TYPE_INTERNAL, id = Display.DEFAULT_DISPLAY), display(TYPE_EXTERNAL, id = 1), ) - focusedDisplayRepository.emit(5) // invalid display + focusedDisplayRepository.setDisplayId(5) // invalid display val onSaved = { _: Uri? -> } screenshotExecutor.executeScreenshots( createScreenshotRequest( diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt index ee9cb141a700..555c717e1e65 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt @@ -20,7 +20,7 @@ import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.testing.TestableLooper.RunWithLooper import android.view.Choreographer -import android.view.MotionEvent +import android.view.accessibility.AccessibilityEvent import android.widget.FrameLayout import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest @@ -45,7 +45,10 @@ import com.android.systemui.res.R import com.android.systemui.settings.brightness.data.repository.BrightnessMirrorShowingRepository import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirrorShowingInteractor import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler +import com.android.systemui.shade.data.repository.ShadeAnimationRepository +import com.android.systemui.shade.data.repository.ShadeRepositoryImpl import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor +import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractorLegacyImpl import com.android.systemui.statusbar.BlurUtils import com.android.systemui.statusbar.DragDownHelper import com.android.systemui.statusbar.LockscreenShadeTransitionController @@ -185,6 +188,10 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { notificationShadeDepthController, underTest, shadeViewController, + ShadeAnimationInteractorLegacyImpl( + ShadeAnimationRepository(), + ShadeRepositoryImpl(testScope), + ), panelExpansionInteractor, ShadeExpansionStateManager(), notificationStackScrollLayoutController, @@ -259,6 +266,20 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { verify(configurationForwarder).dispatchOnMovedToDisplay(eq(1), eq(config)) } + @Test + @EnableFlags(AConfigFlags.FLAG_SHADE_LAUNCH_ACCESSIBILITY) + fun requestSendAccessibilityEvent_duringLaunchAnimation_blocksFocusEvent() { + underTest.setAnimatingContentLaunch(true) + + assertThat( + underTest.requestSendAccessibilityEvent( + underTest.getChildAt(0), + AccessibilityEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED), + ) + ) + .isFalse() + } + private fun captureInteractionEventHandler() { verify(underTest).setInteractionEventHandler(interactionEventHandlerCaptor.capture()) interactionEventHandler = interactionEventHandlerCaptor.value diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QsBatteryModeControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QsBatteryModeControllerTest.kt index ab5fa8ef43fb..5566c10dc9e9 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QsBatteryModeControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QsBatteryModeControllerTest.kt @@ -38,7 +38,7 @@ class QsBatteryModeControllerTest : SysuiTestCase() { private val kosmos = testKosmos() private val insetsProviderStore = kosmos.fakeStatusBarContentInsetsProviderStore - private val insetsProvider = insetsProviderStore.defaultDisplay + private val insetsProvider = insetsProviderStore.forDisplay(context.displayId) @JvmField @Rule val mockitoRule = MockitoJUnit.rule()!! diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryTest.kt index 007a0fb87953..4f332d4bbed8 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryTest.kt @@ -17,15 +17,21 @@ package com.android.systemui.shade.data.repository import android.provider.Settings.Global.DEVELOPMENT_SHADE_DISPLAY_AWARENESS +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.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.kosmos.testScope +import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.shade.display.AnyExternalShadeDisplayPolicy import com.android.systemui.shade.display.DefaultDisplayShadePolicy +import com.android.systemui.shade.display.FakeShadeDisplayPolicy import com.android.systemui.shade.display.StatusBarTouchShadeDisplayPolicy import com.android.systemui.testKosmos import com.android.systemui.util.settings.fakeGlobalSettings @@ -37,24 +43,28 @@ import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class ShadeDisplaysRepositoryTest : SysuiTestCase() { - private val kosmos = testKosmos() + private val kosmos = testKosmos().useUnconfinedTestDispatcher() private val testScope = kosmos.testScope private val globalSettings = kosmos.fakeGlobalSettings private val displayRepository = kosmos.displayRepository private val defaultPolicy = DefaultDisplayShadePolicy() private val policies = kosmos.shadeDisplayPolicies + private val keyguardRepository = kosmos.fakeKeyguardRepository - private val underTest = + private fun createUnderTest(shadeOnDefaultDisplayWhenLocked: Boolean = false) = ShadeDisplaysRepositoryImpl( globalSettings, defaultPolicy, testScope.backgroundScope, policies, + shadeOnDefaultDisplayWhenLocked = shadeOnDefaultDisplayWhenLocked, + keyguardRepository, ) @Test fun policy_changing_propagatedFromTheLatestPolicy() = testScope.runTest { + val underTest = createUnderTest() val displayIds by collectValues(underTest.displayId) assertThat(displayIds).containsExactly(0) @@ -81,30 +91,54 @@ class ShadeDisplaysRepositoryTest : SysuiTestCase() { @Test fun policy_updatesBasedOnSettingValue_defaultDisplay() = testScope.runTest { - val policy by collectLastValue(underTest.policy) - + val underTest = createUnderTest() globalSettings.putString(DEVELOPMENT_SHADE_DISPLAY_AWARENESS, "default_display") - assertThat(policy).isInstanceOf(DefaultDisplayShadePolicy::class.java) + assertThat(underTest.currentPolicy).isInstanceOf(DefaultDisplayShadePolicy::class.java) } @Test fun policy_updatesBasedOnSettingValue_anyExternal() = testScope.runTest { - val policy by collectLastValue(underTest.policy) - + val underTest = createUnderTest() globalSettings.putString(DEVELOPMENT_SHADE_DISPLAY_AWARENESS, "any_external_display") - assertThat(policy).isInstanceOf(AnyExternalShadeDisplayPolicy::class.java) + assertThat(underTest.currentPolicy) + .isInstanceOf(AnyExternalShadeDisplayPolicy::class.java) } @Test fun policy_updatesBasedOnSettingValue_focusBased() = testScope.runTest { - val policy by collectLastValue(underTest.policy) - + val underTest = createUnderTest() globalSettings.putString(DEVELOPMENT_SHADE_DISPLAY_AWARENESS, "status_bar_latest_touch") - assertThat(policy).isInstanceOf(StatusBarTouchShadeDisplayPolicy::class.java) + assertThat(underTest.currentPolicy) + .isInstanceOf(StatusBarTouchShadeDisplayPolicy::class.java) + } + + @Test + fun displayId_afterKeyguardHides_goesBackToPreviousDisplay() = + testScope.runTest { + val underTest = createUnderTest(shadeOnDefaultDisplayWhenLocked = true) + globalSettings.putString( + DEVELOPMENT_SHADE_DISPLAY_AWARENESS, + FakeShadeDisplayPolicy.name, + ) + + val displayId by collectLastValue(underTest.displayId) + + displayRepository.addDisplays(display(id = 2, type = TYPE_EXTERNAL)) + FakeShadeDisplayPolicy.setDisplayId(2) + + assertThat(displayId).isEqualTo(2) + + keyguardRepository.setKeyguardShowing(true) + + assertThat(displayId).isEqualTo(Display.DEFAULT_DISPLAY) + + keyguardRepository.setKeyguardShowing(false) + + assertThat(displayId).isEqualTo(2) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadePrimaryDisplayCommandTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadePrimaryDisplayCommandTest.kt index eeb3e6b31c69..fd6bc98b006c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadePrimaryDisplayCommandTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadePrimaryDisplayCommandTest.kt @@ -25,7 +25,7 @@ import com.android.systemui.display.data.repository.displayRepository import com.android.systemui.kosmos.testScope import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.shade.ShadePrimaryDisplayCommand -import com.android.systemui.shade.display.ShadeDisplayPolicy +import com.android.systemui.shade.display.FakeShadeDisplayPolicy import com.android.systemui.statusbar.commandline.commandRegistry import com.android.systemui.testKosmos import com.android.systemui.util.settings.fakeGlobalSettings @@ -33,8 +33,6 @@ import com.google.common.truth.StringSubject import com.google.common.truth.Truth.assertThat import java.io.PrintWriter import java.io.StringWriter -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test @@ -118,23 +116,12 @@ class ShadePrimaryDisplayCommandTest : SysuiTestCase() { @Test fun policies_setsNewPolicy() = testScope.runTest { - val policy by collectLastValue(shadeDisplaysRepository.policy) - val newPolicy = policies.last().name + val newPolicy = FakeShadeDisplayPolicy.name commandRegistry.onShellCommand(pw, arrayOf("shade_display_override", newPolicy)) - assertThat(policy!!.name).isEqualTo(newPolicy) + assertThat(shadeDisplaysRepository.currentPolicy.name).isEqualTo(newPolicy) } - - private fun makePolicy(policyName: String): ShadeDisplayPolicy { - return object : ShadeDisplayPolicy { - override val name: String - get() = policyName - - override val displayId: StateFlow<Int> - get() = MutableStateFlow(0) - } - } } private fun StringSubject.containsAllIn(strings: List<String>) { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/display/FocusShadeDisplayPolicyTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/display/FocusShadeDisplayPolicyTest.kt new file mode 100644 index 000000000000..b4249ef72e62 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/display/FocusShadeDisplayPolicyTest.kt @@ -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.systemui.shade.display + +import android.view.Display +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.testScope +import com.android.systemui.kosmos.useUnconfinedTestDispatcher +import com.android.systemui.shade.data.repository.fakeFocusedDisplayRepository +import com.android.systemui.shade.data.repository.focusShadeDisplayPolicy +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 FocusShadeDisplayPolicyTest : SysuiTestCase() { + private val kosmos = testKosmos().useUnconfinedTestDispatcher() + private val testScope = kosmos.testScope + private val focusedDisplayRepository = kosmos.fakeFocusedDisplayRepository + + private val underTest = kosmos.focusShadeDisplayPolicy + + @Test + fun displayId_propagatedFromRepository() = + testScope.runTest { + val displayId by collectLastValue(underTest.displayId) + + assertThat(displayId).isEqualTo(Display.DEFAULT_DISPLAY) + + focusedDisplayRepository.setDisplayId(2) + + assertThat(displayId).isEqualTo(2) + + focusedDisplayRepository.setDisplayId(3) + + assertThat(displayId).isEqualTo(3) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicyTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicyTest.kt index 20dfd3e11947..e43c46b36a06 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicyTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicyTest.kt @@ -26,12 +26,11 @@ 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.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.kosmos.testScope import com.android.systemui.kosmos.useUnconfinedTestDispatcher +import com.android.systemui.shade.data.repository.statusBarTouchShadeDisplayPolicy import com.android.systemui.shade.domain.interactor.notificationElement import com.android.systemui.shade.domain.interactor.qsElement -import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlin.test.Test @@ -45,22 +44,9 @@ import org.mockito.kotlin.mock class StatusBarTouchShadeDisplayPolicyTest : SysuiTestCase() { private val kosmos = testKosmos().useUnconfinedTestDispatcher() private val testScope = kosmos.testScope - private val keyguardRepository = kosmos.fakeKeyguardRepository private val displayRepository = kosmos.displayRepository - private fun createUnderTest( - shadeOnDefaultDisplayWhenLocked: Boolean = false - ): StatusBarTouchShadeDisplayPolicy { - return StatusBarTouchShadeDisplayPolicy( - displayRepository, - keyguardRepository, - testScope.backgroundScope, - shadeOnDefaultDisplayWhenLocked = shadeOnDefaultDisplayWhenLocked, - shadeInteractor = { kosmos.shadeInteractor }, - { kosmos.qsElement }, - { kosmos.notificationElement }, - ) - } + private val underTest = kosmos.statusBarTouchShadeDisplayPolicy private fun createMotionEventForDisplay(displayId: Int, xCoordinate: Float = 0f): MotionEvent { return mock<MotionEvent> { @@ -71,15 +57,12 @@ class StatusBarTouchShadeDisplayPolicyTest : SysuiTestCase() { @Test fun displayId_defaultToDefaultDisplay() { - val underTest = createUnderTest() - assertThat(underTest.displayId.value).isEqualTo(Display.DEFAULT_DISPLAY) } @Test fun onStatusBarTouched_called_updatesDisplayId() = testScope.runTest { - val underTest = createUnderTest() val displayId by collectLastValue(underTest.displayId) displayRepository.addDisplays(display(id = 2, type = TYPE_EXTERNAL)) @@ -91,7 +74,6 @@ class StatusBarTouchShadeDisplayPolicyTest : SysuiTestCase() { @Test fun onStatusBarTouched_notExistentDisplay_displayIdNotUpdated() = testScope.runTest { - val underTest = createUnderTest() val displayIds by collectValues(underTest.displayId) assertThat(displayIds).isEqualTo(listOf(Display.DEFAULT_DISPLAY)) @@ -104,7 +86,6 @@ class StatusBarTouchShadeDisplayPolicyTest : SysuiTestCase() { @Test fun onStatusBarTouched_afterDisplayRemoved_goesBackToDefaultDisplay() = testScope.runTest { - val underTest = createUnderTest() val displayId by collectLastValue(underTest.displayId) displayRepository.addDisplays(display(id = 2, type = TYPE_EXTERNAL)) @@ -118,46 +99,8 @@ class StatusBarTouchShadeDisplayPolicyTest : SysuiTestCase() { } @Test - fun onStatusBarTouched_afterKeyguardVisible_goesBackToDefaultDisplay() = - testScope.runTest { - val underTest = createUnderTest(shadeOnDefaultDisplayWhenLocked = true) - val displayId by collectLastValue(underTest.displayId) - - displayRepository.addDisplays(display(id = 2, type = TYPE_EXTERNAL)) - underTest.onStatusBarTouched(createMotionEventForDisplay(2), STATUS_BAR_WIDTH) - - assertThat(displayId).isEqualTo(2) - - keyguardRepository.setKeyguardShowing(true) - - assertThat(displayId).isEqualTo(Display.DEFAULT_DISPLAY) - } - - @Test - fun onStatusBarTouched_afterKeyguardHides_goesBackToPreviousDisplay() = - testScope.runTest { - val underTest = createUnderTest(shadeOnDefaultDisplayWhenLocked = true) - val displayId by collectLastValue(underTest.displayId) - - displayRepository.addDisplays(display(id = 2, type = TYPE_EXTERNAL)) - underTest.onStatusBarTouched(createMotionEventForDisplay(2), STATUS_BAR_WIDTH) - - assertThat(displayId).isEqualTo(2) - - keyguardRepository.setKeyguardShowing(true) - - assertThat(displayId).isEqualTo(Display.DEFAULT_DISPLAY) - - keyguardRepository.setKeyguardShowing(false) - - assertThat(displayId).isEqualTo(2) - } - - @Test fun onStatusBarTouched_leftSide_intentSetToNotifications() = testScope.runTest { - val underTest = createUnderTest(shadeOnDefaultDisplayWhenLocked = true) - underTest.onStatusBarTouched( createMotionEventForDisplay(2, STATUS_BAR_WIDTH * 0.1f), STATUS_BAR_WIDTH, @@ -169,8 +112,6 @@ class StatusBarTouchShadeDisplayPolicyTest : SysuiTestCase() { @Test fun onStatusBarTouched_rightSide_intentSetToQs() = testScope.runTest { - val underTest = createUnderTest(shadeOnDefaultDisplayWhenLocked = true) - underTest.onStatusBarTouched( createMotionEventForDisplay(2, STATUS_BAR_WIDTH * 0.95f), STATUS_BAR_WIDTH, @@ -182,8 +123,6 @@ class StatusBarTouchShadeDisplayPolicyTest : SysuiTestCase() { @Test fun onStatusBarTouched_nullAfterConsumed() = testScope.runTest { - val underTest = createUnderTest(shadeOnDefaultDisplayWhenLocked = true) - underTest.onStatusBarTouched( createMotionEventForDisplay(2, STATUS_BAR_WIDTH * 0.1f), STATUS_BAR_WIDTH, 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 d3ba3dceb4cf..246283c236fe 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 @@ -22,17 +22,24 @@ import android.view.Display import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.common.ui.data.repository.configurationRepository +import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository import com.android.systemui.kosmos.testScope import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.scene.ui.view.mockShadeRootView import com.android.systemui.shade.data.repository.fakeShadeDisplaysRepository +import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository +import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs +import com.android.systemui.statusbar.notification.row.notificationRebindingTracker +import com.android.systemui.statusbar.notification.stack.notificationStackRebindingHider import com.android.systemui.testKosmos -import kotlinx.coroutines.test.advanceUntilIdle +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.never +import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.kotlin.any import org.mockito.kotlin.eq @@ -42,7 +49,7 @@ import org.mockito.kotlin.whenever @RunWith(AndroidJUnit4::class) @SmallTest class ShadeDisplaysInteractorTest : SysuiTestCase() { - val kosmos = testKosmos().useUnconfinedTestDispatcher() + private val kosmos = testKosmos().useUnconfinedTestDispatcher() private val testScope = kosmos.testScope private val shadeRootview = kosmos.mockShadeRootView @@ -52,6 +59,10 @@ class ShadeDisplaysInteractorTest : SysuiTestCase() { private val latencyTracker = kosmos.mockedShadeDisplayChangeLatencyTracker private val configuration = mock<Configuration>() private val display = mock<Display>() + private val activeNotificationRepository = kosmos.activeNotificationListRepository + private val notificationRebindingTracker = kosmos.notificationRebindingTracker + private val notificationStackRebindingHider = kosmos.notificationStackRebindingHider + private val configurationRepository = kosmos.fakeConfigurationRepository private val underTest by lazy { kosmos.shadeDisplaysInteractor } @@ -68,24 +79,26 @@ class ShadeDisplaysInteractorTest : SysuiTestCase() { } @Test - fun start_shadeInCorrectPosition_notAddedOrRemoved() { - whenever(display.displayId).thenReturn(0) - positionRepository.setDisplayId(0) + fun start_shadeInCorrectPosition_notAddedOrRemoved() = + testScope.runTest { + whenever(display.displayId).thenReturn(0) + positionRepository.setDisplayId(0) - underTest.start() + underTest.start() - verify(shadeContext, never()).reparentToDisplay(any()) - } + verify(shadeContext, never()).reparentToDisplay(any()) + } @Test - fun start_shadeInWrongPosition_changes() { - whenever(display.displayId).thenReturn(0) - positionRepository.setDisplayId(1) + fun start_shadeInWrongPosition_changes() = + testScope.runTest { + whenever(display.displayId).thenReturn(0) + positionRepository.setDisplayId(1) - underTest.start() + underTest.start() - verify(shadeContext).reparentToDisplay(eq(1)) - } + verify(shadeContext).reparentToDisplay(eq(1)) + } @Test fun start_shadeInWrongPosition_logsStartToLatencyTracker() = @@ -94,8 +107,81 @@ class ShadeDisplaysInteractorTest : SysuiTestCase() { positionRepository.setDisplayId(1) underTest.start() - advanceUntilIdle() verify(latencyTracker).onShadeDisplayChanging(eq(1)) } + + @Test + fun start_shadeInWrongPosition_someNotificationsVisible_hiddenThenShown() = + testScope.runTest { + whenever(display.displayId).thenReturn(0) + positionRepository.setDisplayId(1) + activeNotificationRepository.setActiveNotifs(1) + + underTest.start() + + verify(notificationStackRebindingHider).setVisible(eq(false), eq(false)) + configurationRepository.onMovedToDisplay(1) + verify(notificationStackRebindingHider).setVisible(eq(true), eq(true)) + } + + @Test + fun start_shadeInWrongPosition_someNotificationsVisible_waitsForInflationsBeforeShowingNssl() = + testScope.runTest { + whenever(display.displayId).thenReturn(0) + positionRepository.setDisplayId(1) + activeNotificationRepository.setActiveNotifs(1) + + val endRebinding = notificationRebindingTracker.trackRebinding("test") + + assertThat(notificationRebindingTracker.rebindingInProgressCount.value).isEqualTo(1) + + underTest.start() + + verify(notificationStackRebindingHider).setVisible(eq(false), eq(false)) + configurationRepository.onMovedToDisplay(1) + + // Verify that setVisible(true, true) is NOT called yet, as we + // first need to wait for notification bindings to have happened + verify(notificationStackRebindingHider, never()).setVisible(eq(true), eq(true)) + + endRebinding.onFinished() + + // Now verify that setVisible(true, true) is called + verify(notificationStackRebindingHider, times(1)).setVisible(eq(true), eq(true)) + } + + @Test + fun start_shadeInWrongPosition_noNotifications_nsslNotHidden() = + testScope.runTest { + whenever(display.displayId).thenReturn(0) + positionRepository.setDisplayId(1) + activeNotificationRepository.setActiveNotifs(0) + + underTest.start() + + verify(notificationStackRebindingHider) + .setVisible(visible = eq(true), animated = eq(false)) + verify(notificationStackRebindingHider, never()).setVisible(eq(false), eq(false)) + verify(notificationStackRebindingHider, never()).setVisible(eq(true), eq(true)) + } + + @Test + fun start_shadeInWrongPosition_waitsUntilMovedToDisplayReceived() = + testScope.runTest { + whenever(display.displayId).thenReturn(0) + positionRepository.setDisplayId(1) + activeNotificationRepository.setActiveNotifs(1) + + underTest.start() + + verify(notificationStackRebindingHider).setVisible(eq(false), eq(false)) + // It's not set to visible yet, as we first need to wait for the view to receive the + // display moved callback. + verify(notificationStackRebindingHider, never()).setVisible(eq(true), eq(true)) + + configurationRepository.onMovedToDisplay(1) + + verify(notificationStackRebindingHider).setVisible(eq(true), eq(true)) + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorImplTest.kt index a47db2ec728b..668f568d7f46 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorImplTest.kt @@ -16,15 +16,11 @@ package com.android.systemui.shade.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.coroutines.collectLastValue import com.android.systemui.kosmos.testScope -import com.android.systemui.shade.data.repository.shadeRepository -import com.android.systemui.shade.shared.flag.DualShade import com.android.systemui.shade.shared.model.ShadeMode import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat @@ -48,82 +44,79 @@ class ShadeModeInteractorImplTest : SysuiTestCase() { } @Test - @DisableFlags(DualShade.FLAG_NAME) fun legacyShadeMode_narrowScreen_singleShade() = testScope.runTest { val shadeMode by collectLastValue(underTest.shadeMode) - kosmos.shadeRepository.setShadeLayoutWide(false) + kosmos.enableSingleShade() assertThat(shadeMode).isEqualTo(ShadeMode.Single) } @Test - @DisableFlags(DualShade.FLAG_NAME) fun legacyShadeMode_wideScreen_splitShade() = testScope.runTest { val shadeMode by collectLastValue(underTest.shadeMode) - kosmos.shadeRepository.setShadeLayoutWide(true) + kosmos.enableSplitShade() assertThat(shadeMode).isEqualTo(ShadeMode.Split) } @Test - @EnableFlags(DualShade.FLAG_NAME) fun shadeMode_wideScreen_isDual() = testScope.runTest { val shadeMode by collectLastValue(underTest.shadeMode) - kosmos.shadeRepository.setShadeLayoutWide(true) + kosmos.enableDualShade(wideLayout = true) assertThat(shadeMode).isEqualTo(ShadeMode.Dual) } @Test - @EnableFlags(DualShade.FLAG_NAME) fun shadeMode_narrowScreen_isDual() = testScope.runTest { val shadeMode by collectLastValue(underTest.shadeMode) - kosmos.shadeRepository.setShadeLayoutWide(false) + kosmos.enableDualShade(wideLayout = false) assertThat(shadeMode).isEqualTo(ShadeMode.Dual) } @Test - @EnableFlags(DualShade.FLAG_NAME) - fun isDualShade_flagEnabled_true() = + fun isDualShade_settingEnabled_returnsTrue() = testScope.runTest { - // Initiate collection. + // TODO(b/391578667): Add a test case for user switching once the bug is fixed. val shadeMode by collectLastValue(underTest.shadeMode) + kosmos.enableDualShade() + assertThat(shadeMode).isEqualTo(ShadeMode.Dual) assertThat(underTest.isDualShade).isTrue() } @Test - @DisableFlags(DualShade.FLAG_NAME) - fun isDualShade_flagDisabled_false() = + fun isDualShade_settingDisabled_returnsFalse() = testScope.runTest { - // Initiate collection. val shadeMode by collectLastValue(underTest.shadeMode) + kosmos.disableDualShade() + assertThat(shadeMode).isNotEqualTo(ShadeMode.Dual) assertThat(underTest.isDualShade).isFalse() } @Test fun getTopEdgeSplitFraction_narrowScreen_splitInHalf() = testScope.runTest { - // Ensure isShadeLayoutWide is collected. - val isShadeLayoutWide by collectLastValue(underTest.isShadeLayoutWide) - kosmos.shadeRepository.setShadeLayoutWide(false) + val shadeMode by collectLastValue(underTest.shadeMode) + kosmos.enableDualShade(wideLayout = false) + assertThat(shadeMode).isEqualTo(ShadeMode.Dual) assertThat(underTest.getTopEdgeSplitFraction()).isEqualTo(0.5f) } @Test fun getTopEdgeSplitFraction_wideScreen_splitInHalf() = testScope.runTest { - // Ensure isShadeLayoutWide is collected. - val isShadeLayoutWide by collectLastValue(underTest.isShadeLayoutWide) - kosmos.shadeRepository.setShadeLayoutWide(true) + val shadeMode by collectLastValue(underTest.shadeMode) + kosmos.enableDualShade(wideLayout = true) + assertThat(shadeMode).isEqualTo(ShadeMode.Dual) assertThat(underTest.getTopEdgeSplitFraction()).isEqualTo(0.5f) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt index eb8ea8ba15cf..0da1e7f11582 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt @@ -102,12 +102,12 @@ class ShadeHeaderViewModelTest : SysuiTestCase() { @Test @DisableFlags(DualShade.FLAG_NAME) - fun onSystemIconContainerClicked_locked_collapsesShadeToLockscreen() = + fun onSystemIconChipClicked_locked_collapsesShadeToLockscreen() = testScope.runTest { setDeviceEntered(false) setScene(Scenes.Shade) - underTest.onSystemIconContainerClicked() + underTest.onSystemIconChipClicked() runCurrent() assertThat(sceneInteractor.currentScene.value).isEqualTo(Scenes.Lockscreen) @@ -115,16 +115,16 @@ class ShadeHeaderViewModelTest : SysuiTestCase() { @Test @EnableFlags(DualShade.FLAG_NAME) - fun onSystemIconContainerClicked_lockedOnDualShade_collapsesShadeToLockscreen() = + fun onSystemIconChipClicked_lockedOnQsShade_collapsesShadeToLockscreen() = testScope.runTest { val currentScene by collectLastValue(sceneInteractor.currentScene) val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) setDeviceEntered(false) setScene(Scenes.Lockscreen) - setOverlay(Overlays.NotificationsShade) + setOverlay(Overlays.QuickSettingsShade) assertThat(currentOverlays).isNotEmpty() - underTest.onSystemIconContainerClicked() + underTest.onSystemIconChipClicked() runCurrent() assertThat(currentScene).isEqualTo(Scenes.Lockscreen) @@ -132,13 +132,32 @@ class ShadeHeaderViewModelTest : SysuiTestCase() { } @Test + @EnableFlags(DualShade.FLAG_NAME) + fun onSystemIconChipClicked_lockedOnNotifShade_expandsQsShade() = + testScope.runTest { + val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) + setDeviceEntered(false) + setScene(Scenes.Lockscreen) + setOverlay(Overlays.NotificationsShade) + assertThat(currentOverlays).isNotEmpty() + + underTest.onSystemIconChipClicked() + runCurrent() + + assertThat(currentScene).isEqualTo(Scenes.Lockscreen) + assertThat(currentOverlays).contains(Overlays.QuickSettingsShade) + assertThat(currentOverlays).doesNotContain(Overlays.NotificationsShade) + } + + @Test @DisableFlags(DualShade.FLAG_NAME) - fun onSystemIconContainerClicked_unlocked_collapsesShadeToGone() = + fun onSystemIconChipClicked_unlocked_collapsesShadeToGone() = testScope.runTest { setDeviceEntered(true) setScene(Scenes.Shade) - underTest.onSystemIconContainerClicked() + underTest.onSystemIconChipClicked() runCurrent() assertThat(sceneInteractor.currentScene.value).isEqualTo(Scenes.Gone) @@ -146,7 +165,81 @@ class ShadeHeaderViewModelTest : SysuiTestCase() { @Test @EnableFlags(DualShade.FLAG_NAME) - fun onSystemIconContainerClicked_unlockedOnDualShade_collapsesShadeToGone() = + fun onSystemIconChipClicked_unlockedOnQsShade_collapsesShadeToGone() = + testScope.runTest { + val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) + setDeviceEntered(true) + setScene(Scenes.Gone) + setOverlay(Overlays.QuickSettingsShade) + assertThat(currentOverlays).isNotEmpty() + + underTest.onSystemIconChipClicked() + runCurrent() + + assertThat(currentScene).isEqualTo(Scenes.Gone) + assertThat(currentOverlays).isEmpty() + } + + @Test + @EnableFlags(DualShade.FLAG_NAME) + fun onSystemIconChipClicked_unlockedOnNotifShade_expandsQsShade() = + testScope.runTest { + val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) + setDeviceEntered(true) + setScene(Scenes.Gone) + setOverlay(Overlays.NotificationsShade) + assertThat(currentOverlays).isNotEmpty() + + underTest.onSystemIconChipClicked() + runCurrent() + + assertThat(currentScene).isEqualTo(Scenes.Gone) + assertThat(currentOverlays).contains(Overlays.QuickSettingsShade) + assertThat(currentOverlays).doesNotContain(Overlays.NotificationsShade) + } + + @Test + @EnableFlags(DualShade.FLAG_NAME) + fun onNotificationIconChipClicked_lockedOnNotifShade_collapsesShadeToLockscreen() = + testScope.runTest { + val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) + setDeviceEntered(false) + setScene(Scenes.Lockscreen) + setOverlay(Overlays.NotificationsShade) + assertThat(currentOverlays).isNotEmpty() + + underTest.onNotificationIconChipClicked() + runCurrent() + + assertThat(currentScene).isEqualTo(Scenes.Lockscreen) + assertThat(currentOverlays).isEmpty() + } + + @Test + @EnableFlags(DualShade.FLAG_NAME) + fun onNotificationIconChipClicked_lockedOnQsShade_expandsNotifShade() = + testScope.runTest { + val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) + setDeviceEntered(false) + setScene(Scenes.Lockscreen) + setOverlay(Overlays.QuickSettingsShade) + assertThat(currentOverlays).isNotEmpty() + + underTest.onNotificationIconChipClicked() + runCurrent() + + assertThat(currentScene).isEqualTo(Scenes.Lockscreen) + assertThat(currentOverlays).contains(Overlays.NotificationsShade) + assertThat(currentOverlays).doesNotContain(Overlays.QuickSettingsShade) + } + + @Test + @EnableFlags(DualShade.FLAG_NAME) + fun onNotificationIconChipClicked_unlockedOnNotifShade_collapsesShadeToGone() = testScope.runTest { val currentScene by collectLastValue(sceneInteractor.currentScene) val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) @@ -155,13 +248,32 @@ class ShadeHeaderViewModelTest : SysuiTestCase() { setOverlay(Overlays.NotificationsShade) assertThat(currentOverlays).isNotEmpty() - underTest.onSystemIconContainerClicked() + underTest.onNotificationIconChipClicked() runCurrent() assertThat(currentScene).isEqualTo(Scenes.Gone) assertThat(currentOverlays).isEmpty() } + @Test + @EnableFlags(DualShade.FLAG_NAME) + fun onNotificationIconChipClicked_unlockedOnQsShade_expandsNotifShade() = + testScope.runTest { + val currentScene by collectLastValue(sceneInteractor.currentScene) + val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) + setDeviceEntered(true) + setScene(Scenes.Gone) + setOverlay(Overlays.QuickSettingsShade) + assertThat(currentOverlays).isNotEmpty() + + underTest.onNotificationIconChipClicked() + runCurrent() + + assertThat(currentScene).isEqualTo(Scenes.Gone) + assertThat(currentOverlays).contains(Overlays.NotificationsShade) + assertThat(currentOverlays).doesNotContain(Overlays.QuickSettingsShade) + } + companion object { private val SUB_1 = SubscriptionModel( diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java index deaf57999b21..0713a247a4a3 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java @@ -225,6 +225,7 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { Notification notifWithPrivateVisibility = new Notification(); notifWithPrivateVisibility.visibility = VISIBILITY_PRIVATE; + notifWithPrivateVisibility.when = System.currentTimeMillis(); mCurrentUserNotif = new NotificationEntryBuilder() .setNotification(notifWithPrivateVisibility) .setUser(new UserHandle(mCurrentUser.id)) @@ -260,7 +261,7 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { .setChannel(channel) .setSensitiveContent(true) .setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build()); - mSensitiveNotifPostTime = mSensitiveContentNotif.getSbn().getPostTime(); + mSensitiveNotifPostTime = mSensitiveContentNotif.getSbn().getNotification().getWhen(); when(mNotifCollection.getEntry(mWorkProfileNotif.getKey())).thenReturn(mWorkProfileNotif); when(mKeyguardInteractorLazy.get()).thenReturn(mKeyguardInteractor); when(mKeyguardInteractor.isKeyguardDismissible()) 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 eec23d3ffb1a..55f3717535b7 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 @@ -609,7 +609,7 @@ class NotifChipsViewModelTest : SysuiTestCase() { listOf( activeNotificationModel( key = "notif", - statusBarChipIcon = mock<StatusBarIconView>(), + statusBarChipIcon = createStatusBarIconViewOrNull(), promotedContent = promotedContentBuilder.build(), ) ) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/compose/ChronometerStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChronometerStateTest.kt index e68045fe470f..4e92540396d3 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/compose/ChronometerStateTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChronometerStateTest.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 The Android Open Source Project + * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.statusbar.chips.ui.compose +package com.android.systemui.statusbar.chips.ui.viewmodel import android.text.format.DateUtils.formatElapsedTime import androidx.test.ext.junit.runners.AndroidJUnit4 diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolverTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolverTest.kt index 22906b8724b5..c515d940d2aa 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolverTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolverTest.kt @@ -144,7 +144,8 @@ class TargetSdkResolverTest : SysuiTestCase() { /* rankingAdjustment = */ 0, /* isBubble = */ false, /* proposedImportance = */ 0, - /* sensitiveContent = */ false + /* sensitiveContent = */ false, + /* summarization = */ null ) } } 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 7b120947b1d6..2aa1efaa429f 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 @@ -107,7 +107,7 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase { @Parameters(name = "{0}") public static List<FlagsParameterization> getParams() { return SceneContainerFlagParameterizationKt - .andSceneContainer(allCombinationsOf(Flags.FLAG_STABILIZE_HEADS_UP_GROUP)); + .andSceneContainer(allCombinationsOf(Flags.FLAG_STABILIZE_HEADS_UP_GROUP_V2)); } private VisualStabilityCoordinator mCoordinator; diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/BundleNotificationInfoTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/BundleNotificationInfoTest.java index b2962eeb9001..66277e2d13a6 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/BundleNotificationInfoTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/BundleNotificationInfoTest.java @@ -198,7 +198,8 @@ public class BundleNotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); // and the feedback button is clicked, final View feedbackButton = mInfo.findViewById(R.id.notification_guts_bundle_feedback); feedbackButton.performClick(); @@ -253,7 +254,8 @@ public class BundleNotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); final View feedbackButton = mInfo.findViewById(R.id.notification_guts_bundle_feedback); feedbackButton.performClick(); @@ -294,7 +296,8 @@ public class BundleNotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); final View feedbackButton = mInfo.findViewById(R.id.notification_guts_bundle_feedback); feedbackButton.performClick(); 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 8bca17f72c9f..25ae13fefdd7 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 @@ -109,6 +109,7 @@ class ExpandableNotificationRowControllerTest : SysuiTestCase() { private val statusBarService: IStatusBarService = mock() private val uiEventLogger: UiEventLogger = mock() private val msdlPlayer: MSDLPlayer = mock() + private val rebindingTracker: NotificationRebindingTracker = mock() private lateinit var controller: ExpandableNotificationRowController @Before @@ -150,6 +151,7 @@ class ExpandableNotificationRowControllerTest : SysuiTestCase() { statusBarService, uiEventLogger, msdlPlayer, + rebindingTracker, ) whenever(view.childrenContainer).thenReturn(childrenContainer) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt index 6a0a5bb3b191..39c42f183481 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt @@ -544,6 +544,7 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase( /* wasShownHighPriority = */ eq(true), eq(assistantFeedbackController), eq(metricsLogger), + any<View.OnClickListener>(), ) } @@ -580,6 +581,7 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase( /* wasShownHighPriority = */ eq(false), eq(assistantFeedbackController), eq(metricsLogger), + any<View.OnClickListener>(), ) } @@ -614,6 +616,7 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase( /* wasShownHighPriority = */ eq(false), eq(assistantFeedbackController), eq(metricsLogger), + any<View.OnClickListener>(), ) } @@ -651,6 +654,7 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase( /* wasShownHighPriority = */ eq(false), eq(assistantFeedbackController), eq(metricsLogger), + any<View.OnClickListener>(), ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java index 245a6a0b130c..fdba7ba34855 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java @@ -187,7 +187,7 @@ public class NotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, null); final TextView textView = mNotificationInfo.findViewById(R.id.pkg_name); assertTrue(textView.getText().toString().contains("App Name")); assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility()); @@ -213,7 +213,7 @@ public class NotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, null); final ImageView iconView = mNotificationInfo.findViewById(R.id.pkg_icon); assertEquals(iconDrawable, iconView.getDrawable()); } @@ -235,7 +235,7 @@ public class NotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, null); final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name); assertEquals(GONE, nameView.getVisibility()); } @@ -266,7 +266,7 @@ public class NotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, null); final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name); assertEquals(VISIBLE, nameView.getVisibility()); assertTrue(nameView.getText().toString().contains("Proxied")); @@ -289,7 +289,7 @@ public class NotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, null); final TextView groupNameView = mNotificationInfo.findViewById(R.id.group_name); assertEquals(GONE, groupNameView.getVisibility()); } @@ -317,7 +317,7 @@ public class NotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, null); final TextView groupNameView = mNotificationInfo.findViewById(R.id.group_name); assertEquals(View.VISIBLE, groupNameView.getVisibility()); assertEquals("Test Group Name", groupNameView.getText()); @@ -340,7 +340,7 @@ public class NotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, null); final TextView textView = mNotificationInfo.findViewById(R.id.channel_name); assertEquals(TEST_CHANNEL_NAME, textView.getText()); } @@ -362,7 +362,7 @@ public class NotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, null); final TextView textView = mNotificationInfo.findViewById(R.id.channel_name); assertEquals(GONE, textView.getVisibility()); } @@ -388,7 +388,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); final TextView textView = mNotificationInfo.findViewById(R.id.channel_name); assertEquals(VISIBLE, textView.getVisibility()); } @@ -410,7 +411,8 @@ public class NotificationInfoTest extends SysuiTestCase { true, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); final TextView textView = mNotificationInfo.findViewById(R.id.channel_name); assertEquals(VISIBLE, textView.getVisibility()); } @@ -436,7 +438,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); final View settingsButton = mNotificationInfo.findViewById(R.id.info); settingsButton.performClick(); @@ -461,7 +464,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); final View settingsButton = mNotificationInfo.findViewById(R.id.info); assertTrue(settingsButton.getVisibility() != View.VISIBLE); } @@ -486,7 +490,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); final View settingsButton = mNotificationInfo.findViewById(R.id.info); assertTrue(settingsButton.getVisibility() != View.VISIBLE); } @@ -508,7 +513,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); mNotificationInfo.bindNotification( mMockPackageManager, mMockINotificationManager, @@ -524,7 +530,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); final View settingsButton = mNotificationInfo.findViewById(R.id.info); assertEquals(View.VISIBLE, settingsButton.getVisibility()); } @@ -546,7 +553,8 @@ public class NotificationInfoTest extends SysuiTestCase { true, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); final TextView view = mNotificationInfo.findViewById(R.id.non_configurable_text); assertEquals(View.VISIBLE, view.getVisibility()); assertEquals(mContext.getString(R.string.notification_unblockable_desc), @@ -589,7 +597,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); final TextView view = mNotificationInfo.findViewById(R.id.non_configurable_call_text); assertEquals(View.VISIBLE, view.getVisibility()); assertEquals(mContext.getString(R.string.notification_unblockable_call_desc), @@ -632,7 +641,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); assertEquals(GONE, mNotificationInfo.findViewById(R.id.non_configurable_call_text).getVisibility()); assertEquals(VISIBLE, @@ -659,7 +669,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.automatic).getVisibility()); assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.automatic_summary).getVisibility()); } @@ -681,7 +692,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); assertEquals(GONE, mNotificationInfo.findViewById(R.id.automatic).getVisibility()); assertEquals(GONE, mNotificationInfo.findViewById(R.id.automatic_summary).getVisibility()); } @@ -705,7 +717,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); assertTrue(mNotificationInfo.findViewById(R.id.automatic).isSelected()); } @@ -726,7 +739,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); assertTrue(mNotificationInfo.findViewById(R.id.alert).isSelected()); } @@ -747,7 +761,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, false, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); assertTrue(mNotificationInfo.findViewById(R.id.silence).isSelected()); } @@ -768,7 +783,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); mTestableLooper.processAllMessages(); verify(mMockINotificationManager, never()).updateNotificationChannelForPackage( anyString(), eq(TEST_UID), any()); @@ -791,7 +807,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); assertEquals(1, mUiEventLogger.numLogs()); assertEquals(NotificationControlsEvent.NOTIFICATION_CONTROLS_OPEN.getId(), mUiEventLogger.eventId(0)); @@ -815,7 +832,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, false, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); mNotificationInfo.findViewById(R.id.alert).performClick(); mTestableLooper.processAllMessages(); @@ -842,7 +860,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); mNotificationInfo.findViewById(R.id.silence).performClick(); mTestableLooper.processAllMessages(); @@ -869,7 +888,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); mNotificationInfo.findViewById(R.id.automatic).performClick(); mTestableLooper.processAllMessages(); @@ -897,7 +917,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); mNotificationInfo.handleCloseControls(true, false); mTestableLooper.processAllMessages(); @@ -924,7 +945,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); mNotificationInfo.handleCloseControls(true, false); mTestableLooper.processAllMessages(); @@ -959,7 +981,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); mNotificationInfo.handleCloseControls(true, false); @@ -987,7 +1010,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); mNotificationInfo.findViewById(R.id.silence).performClick(); mNotificationInfo.findViewById(R.id.done).performClick(); @@ -1028,7 +1052,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, false, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); mNotificationInfo.findViewById(R.id.alert).performClick(); mNotificationInfo.findViewById(R.id.done).performClick(); @@ -1065,7 +1090,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); mNotificationInfo.findViewById(R.id.automatic).performClick(); mNotificationInfo.findViewById(R.id.done).performClick(); @@ -1097,7 +1123,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); mNotificationInfo.findViewById(R.id.silence).performClick(); mNotificationInfo.findViewById(R.id.done).performClick(); @@ -1133,7 +1160,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, false, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); assertEquals(mContext.getString(R.string.inline_done_button), ((TextView) mNotificationInfo.findViewById(R.id.done)).getText()); @@ -1171,7 +1199,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, false, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); mNotificationInfo.findViewById(R.id.silence).performClick(); mNotificationInfo.handleCloseControls(false, false); @@ -1202,7 +1231,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, false, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); assertEquals(mContext.getString(R.string.inline_done_button), ((TextView) mNotificationInfo.findViewById(R.id.done)).getText()); @@ -1240,7 +1270,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, true, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); mNotificationInfo.findViewById(R.id.silence).performClick(); mNotificationInfo.findViewById(R.id.done).performClick(); @@ -1269,7 +1300,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, false, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); assertEquals(mContext.getString(R.string.inline_done_button), ((TextView) mNotificationInfo.findViewById(R.id.done)).getText()); @@ -1300,7 +1332,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, false, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); mNotificationInfo.findViewById(R.id.alert).performClick(); mNotificationInfo.findViewById(R.id.done).performClick(); @@ -1335,7 +1368,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, false, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); mNotificationInfo.findViewById(R.id.alert).performClick(); mNotificationInfo.findViewById(R.id.done).performClick(); @@ -1368,7 +1402,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, false, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); mNotificationInfo.findViewById(R.id.alert).performClick(); mNotificationInfo.findViewById(R.id.done).performClick(); @@ -1401,7 +1436,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, false, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); mNotificationInfo.findViewById(R.id.alert).performClick(); @@ -1427,7 +1463,8 @@ public class NotificationInfoTest extends SysuiTestCase { false, false, mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + null); assertFalse(mNotificationInfo.willBeRemoved()); } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRebindingTrackerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRebindingTrackerTest.kt new file mode 100644 index 000000000000..8bffab617e0a --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRebindingTrackerTest.kt @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.testScope +import com.android.systemui.kosmos.useUnconfinedTestDispatcher +import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository +import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlin.test.Test +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class NotificationRebindingTrackerTest : SysuiTestCase() { + + private val kosmos = testKosmos().useUnconfinedTestDispatcher() + private val testScope = kosmos.testScope + private val activeNotificationRepository = kosmos.activeNotificationListRepository + + private val underTest: NotificationRebindingTracker = kosmos.notificationRebindingTracker + + @Before + fun setup() { + underTest.start() + } + + @Test + fun rebindingInProgressCount_noneStarted_isZero() = + testScope.runTest { + val count by collectLastValue(underTest.rebindingInProgressCount) + + assertThat(count).isEqualTo(0) + } + + @Test + fun rebindingInProgressCount_oneStarted_isOne() = + testScope.runTest { + val count by collectLastValue(underTest.rebindingInProgressCount) + activeNotificationRepository.setActiveNotifs(1) + + underTest.trackRebinding("0") + + assertThat(count).isEqualTo(1) + } + + @Test + fun rebindingInProgressCount_oneStartedThenFinished_goesFromOneToZero() = + testScope.runTest { + val count by collectLastValue(underTest.rebindingInProgressCount) + activeNotificationRepository.setActiveNotifs(1) + + val endRebinding = underTest.trackRebinding("0") + + assertThat(count).isEqualTo(1) + + endRebinding.onFinished() + + assertThat(count).isEqualTo(0) + } + + @Test + fun rebindingInProgressCount_twoStarted_goesToTwo() = + testScope.runTest { + val count by collectLastValue(underTest.rebindingInProgressCount) + activeNotificationRepository.setActiveNotifs(2) + + underTest.trackRebinding("0") + underTest.trackRebinding("1") + + assertThat(count).isEqualTo(2) + } + + @Test + fun rebindingInProgressCount_twoStarted_oneNotActiveAnymore_goesToZero() = + testScope.runTest { + val count by collectLastValue(underTest.rebindingInProgressCount) + activeNotificationRepository.setActiveNotifs(2) + + val finishFirstRebinding = underTest.trackRebinding("0") + underTest.trackRebinding("1") + + assertThat(count).isEqualTo(2) + + activeNotificationRepository.setActiveNotifs(1) + + assertThat(count).isEqualTo(1) + + finishFirstRebinding.onFinished() + + assertThat(count).isEqualTo(0) + } +} 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 b8d18757afbb..c39b252cd795 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 @@ -653,7 +653,8 @@ public class NotificationTestHelper { mock(SmartReplyConstants.class), mock(SmartReplyController.class), mock(IStatusBarService.class), - mock(UiEventLogger.class)); + mock(UiEventLogger.class), + mock(NotificationRebindingTracker.class)); row.setAboveShelfChangedListener(aboveShelf -> { }); mBindStage.getStageParams(entry).requireContentViews(extraInflationFlags); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfoTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfoTest.java new file mode 100644 index 000000000000..b33f93d5b523 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfoTest.java @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 static android.app.Notification.EXTRA_BUILDER_APPLICATION_INFO; +import static android.app.NotificationManager.IMPORTANCE_LOW; + +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.INotificationManager; +import android.app.Notification; +import android.app.NotificationChannel; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.os.UserHandle; +import android.platform.test.annotations.EnableFlags; +import android.service.notification.StatusBarNotification; +import android.telecom.TelecomManager; +import android.testing.TestableLooper; +import android.view.LayoutInflater; +import android.view.View; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.testing.UiEventLoggerFake; +import com.android.systemui.Dependency; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.res.R; +import com.android.systemui.statusbar.notification.AssistantFeedbackController; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; + +import 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.util.concurrent.CountDownLatch; + +@SmallTest +@RunWith(AndroidJUnit4.class) +@TestableLooper.RunWithLooper +public class PromotedNotificationInfoTest extends SysuiTestCase { + private static final String TEST_PACKAGE_NAME = "test_package"; + private static final int TEST_UID = 1; + private static final String TEST_CHANNEL = "test_channel"; + private static final String TEST_CHANNEL_NAME = "TEST CHANNEL NAME"; + + private TestableLooper mTestableLooper; + private PromotedNotificationInfo mInfo; + private NotificationChannel mNotificationChannel; + private StatusBarNotification mSbn; + private NotificationEntry mEntry; + private UiEventLoggerFake mUiEventLogger = new UiEventLoggerFake(); + + @Rule + public MockitoRule mockito = MockitoJUnit.rule(); + @Mock + private MetricsLogger mMetricsLogger; + @Mock + private INotificationManager mMockINotificationManager; + @Mock + private PackageManager mMockPackageManager; + @Mock + private OnUserInteractionCallback mOnUserInteractionCallback; + @Mock + private ChannelEditorDialogController mChannelEditorDialogController; + @Mock + private AssistantFeedbackController mAssistantFeedbackController; + @Mock + private TelecomManager mTelecomManager; + + @Before + public void setUp() throws Exception { + final ApplicationInfo applicationInfo = new ApplicationInfo(); + applicationInfo.uid = TEST_UID; // non-zero + + mNotificationChannel = new NotificationChannel( + TEST_CHANNEL, TEST_CHANNEL_NAME, IMPORTANCE_LOW); + Notification notification = new Notification(); + notification.extras.putParcelable(EXTRA_BUILDER_APPLICATION_INFO, applicationInfo); + mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, 0, + notification, UserHandle.getUserHandleForUid(TEST_UID), null, 0); + mEntry = new NotificationEntryBuilder().setSbn(mSbn).build(); + when(mAssistantFeedbackController.isFeedbackEnabled()).thenReturn(false); + + mTestableLooper = TestableLooper.get(this); + + mContext.addMockSystemService(TelecomManager.class, mTelecomManager); + + mDependency.injectTestDependency(Dependency.BG_LOOPER, mTestableLooper.getLooper()); + // Inflate the layout + final LayoutInflater layoutInflater = LayoutInflater.from(mContext); + mInfo = (PromotedNotificationInfo) layoutInflater.inflate( + R.layout.promoted_notification_info, null); + mInfo.setGutsParent(mock(NotificationGuts.class)); + // Our view is never attached to a window so the View#post methods in + // BundleNotificationInfo never get called. Setting this will skip the post and do the + // action immediately. + mInfo.mSkipPost = true; + } + + @Test + @EnableFlags(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI) + public void testBindNotification_setsOnClickListenerForFeedback() throws Exception { + + // Bind the notification to the Info object + final CountDownLatch latch = new CountDownLatch(1); + mInfo.bindNotification( + mMockPackageManager, + mMockINotificationManager, + mOnUserInteractionCallback, + mChannelEditorDialogController, + TEST_PACKAGE_NAME, + mNotificationChannel, + mEntry, + null, + null, + mUiEventLogger, + true, + false, + true, + mAssistantFeedbackController, + mMetricsLogger, + null); + // Click demote button + final View demoteButton = mInfo.findViewById(R.id.promoted_demote); + demoteButton.performClick(); + // verify that notiManager tried to demote + verify(mMockINotificationManager, atLeastOnce()).setCanBePromoted(TEST_PACKAGE_NAME, + mSbn.getUid(), false, true); + + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt index 50db9f7268e4..4b8a0c21f03d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.notification.stack import android.annotation.DimenRes +import android.platform.test.annotations.EnableFlags import android.service.notification.StatusBarNotification import android.view.View.VISIBLE import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -30,6 +31,7 @@ import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.SysuiStatusBarStateController import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor +import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiForceExpanded import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.ExpandableView import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController @@ -152,6 +154,29 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() { } @Test + @EnableFlags(PromotedNotificationUiForceExpanded.FLAG_NAME) + fun maxKeyguardNotificationsForPromotedOngoing_onLockscreenSpaceForMinHeightButNotIntrinsicHeight_returnsOne() { + setGapHeight(0f) + // No divider height since we're testing one element where index = 0 + + whenever(sysuiStatusBarStateController.state).thenReturn(StatusBarState.KEYGUARD) + whenever(lockscreenShadeTransitionController.fractionToShade).thenReturn(0f) + + val row = createMockRow(10f, isPromotedOngoing = true) + whenever(row.getMinHeight(any())).thenReturn(5) + + val maxNotifications = + computeMaxKeyguardNotifications( + listOf(row), + /* spaceForNotifications= */ 5f, + /* spaceForShelf= */ 0f, + /* shelfHeight= */ 0f, + ) + + assertThat(maxNotifications).isEqualTo(1) + } + + @Test fun computeMaxKeyguardNotifications_spaceForTwo_returnsTwo() { setGapHeight(gapHeight) val shelfHeight = shelfHeight + dividerHeight @@ -257,6 +282,26 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() { } @Test + @EnableFlags(PromotedNotificationUiForceExpanded.FLAG_NAME) + fun getSpaceNeeded_onLockscreenEnoughSpacePromotedOngoing_intrinsicHeight() { + setGapHeight(0f) + // No divider height since we're testing one element where index = 0 + + val row = createMockRow(10f, isPromotedOngoing = true) + whenever(row.getMinHeight(any())).thenReturn(5) + + val space = + sizeCalculator.getSpaceNeeded( + row, + visibleIndex = 0, + previousView = null, + stack = stackLayout, + onLockscreen = true, + ) + assertThat(space.whenEnoughSpace).isEqualTo(10f) + } + + @Test fun getSpaceNeeded_onLockscreenEnoughSpaceNotStickyHun_minHeight() { setGapHeight(0f) // No divider height since we're testing one element where index = 0 @@ -296,6 +341,26 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() { } @Test + @EnableFlags(PromotedNotificationUiForceExpanded.FLAG_NAME) + fun getSpaceNeeded_onLockscreenSavingSpacePromotedOngoing_minHeight() { + setGapHeight(0f) + // No divider height since we're testing one element where index = 0 + + val expandableView = createMockRow(10f, isPromotedOngoing = true) + whenever(expandableView.getMinHeight(any())).thenReturn(5) + + val space = + sizeCalculator.getSpaceNeeded( + expandableView, + visibleIndex = 0, + previousView = null, + stack = stackLayout, + onLockscreen = true, + ) + assertThat(space.whenSavingSpace).isEqualTo(5) + } + + @Test fun getSpaceNeeded_onLockscreenSavingSpaceNotStickyHun_minHeight() { setGapHeight(0f) // No divider height since we're testing one element where index = 0 @@ -366,6 +431,7 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() { isSticky: Boolean = false, isRemoved: Boolean = false, visibility: Int = VISIBLE, + isPromotedOngoing: Boolean = false, ): ExpandableNotificationRow { val row = mock(ExpandableNotificationRow::class.java) val entry = mock(NotificationEntry::class.java) @@ -378,6 +444,7 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() { whenever(row.getMinHeight(any())).thenReturn(height.toInt()) whenever(row.intrinsicHeight).thenReturn(height.toInt()) whenever(row.heightWithoutLockscreenConstraints).thenReturn(height.toInt()) + whenever(row.isPromotedOngoing).thenReturn(isPromotedOngoing) return row } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt index 43ad042ecf78..57b7df7a8d31 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt @@ -162,7 +162,7 @@ class KeyguardStatusBarViewControllerTest : SysuiTestCase() { Mockito.`when`(iconManagerFactory.create(ArgumentMatchers.any(), ArgumentMatchers.any())) .thenReturn(iconManager) - Mockito.`when`(statusBarContentInsetsProviderStore.defaultDisplay) + Mockito.`when`(statusBarContentInsetsProviderStore.forDisplay(context.displayId)) .thenReturn(kosmos.mockStatusBarContentInsetsProvider) allowTestableLooperAsMainThread() looper.runWithLooper { @@ -180,6 +180,7 @@ class KeyguardStatusBarViewControllerTest : SysuiTestCase() { private fun createController(): KeyguardStatusBarViewController { return KeyguardStatusBarViewController( kosmos.testDispatcher, + context, keyguardStatusBarView, carrierTextController, configurationController, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/SwitchAppsGestureRecognizerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/SwitchAppsGestureRecognizerTest.kt new file mode 100644 index 000000000000..756b79c6a868 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/SwitchAppsGestureRecognizerTest.kt @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.tutorial.ui.gesture + +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.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.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class SwitchAppsGestureRecognizerTest : SysuiTestCase() { + + private var gestureState: GestureState = NotStarted + private val gestureRecognizer = + SwitchAppsGestureRecognizer(gestureDistanceThresholdPx = SWIPE_DISTANCE.toInt()) + + @Before + fun before() { + gestureRecognizer.addGestureStateCallback { gestureState = it } + } + + @Test + fun triggersProgressRelativeToDistanceWhenSwipingRight() { + assertProgressWhileMovingFingers( + deltaX = SWIPE_DISTANCE / 2, + expected = InProgress(progress = 0.5f), + ) + assertProgressWhileMovingFingers( + deltaX = SWIPE_DISTANCE, + expected = InProgress(progress = 1f), + ) + } + + @Test + fun triggersProgressDoesNotGoBelowZero() { + // going in the wrong direction + assertProgressWhileMovingFingers( + deltaX = -SWIPE_DISTANCE, + expected = InProgress(progress = 0f), + ) + } + + @Test + fun triggersProgressDoesNotExceedOne() { + // going further than required distance + assertProgressWhileMovingFingers( + deltaX = SWIPE_DISTANCE * 2, + expected = InProgress(progress = 1f), + ) + } + + private fun assertProgressWhileMovingFingers(deltaX: Float, expected: InProgress) { + assertStateAfterEvents( + events = FourFingerGesture.eventsForGestureInProgress { move(deltaX = deltaX) }, + expectedState = expected, + ) + } + + private fun assertStateAfterEvents(events: List<MotionEvent>, expectedState: GestureState) { + events.forEach { gestureRecognizer.accept(it) } + assertThat(gestureState).isEqualTo(expectedState) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/SwitchAppsGestureScreenViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/SwitchAppsGestureScreenViewModelTest.kt new file mode 100644 index 000000000000..ee339b0b7446 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/SwitchAppsGestureScreenViewModelTest.kt @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.touchpad.tutorial.ui.viewmodel + +import android.content.res.mockResources +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.common.ui.data.repository.fakeConfigurationRepository +import com.android.systemui.inputdevice.tutorial.inputDeviceTutorialLogger +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Error +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Finished +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.InProgress +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.res.R +import com.android.systemui.testKosmos +import com.android.systemui.touchpad.tutorial.ui.gesture.FourFingerGesture +import com.android.systemui.touchpad.tutorial.ui.gesture.MultiFingerGesture.Companion.SWIPE_DISTANCE +import com.android.systemui.touchpad.ui.gesture.touchpadGestureResources +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.whenever + +@SmallTest +@RunWith(AndroidJUnit4::class) +class SwitchAppsGestureScreenViewModelTest : SysuiTestCase() { + + private val kosmos = testKosmos() + private val resources = kosmos.mockResources + private val fakeConfigRepository = kosmos.fakeConfigurationRepository + private val viewModel = + SwitchAppsGestureScreenViewModel( + GestureRecognizerAdapter( + SwitchAppsGestureRecognizerProvider(kosmos.touchpadGestureResources), + kosmos.inputDeviceTutorialLogger, + ) + ) + + @Before + fun before() { + setDistanceThreshold(threshold = SWIPE_DISTANCE - 1) + kosmos.useUnconfinedTestDispatcher() + } + + @Test + fun emitsProgressStateWithProgressAnimation() = + kosmos.runTest { + assertProgressWhileMovingFingers( + deltaX = SWIPE_DISTANCE, + expected = + InProgress( + progress = 1f, + startMarker = "gesture to R", + endMarker = "end of gesture", + ), + ) + } + + @Test + fun emitsFinishedStateWithSuccessAnimation() = + kosmos.runTest { + assertStateAfterEvents( + events = FourFingerGesture.swipeRight(), + expected = Finished(successAnimation = R.raw.trackpad_switch_apps_success), + ) + } + + @Test + fun emitErrorStateWhenLatestDistanceThresholdNotReached() = + kosmos.runTest { + fun performGesture() = + FourFingerGesture.swipeRight().forEach { viewModel.handleEvent(it) } + val state by collectLastValue(viewModel.tutorialState) + performGesture() + assertThat(state).isInstanceOf(Finished::class.java) + + setDistanceThreshold(SWIPE_DISTANCE + 1) + performGesture() // now swipe distance is not enough to trigger success + + assertThat(state).isInstanceOf(Error::class.java) + } + + private fun setDistanceThreshold(threshold: Float) { + whenever( + resources.getDimensionPixelSize( + R.dimen.touchpad_tutorial_gestures_distance_threshold + ) + ) + .thenReturn(threshold.toInt()) + fakeConfigRepository.onAnyConfigurationChange() + } + + private fun Kosmos.assertProgressWhileMovingFingers( + deltaX: Float, + expected: TutorialActionState, + ) { + assertStateAfterEvents( + events = FourFingerGesture.eventsForGestureInProgress { move(deltaX = deltaX) }, + expected = expected, + ) + } + + private fun Kosmos.assertStateAfterEvents( + events: List<MotionEvent>, + expected: TutorialActionState, + ) { + val state by collectLastValue(viewModel.tutorialState) + events.forEach { viewModel.handleEvent(it) } + assertThat(state).isEqualTo(expected) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractorTest.kt index fec186e862be..b837253f44a4 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractorTest.kt @@ -16,12 +16,16 @@ package com.android.systemui.volume.dialog.domain.interactor +import android.media.AudioManager.RINGER_MODE_NORMAL +import android.media.AudioManager.RINGER_MODE_SILENT +import android.media.AudioManager.RINGER_MODE_VIBRATE 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.kosmos.useUnconfinedTestDispatcher +import com.android.systemui.plugins.fakeVolumeDialogController import com.android.systemui.testKosmos import com.android.systemui.volume.dialog.domain.model.VolumeDialogEventModel import com.google.common.truth.Truth.assertThat @@ -44,4 +48,30 @@ class VolumeDialogCallbacksInteractorTest : SysuiTestCase() { val event by collectLastValue(underTest.event) assertThat(event).isInstanceOf(VolumeDialogEventModel.SubscribedToEvents::class.java) } + + @Test + fun showSilentHint_setsRingerModeToNormal() = + kosmos.runTest { + fakeVolumeDialogController.setRingerMode(RINGER_MODE_VIBRATE, false) + + underTest // It should eagerly collect the values and update the controller + fakeVolumeDialogController.onShowSilentHint() + fakeVolumeDialogController.getState() + + assertThat(fakeVolumeDialogController.state.ringerModeInternal) + .isEqualTo(RINGER_MODE_NORMAL) + } + + @Test + fun showVibrateHint_setsRingerModeToSilent() = + kosmos.runTest { + fakeVolumeDialogController.setRingerMode(RINGER_MODE_VIBRATE, false) + + underTest // It should eagerly collect the values and update the controller + fakeVolumeDialogController.onShowVibrateHint() + fakeVolumeDialogController.getState() + + assertThat(fakeVolumeDialogController.state.ringerModeInternal) + .isEqualTo(RINGER_MODE_SILENT) + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractorTest.kt index 7d5559933cd8..12885a83a70b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractorTest.kt @@ -25,14 +25,13 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.settingslib.volume.shared.model.RingerMode import com.android.systemui.SysuiTestCase -import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.kosmos.testScope +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.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.runCurrent -import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -43,8 +42,7 @@ import org.junit.runner.RunWith @TestableLooper.RunWithLooper class VolumeDialogRingerInteractorTest : SysuiTestCase() { - private val kosmos = testKosmos() - private val testScope = kosmos.testScope + private val kosmos = testKosmos().useUnconfinedTestDispatcher() private val controller = kosmos.fakeVolumeDialogController private lateinit var underTest: VolumeDialogRingerInteractor @@ -57,13 +55,11 @@ class VolumeDialogRingerInteractorTest : SysuiTestCase() { @Test fun setRingerMode_normal() = - testScope.runTest { - runCurrent() + kosmos.runTest { val ringerModel by collectLastValue(underTest.ringerModel) underTest.setRingerMode(RingerMode(RINGER_MODE_NORMAL)) controller.getState() - runCurrent() assertThat(ringerModel).isNotNull() assertThat(ringerModel?.currentRingerMode).isEqualTo(RingerMode(RINGER_MODE_NORMAL)) @@ -71,13 +67,11 @@ class VolumeDialogRingerInteractorTest : SysuiTestCase() { @Test fun setRingerMode_silent() = - testScope.runTest { - runCurrent() + kosmos.runTest { val ringerModel by collectLastValue(underTest.ringerModel) underTest.setRingerMode(RingerMode(RINGER_MODE_SILENT)) controller.getState() - runCurrent() assertThat(ringerModel).isNotNull() assertThat(ringerModel?.currentRingerMode).isEqualTo(RingerMode(RINGER_MODE_SILENT)) @@ -85,13 +79,11 @@ class VolumeDialogRingerInteractorTest : SysuiTestCase() { @Test fun setRingerMode_vibrate() = - testScope.runTest { - runCurrent() + kosmos.runTest { val ringerModel by collectLastValue(underTest.ringerModel) underTest.setRingerMode(RingerMode(RINGER_MODE_VIBRATE)) controller.getState() - runCurrent() assertThat(ringerModel).isNotNull() assertThat(ringerModel?.currentRingerMode).isEqualTo(RingerMode(RINGER_MODE_VIBRATE)) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInputEventsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInputEventsInteractorTest.kt index 799ca4a49038..0a50722d8fed 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInputEventsInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInputEventsInteractorTest.kt @@ -18,7 +18,6 @@ package com.android.systemui.volume.dialog.sliders.domain.interactor import android.app.ActivityManager import android.testing.TestableLooper -import android.view.MotionEvent import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase @@ -29,6 +28,7 @@ import com.android.systemui.testKosmos import com.android.systemui.volume.Events import com.android.systemui.volume.dialog.domain.interactor.volumeDialogVisibilityInteractor import com.android.systemui.volume.dialog.shared.model.VolumeDialogVisibilityModel +import com.android.systemui.volume.dialog.sliders.shared.model.SliderInputEvent import com.google.common.truth.Truth.assertThat import kotlin.test.Test import kotlin.time.Duration.Companion.seconds @@ -73,16 +73,7 @@ class VolumeDialogSliderInputEventsInteractorTest : SysuiTestCase() { assertThat(dialogVisibility) .isInstanceOf(VolumeDialogVisibilityModel.Visible::class.java) - underTest.onTouchEvent( - MotionEvent.obtain( - /* downTime = */ 0, - /* eventTime = */ 0, - /* action = */ 0, - /* x = */ 0f, - /* y = */ 0f, - /* metaState = */ 0, - ) - ) + underTest.onTouchEvent(SliderInputEvent.Touch.Start(0f, 0f)) advanceTimeBy(volumeDialogTimeout / 2) assertThat(dialogVisibility) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/MediaOutputAvailabilityCriteriaTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/MediaOutputAvailabilityCriteriaTest.kt new file mode 100644 index 000000000000..d0cc56860ce8 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/MediaOutputAvailabilityCriteriaTest.kt @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.panel.component.mediaoutput.domain + +import android.content.mockedContext +import android.content.packageManager +import android.content.pm.PackageManager.FEATURE_PC +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.collectLastValue +import com.android.systemui.kosmos.runTest +import com.android.systemui.kosmos.testScope +import com.android.systemui.testKosmos +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +class MediaOutputAvailabilityCriteriaTest : SysuiTestCase() { + + private val kosmos = testKosmos() + private val scope = kosmos.testScope + + private lateinit var underTest: MediaOutputAvailabilityCriteria + + @Before + fun setup() { + with(kosmos) { + underTest = MediaOutputAvailabilityCriteria(kosmos.mockedContext, scope.backgroundScope) + } + } + + @Test + fun isDesktop_unavailable() = + kosmos.runTest { + whenever(mockedContext.getPackageManager()).thenReturn(packageManager) + whenever(packageManager.hasSystemFeature(FEATURE_PC)).thenReturn(true) + + val isAvailable by collectLastValue(underTest.isAvailable()) + + assertThat(isAvailable).isFalse() + } + + @Test + fun notIsDesktop_available() = + kosmos.runTest { + whenever(mockedContext.getPackageManager()).thenReturn(packageManager) + whenever(packageManager.hasSystemFeature(FEATURE_PC)).thenReturn(false) + + val isAvailable by collectLastValue(underTest.isAvailable()) + + assertThat(isAvailable).isTrue() + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/GradientColorWallpaperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/GradientColorWallpaperTest.kt index ba6ea9f5e8bb..89410593fe62 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/GradientColorWallpaperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/GradientColorWallpaperTest.kt @@ -16,11 +16,14 @@ package com.android.systemui.wallpapers +import android.app.Flags import android.content.Context import android.graphics.Canvas import android.graphics.Paint import android.graphics.Rect import android.graphics.RectF +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags import android.service.wallpaper.WallpaperService.Engine import android.testing.TestableLooper.RunWithLooper import android.view.Surface @@ -37,6 +40,7 @@ import org.mockito.Mockito.spy import org.mockito.MockitoAnnotations import org.mockito.kotlin.any import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyZeroInteractions import org.mockito.kotlin.whenever @SmallTest @@ -70,6 +74,18 @@ class GradientColorWallpaperTest : SysuiTestCase() { } @Test + @DisableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WALLPAPER) + fun onSurfaceRedrawNeeded_flagDisabled_shouldNotDrawInCanvas() { + val engine = createGradientColorWallpaperEngine() + engine.onCreate(surfaceHolder) + + engine.onSurfaceRedrawNeeded(surfaceHolder) + + verifyZeroInteractions(canvas) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WALLPAPER) fun onSurfaceRedrawNeeded_shouldDrawInCanvas() { val engine = createGradientColorWallpaperEngine() engine.onCreate(surfaceHolder) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt index 111c232280c3..d6343c840d9b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt @@ -19,10 +19,12 @@ package com.android.systemui.wallpapers.data.repository import android.app.WallpaperInfo import android.view.View import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.flowOf /** Fake implementation of the wallpaper repository. */ class FakeWallpaperRepository : WallpaperRepository { override val wallpaperInfo = MutableStateFlow<WallpaperInfo?>(null) - override val wallpaperSupportsAmbientMode = MutableStateFlow(false) + override val wallpaperSupportsAmbientMode = flowOf(false) override var rootView: View? = null + override val shouldSendFocalArea = MutableStateFlow(false) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt index b8dd334dcad9..115edd0d3bb0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt @@ -24,11 +24,13 @@ import android.content.pm.UserInfo import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.internal.R import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyguard.data.repository.FakeKeyguardClockRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.shared.Flags as SharedFlags import com.android.systemui.user.data.model.SelectedUserModel import com.android.systemui.user.data.model.SelectionStatus import com.android.systemui.user.data.repository.FakeUserRepository @@ -65,7 +67,6 @@ class WallpaperRepositoryImplTest : SysuiTestCase() { fakeBroadcastDispatcher, userRepository, keyguardRepository, - keyguardClockRepository, wallpaperManager, context, ) @@ -74,10 +75,6 @@ class WallpaperRepositoryImplTest : SysuiTestCase() { @Before fun setUp() { whenever(wallpaperManager.isWallpaperSupported).thenReturn(true) - context.orCreateTestableResources.addOverride( - com.android.internal.R.bool.config_dozeSupportsAodWallpaper, - true, - ) } @Test @@ -225,221 +222,36 @@ class WallpaperRepositoryImplTest : SysuiTestCase() { } @Test - fun wallpaperInfo_deviceDoesNotSupportAmbientWallpaper_alwaysFalse() = + @EnableFlags(SharedFlags.FLAG_AMBIENT_AOD) + fun wallpaperSupportsAmbientMode_deviceDoesNotSupport_false() = testScope.runTest { context.orCreateTestableResources.addOverride( - com.android.internal.R.bool.config_dozeSupportsAodWallpaper, + R.bool.config_dozeSupportsAodWallpaper, false, ) - val latest by collectLastValue(underTest.wallpaperInfo) - assertThat(latest).isNull() - - // Even WHEN there *is* current wallpaper - val wp1 = mock<WallpaperInfo>() - whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(wp1) - fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly( - context, - Intent(Intent.ACTION_WALLPAPER_CHANGED), - ) - - // THEN the value is still null because wallpaper isn't supported - assertThat(latest).isNull() - } - - @Test - fun wallpaperSupportsAmbientMode_nullInfo_false() = - testScope.runTest { - val latest by collectLastValue(underTest.wallpaperSupportsAmbientMode) - - whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(null) - - fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly( - context, - Intent(Intent.ACTION_WALLPAPER_CHANGED), - ) - - assertThat(latest).isFalse() - } - - @Test - fun wallpaperSupportsAmbientMode_infoDoesNotSupport_false() = - testScope.runTest { - val latest by collectLastValue(underTest.wallpaperSupportsAmbientMode) - - whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(UNSUPPORTED_WP) - - fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly( - context, - Intent(Intent.ACTION_WALLPAPER_CHANGED), - ) - - assertThat(latest).isFalse() - } - - @Test - fun wallpaperSupportsAmbientMode_infoSupports_true() = - testScope.runTest { - val latest by collectLastValue(underTest.wallpaperSupportsAmbientMode) - - whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(SUPPORTED_WP) - - fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly( - context, - Intent(Intent.ACTION_WALLPAPER_CHANGED), - ) - - assertThat(latest).isTrue() - } - - @Test - fun wallpaperSupportsAmbientMode_initialValueIsFetched_true() = - testScope.runTest { - whenever(wallpaperManager.getWallpaperInfoForUser(USER_WITH_SUPPORTED_WP.id)) - .thenReturn(SUPPORTED_WP) - userRepository.setUserInfos(listOf(USER_WITH_SUPPORTED_WP)) - userRepository.setSelectedUserInfo(USER_WITH_SUPPORTED_WP) - - // Start up the repo and let it run the initial fetch - underTest.wallpaperSupportsAmbientMode - runCurrent() - - // WHEN the repo initially starts up (underTest is lazy), then it fetches the current - // value for the wallpaper - assertThat(underTest.wallpaperSupportsAmbientMode.value).isTrue() - } - - @Test - fun wallpaperSupportsAmbientMode_initialValueIsFetched_false() = - testScope.runTest { - whenever(wallpaperManager.getWallpaperInfoForUser(USER_WITH_UNSUPPORTED_WP.id)) - .thenReturn(UNSUPPORTED_WP) - userRepository.setUserInfos(listOf(USER_WITH_UNSUPPORTED_WP)) - userRepository.setSelectedUserInfo(USER_WITH_UNSUPPORTED_WP) - - // WHEN the repo initially starts up (underTest is lazy), then it fetches the current - // value for the wallpaper - assertThat(underTest.wallpaperSupportsAmbientMode.value).isFalse() - } - - @Test - fun wallpaperSupportsAmbientMode_updatesOnUserChanged() = - testScope.runTest { - val latest by collectLastValue(underTest.wallpaperSupportsAmbientMode) - - whenever(wallpaperManager.getWallpaperInfoForUser(USER_WITH_SUPPORTED_WP.id)) - .thenReturn(SUPPORTED_WP) - whenever(wallpaperManager.getWallpaperInfoForUser(USER_WITH_UNSUPPORTED_WP.id)) - .thenReturn(UNSUPPORTED_WP) - userRepository.setUserInfos(listOf(USER_WITH_SUPPORTED_WP, USER_WITH_UNSUPPORTED_WP)) - - // WHEN a user with supported wallpaper is selected - userRepository.setSelectedUserInfo(USER_WITH_SUPPORTED_WP) - - // THEN it's true - assertThat(latest).isTrue() - - // WHEN the user is switched to a user with unsupported wallpaper - userRepository.setSelectedUserInfo(USER_WITH_UNSUPPORTED_WP) - - // THEN it's false - assertThat(latest).isFalse() - } - - @Test - fun wallpaperSupportsAmbientMode_doesNotUpdateOnUserChanging() = - testScope.runTest { - val latest by collectLastValue(underTest.wallpaperSupportsAmbientMode) - - whenever(wallpaperManager.getWallpaperInfoForUser(USER_WITH_SUPPORTED_WP.id)) - .thenReturn(SUPPORTED_WP) - whenever(wallpaperManager.getWallpaperInfoForUser(USER_WITH_UNSUPPORTED_WP.id)) - .thenReturn(UNSUPPORTED_WP) - userRepository.setUserInfos(listOf(USER_WITH_SUPPORTED_WP, USER_WITH_UNSUPPORTED_WP)) - - // WHEN a user with supported wallpaper is selected - userRepository.setSelectedUserInfo(USER_WITH_SUPPORTED_WP) - - // THEN it's true - assertThat(latest).isTrue() - - // WHEN the user has started switching to a user with unsupported wallpaper but hasn't - // finished yet - userRepository.selectedUser.value = - SelectedUserModel(USER_WITH_UNSUPPORTED_WP, SelectionStatus.SELECTION_IN_PROGRESS) - - // THEN it still matches the old user - assertThat(latest).isTrue() - } - - @Test - fun wallpaperSupportsAmbientMode_updatesOnIntent() = - testScope.runTest { - val latest by collectLastValue(underTest.wallpaperSupportsAmbientMode) - - whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(UNSUPPORTED_WP) - - assertThat(latest).isFalse() - - // WHEN the info now supports ambient mode and a broadcast is sent - whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(SUPPORTED_WP) - fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly( - context, - Intent(Intent.ACTION_WALLPAPER_CHANGED), - ) - - // THEN the flow updates - assertThat(latest).isTrue() - } - - @Test - fun wallpaperSupportsAmbientMode_wallpaperNotSupported_alwaysFalse() = - testScope.runTest { - whenever(wallpaperManager.isWallpaperSupported).thenReturn(false) - val latest by collectLastValue(underTest.wallpaperSupportsAmbientMode) assertThat(latest).isFalse() - - // Even WHEN the current wallpaper *does* support ambient mode - whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(SUPPORTED_WP) - - fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly( - context, - Intent(Intent.ACTION_WALLPAPER_CHANGED), - ) - - // THEN the value is still false because wallpaper isn't supported - assertThat(latest).isFalse() } @Test - fun wallpaperSupportsAmbientMode_deviceDoesNotSupportAmbientWallpaper_alwaysFalse() = + @EnableFlags(SharedFlags.FLAG_AMBIENT_AOD) + fun wallpaperSupportsAmbientMode_deviceDoesSupport_true() = testScope.runTest { context.orCreateTestableResources.addOverride( - com.android.internal.R.bool.config_dozeSupportsAodWallpaper, - false, + R.bool.config_dozeSupportsAodWallpaper, + true, ) val latest by collectLastValue(underTest.wallpaperSupportsAmbientMode) - assertThat(latest).isFalse() - - // Even WHEN the current wallpaper *does* support ambient mode - whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(SUPPORTED_WP) - - fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly( - context, - Intent(Intent.ACTION_WALLPAPER_CHANGED), - ) - - // THEN the value is still false because the device doesn't support it - assertThat(latest).isFalse() + assertThat(latest).isTrue() } @Test @EnableFlags(Flags.FLAG_MAGIC_PORTRAIT_WALLPAPERS) fun shouldSendNotificationLayout_setMagicPortraitWallpaper_launchSendLayoutJob() = testScope.runTest { - val latest by collectLastValue(underTest.shouldSendNotificationLayout) + val latest by collectLastValue(underTest.shouldSendFocalArea) val magicPortraitWallpaper = mock<WallpaperInfo>().apply { whenever(this.component) @@ -460,7 +272,7 @@ class WallpaperRepositoryImplTest : SysuiTestCase() { @EnableFlags(Flags.FLAG_MAGIC_PORTRAIT_WALLPAPERS) fun shouldSendNotificationLayout_setNotMagicPortraitWallpaper_cancelSendLayoutJob() = testScope.runTest { - val latest by collectLastValue(underTest.shouldSendNotificationLayout) + val latest by collectLastValue(underTest.shouldSendFocalArea) val magicPortraitWallpaper = MAGIC_PORTRAIT_WP whenever(wallpaperManager.getWallpaperInfoForUser(any())) .thenReturn(magicPortraitWallpaper) diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockFaceLayout.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockFaceLayout.kt index a075d0d5b249..37b611f3c6b9 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockFaceLayout.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockFaceLayout.kt @@ -111,28 +111,37 @@ class DefaultClockFaceLayout(val view: View) : ClockFaceLayout { connect(lockscreenClockViewLargeId, START, PARENT_ID, START) connect(lockscreenClockViewLargeId, END, PARENT_ID, END) - // In preview, we'll show UDFPS icon for UDFPS devices - // and nothing for non-UDFPS devices, - // and we're not planning to add this vide in clockHostView - // so we only need position of device entry icon to constrain clock - // Copied calculation codes from applyConstraints in DefaultDeviceEntrySection - clockPreviewConfig.lockId?.let { lockId -> - connect(lockscreenClockViewLargeId, BOTTOM, lockId, TOP) + clockPreviewConfig.udfpsTop?.let { + val screenHeight = context.resources.displayMetrics.heightPixels + connect( + lockscreenClockViewLargeId, + BOTTOM, + PARENT_ID, + BOTTOM, + (screenHeight - it).toInt(), + ) } ?: run { - val bottomPaddingPx = context.getDimen("lock_icon_margin_bottom") - val defaultDensity = - DisplayMetrics.DENSITY_DEVICE_STABLE.toFloat() / - DisplayMetrics.DENSITY_DEFAULT.toFloat() - val lockIconRadiusPx = (defaultDensity * 36).toInt() - val clockBottomMargin = bottomPaddingPx + 2 * lockIconRadiusPx - connect( - lockscreenClockViewLargeId, - BOTTOM, - PARENT_ID, - BOTTOM, - clockBottomMargin, - ) + // Copied calculation codes from applyConstraints in + // DefaultDeviceEntrySection + clockPreviewConfig.lockId?.let { lockId -> + connect(lockscreenClockViewLargeId, BOTTOM, lockId, TOP) + } + ?: run { + val bottomPaddingPx = context.getDimen("lock_icon_margin_bottom") + val defaultDensity = + DisplayMetrics.DENSITY_DEVICE_STABLE.toFloat() / + DisplayMetrics.DENSITY_DEFAULT.toFloat() + val lockIconRadiusPx = (defaultDensity * 36).toInt() + val clockBottomMargin = bottomPaddingPx + 2 * lockIconRadiusPx + connect( + lockscreenClockViewLargeId, + BOTTOM, + PARENT_ID, + BOTTOM, + clockBottomMargin, + ) + } } val smallClockViewId = context.getId("lockscreen_clock_view") diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockPreviewConfig.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockPreviewConfig.kt index c705c51a00d5..abc8e150bfd2 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockPreviewConfig.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockPreviewConfig.kt @@ -25,6 +25,7 @@ data class ClockPreviewConfig( val isShadeLayoutWide: Boolean, val isSceneContainerFlagEnabled: Boolean = false, val lockId: Int? = null, + val udfpsTop: Float? = null, ) { fun getSmallClockTopPadding( statusBarHeight: Int = SystemBarUtils.getStatusBarHeight(context) diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml index b256518e99ac..e7d6b2fe08f4 100644 --- a/packages/SystemUI/res-keyguard/values/styles.xml +++ b/packages/SystemUI/res-keyguard/values/styles.xml @@ -128,7 +128,7 @@ <item name="android:windowNoTitle">true</item> <item name="android:windowFullscreen">true</item> <item name="android:windowContentOverlay">@null</item> - <item name="android:colorBackground">@*android:color/background_material_dark</item> + <item name="android:colorBackground">@android:color/transparent</item> </style> <style name="TextAppearance.Keyguard"> @@ -159,6 +159,17 @@ <item name="android:shadowRadius">?attr/shadowRadius</item> </style> + <style name="TextAppearance.Keyguard.BottomArea.DoubleShadow"> + <item name="keyShadowBlur">0.5dp</item> + <item name="keyShadowOffsetX">0.5dp</item> + <item name="keyShadowOffsetY">0.5dp</item> + <item name="keyShadowAlpha">0.8</item> + <item name="ambientShadowBlur">0.5dp</item> + <item name="ambientShadowOffsetX">0.5dp</item> + <item name="ambientShadowOffsetY">0.5dp</item> + <item name="ambientShadowAlpha">0.6</item> + </style> + <style name="TextAppearance.Keyguard.BottomArea.Button"> <item name="android:shadowRadius">0</item> </style> diff --git a/packages/SystemUI/res/drawable-nodpi/hub_onboarding_bg.png b/packages/SystemUI/res/drawable-nodpi/hub_onboarding_bg.png Binary files differnew file mode 100644 index 000000000000..bd55e83b377b --- /dev/null +++ b/packages/SystemUI/res/drawable-nodpi/hub_onboarding_bg.png diff --git a/packages/SystemUI/res/drawable/ic_widgets.xml b/packages/SystemUI/res/drawable/ic_widgets.xml deleted file mode 100644 index 9e05809bfb33..000000000000 --- a/packages/SystemUI/res/drawable/ic_widgets.xml +++ /dev/null @@ -1,26 +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. - --> - -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:tint="?attr/colorControlNormal" - android:viewportHeight="960" - android:viewportWidth="960"> - <path - android:fillColor="@android:color/black" - android:pathData="M666,520L440,294L666,68L892,294L666,520ZM120,440L120,120L440,120L440,440L120,440ZM520,840L520,520L840,520L840,840L520,840ZM120,840L120,520L440,520L440,840L120,840ZM200,360L360,360L360,200L200,200L200,360ZM667,408L780,295L667,182L554,295L667,408ZM600,760L760,760L760,600L600,600L600,760ZM200,760L360,760L360,600L200,600L200,760ZM360,360L360,360L360,360L360,360L360,360ZM554,295L554,295L554,295L554,295L554,295ZM360,600L360,600L360,600L360,600L360,600ZM600,600L600,600L600,600L600,600L600,600Z" /> -</vector> diff --git a/packages/SystemUI/res/drawable/notif_footer_btn_background.xml b/packages/SystemUI/res/drawable/notif_footer_btn_background.xml index 1d5e09d9b260..e1e60920ab01 100644 --- a/packages/SystemUI/res/drawable/notif_footer_btn_background.xml +++ b/packages/SystemUI/res/drawable/notif_footer_btn_background.xml @@ -26,6 +26,9 @@ <padding android:left="20dp" android:right="20dp" /> + <!-- TODO(b/294830092): Update to the blur surface effect color token when + the resource workaround is resolved and + notification_shade_blur is enabled. --> <solid android:color="@androidprv:color/materialColorSurfaceContainerHigh" /> </shape> </inset> diff --git a/packages/SystemUI/res/layout/media_projection_recent_tasks.xml b/packages/SystemUI/res/layout/media_projection_recent_tasks.xml index 31baf26e4a1b..3c810a43a3a7 100644 --- a/packages/SystemUI/res/layout/media_projection_recent_tasks.xml +++ b/packages/SystemUI/res/layout/media_projection_recent_tasks.xml @@ -51,5 +51,5 @@ android:layout_marginTop="24dp" android:importantForAccessibility="no" android:src="@*android:drawable/ic_drag_handle" - android:tint="?android:attr/textColorSecondary" /> + android:tint="@*android:color/materialColorSecondary" /> </LinearLayout> diff --git a/packages/SystemUI/res/layout/promoted_notification_info.xml b/packages/SystemUI/res/layout/promoted_notification_info.xml new file mode 100644 index 000000000000..5d170a98a806 --- /dev/null +++ b/packages/SystemUI/res/layout/promoted_notification_info.xml @@ -0,0 +1,387 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. +--> + +<com.android.systemui.statusbar.notification.row.PromotedNotificationInfo + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + android:id="@+id/notification_guts" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:focusable="true" + android:clipChildren="false" + android:clipToPadding="true" + android:orientation="vertical" + android:paddingStart="@dimen/notification_shade_content_margin_horizontal"> + + <!-- Package Info --> + <LinearLayout + android:id="@+id/header" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="center_vertical" + android:clipChildren="false" + android:paddingTop="@dimen/notification_guts_header_top_padding" + android:clipToPadding="true"> + <ImageView + android:id="@+id/pkg_icon" + android:layout_width="@dimen/notification_guts_conversation_icon_size" + android:layout_height="@dimen/notification_guts_conversation_icon_size" + android:layout_centerVertical="true" + android:layout_alignParentStart="true" + android:layout_marginEnd="15dp" /> + <LinearLayout + android:id="@+id/names" + android:layout_weight="1" + android:layout_width="0dp" + android:orientation="vertical" + android:layout_height="wrap_content" + android:minHeight="@dimen/notification_guts_conversation_icon_size" + android:layout_centerVertical="true" + android:gravity="center_vertical" + android:layout_alignEnd="@id/pkg_icon" + android:layout_toEndOf="@id/pkg_icon"> + <TextView + android:id="@+id/channel_name" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textDirection="locale" + style="@style/TextAppearance.NotificationImportanceChannel"/> + <TextView + android:id="@+id/group_name" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textDirection="locale" + android:ellipsize="end" + style="@style/TextAppearance.NotificationImportanceChannelGroup"/> + <TextView + android:id="@+id/pkg_name" + android:layout_width="match_parent" + android:layout_height="wrap_content" + style="@style/TextAppearance.NotificationImportanceApp" + android:ellipsize="end" + android:textDirection="locale" + android:maxLines="1"/> + <TextView + android:id="@+id/delegate_name" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + style="@style/TextAppearance.NotificationImportanceHeader" + android:layout_marginStart="2dp" + android:layout_marginEnd="2dp" + android:ellipsize="end" + android:textDirection="locale" + android:text="@string/notification_delegate_header" + android:maxLines="1" /> + + </LinearLayout> + + <!-- end aligned fields --> + <!-- Optional link to app. Only appears if the channel is not disabled and the app +asked for it --> + <ImageButton + android:id="@+id/app_settings" + android:layout_width="@dimen/notification_importance_toggle_size" + android:layout_height="@dimen/notification_importance_toggle_size" + android:layout_centerVertical="true" + android:visibility="gone" + android:background="@drawable/ripple_drawable" + android:contentDescription="@string/notification_app_settings" + android:src="@drawable/ic_info" + android:layout_toStartOf="@id/info" + android:tint="@androidprv:color/materialColorPrimary"/> + <ImageButton + android:id="@+id/info" + android:layout_width="@dimen/notification_importance_toggle_size" + android:layout_height="@dimen/notification_importance_toggle_size" + android:layout_centerVertical="true" + android:contentDescription="@string/notification_more_settings" + android:background="@drawable/ripple_drawable_20dp" + android:src="@drawable/ic_settings" + android:tint="@androidprv:color/materialColorPrimary" + android:layout_alignParentEnd="true" /> + + </LinearLayout> + + <LinearLayout + android:id="@+id/inline_controls" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingEnd="@dimen/notification_shade_content_margin_horizontal" + android:layout_marginTop="@dimen/notification_guts_option_vertical_padding" + android:clipChildren="false" + android:clipToPadding="false" + android:orientation="vertical"> + + <!-- Non configurable app/channel text. appears instead of @+id/interruptiveness_settings--> + <TextView + android:id="@+id/non_configurable_text" + android:text="@string/notification_unblockable_desc" + android:visibility="gone" + android:layout_width="match_parent" + android:layout_height="wrap_content" + style="@*android:style/TextAppearance.DeviceDefault.Notification" /> + + <!-- Non configurable app/channel text. appears instead of @+id/interruptiveness_settings--> + <TextView + android:id="@+id/non_configurable_call_text" + android:text="@string/notification_unblockable_call_desc" + android:visibility="gone" + android:layout_width="match_parent" + android:layout_height="wrap_content" + style="@*android:style/TextAppearance.DeviceDefault.Notification" /> + + <!-- Non configurable multichannel text. appears instead of @+id/interruptiveness_settings--> + <TextView + android:id="@+id/non_configurable_multichannel_text" + android:text="@string/notification_multichannel_desc" + android:visibility="gone" + android:layout_width="match_parent" + android:layout_height="wrap_content" + style="@*android:style/TextAppearance.DeviceDefault.Notification" /> + + <LinearLayout + android:id="@+id/interruptiveness_settings" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="center" + android:orientation="vertical"> + <com.android.systemui.statusbar.notification.row.ButtonLinearLayout + android:id="@+id/automatic" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="@dimen/notification_importance_button_separation" + android:padding="@dimen/notification_importance_button_padding" + android:clickable="true" + android:focusable="true" + android:background="@drawable/notification_guts_priority_button_bg" + android:orientation="vertical" + android:visibility="gone"> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:gravity="center" + > + <ImageView + android:id="@+id/automatic_icon" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:src="@drawable/ic_notifications_automatic" + android:background="@android:color/transparent" + android:tint="@color/notification_guts_priority_contents" + android:clickable="false" + android:focusable="false"/> + <TextView + android:id="@+id/automatic_label" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/notification_importance_drawable_padding" + android:layout_weight="1" + android:ellipsize="end" + android:maxLines="1" + android:clickable="false" + android:focusable="false" + android:textAppearance="@style/TextAppearance.NotificationImportanceButton" + android:text="@string/notification_automatic_title"/> + </LinearLayout> + <TextView + android:id="@+id/automatic_summary" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/notification_importance_button_description_top_margin" + android:visibility="gone" + android:text="@string/notification_channel_summary_automatic" + android:clickable="false" + android:focusable="false" + android:ellipsize="end" + android:maxLines="2" + android:textAppearance="@style/TextAppearance.NotificationImportanceDetail"/> + </com.android.systemui.statusbar.notification.row.ButtonLinearLayout> + + <com.android.systemui.statusbar.notification.row.ButtonLinearLayout + android:id="@+id/alert" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="@dimen/notification_importance_button_padding" + android:clickable="true" + android:focusable="true" + android:background="@drawable/notification_guts_priority_button_bg" + android:orientation="vertical"> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:gravity="center" + > + <ImageView + android:id="@+id/alert_icon" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:src="@drawable/ic_notifications_alert" + android:background="@android:color/transparent" + android:tint="@color/notification_guts_priority_contents" + android:clickable="false" + android:focusable="false"/> + <TextView + android:id="@+id/alert_label" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/notification_importance_drawable_padding" + android:layout_weight="1" + android:ellipsize="end" + android:maxLines="1" + android:clickable="false" + android:focusable="false" + android:textAppearance="@style/TextAppearance.NotificationImportanceButton" + android:text="@string/notification_alert_title"/> + </LinearLayout> + <TextView + android:id="@+id/alert_summary" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/notification_importance_button_description_top_margin" + android:visibility="gone" + android:text="@string/notification_channel_summary_default" + android:clickable="false" + android:focusable="false" + android:ellipsize="end" + android:maxLines="2" + android:textAppearance="@style/TextAppearance.NotificationImportanceDetail"/> + </com.android.systemui.statusbar.notification.row.ButtonLinearLayout> + + <com.android.systemui.statusbar.notification.row.ButtonLinearLayout + android:id="@+id/silence" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/notification_importance_button_separation" + android:padding="@dimen/notification_importance_button_padding" + android:clickable="true" + android:focusable="true" + android:background="@drawable/notification_guts_priority_button_bg" + android:orientation="vertical"> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:gravity="center" + > + <ImageView + android:id="@+id/silence_icon" + android:src="@drawable/ic_notifications_silence" + android:background="@android:color/transparent" + android:tint="@color/notification_guts_priority_contents" + android:layout_gravity="center" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:clickable="false" + android:focusable="false"/> + <TextView + android:id="@+id/silence_label" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:ellipsize="end" + android:maxLines="1" + android:clickable="false" + android:focusable="false" + android:layout_toEndOf="@id/silence_icon" + android:layout_marginStart="@dimen/notification_importance_drawable_padding" + android:textAppearance="@style/TextAppearance.NotificationImportanceButton" + android:text="@string/notification_silence_title"/> + </LinearLayout> + <TextView + android:id="@+id/silence_summary" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/notification_importance_button_description_top_margin" + android:visibility="gone" + android:text="@string/notification_channel_summary_low" + android:clickable="false" + android:focusable="false" + android:ellipsize="end" + android:maxLines="2" + android:textAppearance="@style/TextAppearance.NotificationImportanceDetail"/> + </com.android.systemui.statusbar.notification.row.ButtonLinearLayout> + + </LinearLayout> + + <RelativeLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:minHeight="60dp" + android:gravity="center_vertical" + android:paddingStart="4dp" + android:paddingEnd="4dp" + > + <TextView + android:id="@+id/promoted_demote" + android:text="@string/notification_inline_disable_promotion" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentStart="true" + android:gravity="start|center_vertical" + android:minWidth="@dimen/notification_importance_toggle_size" + android:minHeight="@dimen/notification_importance_toggle_size" + android:maxWidth="200dp" + style="@style/TextAppearance.NotificationInfo.Button"/> + <TextView + android:id="@+id/promoted_dismiss" + android:text="@string/notification_inline_dismiss" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentEnd="true" + android:gravity="end|center_vertical" + android:minWidth="@dimen/notification_importance_toggle_size" + android:minHeight="@dimen/notification_importance_toggle_size" + android:maxWidth="125dp" + style="@style/TextAppearance.NotificationInfo.Button"/> + </RelativeLayout> + + <RelativeLayout + android:id="@+id/bottom_buttons" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:minHeight="60dp" + android:gravity="center_vertical" + android:paddingStart="4dp" + android:paddingEnd="4dp" + > + <TextView + android:id="@+id/turn_off_notifications" + android:text="@string/inline_turn_off_notifications" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentStart="true" + android:gravity="start|center_vertical" + android:minWidth="@dimen/notification_importance_toggle_size" + android:minHeight="@dimen/notification_importance_toggle_size" + android:maxWidth="200dp" + style="@style/TextAppearance.NotificationInfo.Button"/> + <TextView + android:id="@+id/done" + android:text="@string/inline_ok_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentEnd="true" + android:gravity="end|center_vertical" + android:minWidth="@dimen/notification_importance_toggle_size" + android:minHeight="@dimen/notification_importance_toggle_size" + android:maxWidth="125dp" + style="@style/TextAppearance.NotificationInfo.Button"/> + </RelativeLayout> + </LinearLayout> +</com.android.systemui.statusbar.notification.row.PromotedNotificationInfo> diff --git a/packages/SystemUI/res/layout/volume_dialog_slider.xml b/packages/SystemUI/res/layout/volume_dialog_slider.xml index c5f468e731f5..2628f4991b49 100644 --- a/packages/SystemUI/res/layout/volume_dialog_slider.xml +++ b/packages/SystemUI/res/layout/volume_dialog_slider.xml @@ -18,14 +18,10 @@ android:layout_height="match_parent" android:maxHeight="@dimen/volume_dialog_slider_height"> - <com.google.android.material.slider.Slider + <androidx.compose.ui.platform.ComposeView android:id="@+id/volume_dialog_slider" - style="@style/SystemUI.Material3.Slider.Volume" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_gravity="center" - android:layout_marginTop="-20dp" - android:layout_marginBottom="-20dp" - android:orientation="vertical" - android:theme="@style/Theme.Material3.DayNight" /> + android:orientation="vertical" /> </FrameLayout>
\ 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 5e7d9c4a041b..91d92a21ddf1 100644 --- a/packages/SystemUI/res/values-af/strings.xml +++ b/packages/SystemUI/res/values-af/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"Dateer tans op"</string> <string name="status_bar_work" msgid="5238641949837091056">"Werkprofiel"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Vliegtuigmodus"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"Jy sal nie jou volgende wekker <xliff:g id="WHEN">%1$s</xliff:g> hoor nie"</string> <string name="alarm_template" msgid="2234991538018805736">"om <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"op <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satelliet, goeie toestand"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satelliet, verbinding is beskikbaar"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Satelliet-SOS"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"Net noodoproepe of SOS"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"geen sein nie"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"een staaf"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"Terug"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"Kennisgewings"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"Kortpadsleutels"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"Wissel sleutelborduitleg"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"of"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"Maak soeknavraag skoon"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"Kortpadsleutels"</string> @@ -1432,6 +1432,8 @@ <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_split_screen" msgid="1159669813444812244">"Verdeelde skerm"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <string name="shortcut_helper_category_input" msgid="8674018654124839566">"Invoer"</string> <string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Appkortpaaie"</string> <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Huidige app"</string> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"Pasmaak kortpaaie"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Verwyder kortpad?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"Stel terug na verstek?"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Druk sleutel om kortpad toe te wys"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"Druk die handelingsleutel en een of meer ander sleutels saam om hierdie kortpad te skep"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Dit sal jou gepasmaakte kortpad permanent uitvee."</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"Dit sal al jou gepasmaakte kortpaaie permanent uitvee."</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Soekkortpaaie"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"Ja, stel terug"</string> <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_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Sleutelkombinasie is reeds in gebruik. Probeer ’n ander sleutel."</string> + <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="3325858369539848162">"Sleutelkombinasie is reeds in gebruik. Probeer ’n ander kombinasie."</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="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"Voeg kortpad by"</string> @@ -1476,6 +1478,7 @@ <string name="touchpad_tutorial_back_gesture_button" msgid="3104716365403620315">"Gaan terug"</string> <string name="touchpad_tutorial_home_gesture_button" msgid="8023973153559885624">"Gaan na tuisskerm"</string> <string name="touchpad_tutorial_recent_apps_gesture_button" msgid="8919227647650347359">"Bekyk onlangse apps"</string> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"Wissel apps"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"Klaar"</string> <string name="gesture_error_title" msgid="469064941635578511">"Probeer weer!"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"Gaan terug"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"Knap gedaan!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"Jy het die Bekyk Onlangse Apps-gebaar voltooi."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"Swiep op en hou met drie vingers op jou raakpaneel om onlangse apps te bekyk"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"Wissel apps"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"Uitstekende werk!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"Jy het die “wissel tussen apps”-gebaar voltooi."</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"Bekyk alle apps"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"Druk die handelingsleutel op jou sleutelbord"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"Welgedaan!"</string> diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml index d969525b7c4a..6e079a2a647f 100644 --- a/packages/SystemUI/res/values-am/strings.xml +++ b/packages/SystemUI/res/values-am/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"በማዘመን ላይ"</string> <string name="status_bar_work" msgid="5238641949837091056">"የስራ መገለጫ"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"የአውሮፕላን ሁነታ"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"የእርስዎን ቀጣይ ማንቂያ <xliff:g id="WHEN">%1$s</xliff:g> አይሰሙም"</string> <string name="alarm_template" msgid="2234991538018805736">"በ<xliff:g id="WHEN">%1$s</xliff:g> ላይ"</string> <string name="alarm_template_far" msgid="3561752195856839456">"በ<xliff:g id="WHEN">%1$s</xliff:g> ላይ"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"ሳተላይት፣ ጥሩ ግንኙነት"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"ሳተላይት፣ ግንኙነት አለ"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"ሳተላይት ኤስኦኤስ"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"የአደጋ ጥሪዎች ወይም ኤስኦኤስ ብቻ"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>፣ <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>።"</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"ምንም ምልክት የለም"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"አንድ አሞሌ"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"ተመለስ"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"ማሳወቂያዎች"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"የቁልፍ ሰሌዳ አቋራጮች"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"የቁልፍ ሰሌዳ ገጽታ ለውጥ"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"ወይም"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"የፍለጋ መጠይቅን አጽዳ"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"የቁልፍ ሰሌዳ አቋራጮች"</string> @@ -1432,6 +1432,8 @@ <string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"የሥርዓት መተግበሪያዎች"</string> <string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"ብዙ ተግባራትን በተመሳሳይ ጊዜ ማከናወን"</string> <string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"የተከፈለ ማያ ገፅ"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <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> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"አቋራጮችን ያብጁ"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"አቋራጭ ይወገድ?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"ወደ ነባሪ ዳግም ይጀመር?"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"አቋራጭ ለመመደብ ቁልፍ ይጫኑ"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"ይህን አቋራጭ ለመፍጠር የእርምጃ ቁልፉን እና ላይ አንድ ወይም ከዚያ በላይ ሌሎቹ ቁልፎችን አንድ ላይ ይጫኑ"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"ይህ ብጁ አቋራጭዎን በቋሚነት ይሰርዛል።"</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"ይህ ሁሉንም ብጁ አቋራጮችዎን በቋሚነት ይሰርዛል።"</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"የፍለጋ አቋራጮች"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"አዎ፣ ዳግም አስጀምር"</string> <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_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"የቁልፍ ጥምረት አስቀድሞ በሥራ ላይ ነው። ሌላ ቁልፍ ይሞክሩ።"</string> + <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="3325858369539848162">"የቁልፍ ጥምረት ቀድሞውኑ ጥቅም ላይ ውሏል። ሌላ ጥምረት ይሞክሩ።"</string> <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"አቋራጩ ሊቀናበር አይችልም።"</string> <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string> <string name="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"አቋራጭ አክል"</string> @@ -1476,6 +1478,7 @@ <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> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"መተግበሪያዎችን ይቀያይሩ"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"ተከናውኗል"</string> <string name="gesture_error_title" msgid="469064941635578511">"እንደገና ይሞክሩ!"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"ወደኋላ ተመለስ"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"ጥሩ ሠርተዋል!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"የቅርብ ጊዜ መተግበሪያዎች አሳይ ምልክትን አጠናቅቀዋል።"</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"የቅርብ ጊዜ መተግበሪያዎችን ለማየት የመዳሰሻ ሰሌዳዎ ላይ ሦስት ጣቶችን በመጠቀም ወደላይ ያንሸራትቱ እና ይያዙ"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"መተግበሪያዎችን ይቀያይሩ"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"ጥሩ ሠርተዋል!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"የመተግበሪያ ምልክቶችን ይቀይሩ የሚለወን አጠናቅቀዋል።"</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"ሁሉንም መተግበሪያዎች ይመልከቱ"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"በቁልፍ ሰሌዳዎ ላይ ያለውን የተግባር ቁልፍ ይጫኑ"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"ጥሩ ሠርተዋል!"</string> diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml index d46df1d8b348..7896cdbd68f8 100644 --- a/packages/SystemUI/res/values-ar/strings.xml +++ b/packages/SystemUI/res/values-ar/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"جارٍ تعديل الحالة"</string> <string name="status_bar_work" msgid="5238641949837091056">"ملف العمل"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"وضع الطيران"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"لن تسمع المنبّه القادم في <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template" msgid="2234991538018805736">"في <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"يوم <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"قمر صناعي، الاتصال جيد"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"قمر صناعي، الاتصال متوفّر"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"اتصالات الطوارئ بالقمر الصناعي"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"مكالمات الطوارئ أو اتصالات الطوارئ فقط"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"\"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>\"، <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>"</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"ما مِن أشرطة إشارة"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"شريط إشارة واحد"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"رجوع"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"الإشعارات"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"اختصارات لوحة المفاتيح"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"تبديل تنسيق لوحة المفاتيح"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"أو"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"محو طلب البحث"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"اختصارات لوحة المفاتيح"</string> @@ -1432,16 +1432,17 @@ <string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"تطبيقات النظام"</string> <string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"تعدُّد المهام"</string> <string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"تقسيم الشاشة"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <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> - <!-- no translation found for shortcut_helper_customize_mode_title (8327297960035006036) --> - <skip /> + <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"تخصيص الاختصارات"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"هل تريد إزالة هذا الاختصار؟"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"يُرجى تأكيد إعادة الضبط على الإعدادات التلقائية"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"اضغط على مفتاح لتخصيص الاختصار"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"لإنشاء هذا الاختصار، يُرجى الضغط على مفتاح الإجراء ومفتاح آخر أو أكثر معًا"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"سيؤدي هذا الإجراء إلى حذف الاختصار المخصّص نهائيًا."</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"سيؤدي هذا الإجراء إلى حذف جميع الاختصارات المخصّصة نهائيًا."</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"البحث في الاختصارات"</string> @@ -1463,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"نعم، أريد إعادة الضبط"</string> <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_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"يتم حاليًا استخدام مجموعة المفاتيح هذه. يُرجى تجربة مفتاح آخر."</string> + <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="3325858369539848162">"يتم حاليًا استخدام مجموعة المفاتيح هذه. يُرجى تجربة مجموعة أخرى من المفاتيح."</string> <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"تعذَّر ضبط الاختصار."</string> <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string> <string name="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"إضافة اختصار"</string> @@ -1477,6 +1478,7 @@ <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> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"التبديل بين التطبيقات"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"تم"</string> <string name="gesture_error_title" msgid="469064941635578511">"يُرجى إعادة المحاولة"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"رجوع"</string> @@ -1494,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"أحسنت."</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"لقد أكملْت التدريب على إيماءة عرض التطبيقات المستخدَمة مؤخرًا."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"لعرض التطبيقات المستخدَمة مؤخرًا، يُرجى التمرير سريعًا للأعلى مع الاستمرار باستخدام ثلاثة أصابع على لوحة اللمس"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"التبديل بين التطبيقات"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"أحسنت صنعًا."</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"أكملت التدريب على إيماءة التبديل بين التطبيقات."</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"عرض جميع التطبيقات"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"اضغط على مفتاح الإجراء في لوحة المفاتيح"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"أحسنت!"</string> diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml index ff5e2106a8d2..7c99df9a8c78 100644 --- a/packages/SystemUI/res/values-as/strings.xml +++ b/packages/SystemUI/res/values-as/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"আপডে’ট কৰি থকা হৈছে"</string> <string name="status_bar_work" msgid="5238641949837091056">"কৰ্মস্থানৰ প্ৰ\'ফাইল"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"এয়াৰপ্লে’ন ম’ড"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"আপুনি আপোনাৰ পিছৰটো এলাৰ্ম <xliff:g id="WHEN">%1$s</xliff:g> বজাত শুনা নাপাব"</string> <string name="alarm_template" msgid="2234991538018805736">"<xliff:g id="WHEN">%1$s</xliff:g> বজাত"</string> <string name="alarm_template_far" msgid="3561752195856839456">"<xliff:g id="WHEN">%1$s</xliff:g> বজাত"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"উপগ্ৰহ, ভাল সংযোগ"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"উপগ্ৰহ, সংযোগ উপলব্ধ"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"উপগ্ৰহ SOS"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"কেৱল জৰুৰীকালীন কল বা SOS"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"কোনো ছিগনেল নাই"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"এডাল দণ্ড"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"উভতি যাওক"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"জাননীসমূহ"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"কীব\'ৰ্ড শ্বৰ্টকাটসমূহ"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"কীব\'ৰ্ডৰ সজ্জা সলনি কৰক"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"অথবা"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"সন্ধান কৰা প্ৰশ্ন মচক"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"কীব’ৰ্ডৰ শ্বৰ্টকাট"</string> @@ -1432,6 +1432,8 @@ <string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"ছিষ্টেম এপ্"</string> <string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"মাল্টিটাস্কিং"</string> <string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"বিভাজিত স্ক্ৰীন"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <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> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"শ্বৰ্টকাট কাষ্টমাইজ কৰক"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"শ্বৰ্টকাট আঁতৰাবনে?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"ডিফ\'ল্ট হিচাপে পুনৰ ৰিছেট কৰিবনে?"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"শ্বৰ্টকাটৰ ভূমিকা অৰ্পণ কৰিবলৈ কী টিপক"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"এই শ্বৰ্টকাটটো সৃষ্টি কৰিবলৈ, কাৰ্য কী আৰু এক বা একাধিক কী একেলগে টিপক"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"এইটোৱে আপোনাৰ কাষ্টম শ্বৰ্টকাট মচিব।"</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"এইটোৱে আপোনাৰ আটাইবোৰ কাষ্টম শ্বৰ্টকাট স্থায়ীভাৱে মচিব।"</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"সন্ধানৰ শ্বৰ্টকাট"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"হয়, ৰিছেট কৰক"</string> <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_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"কীৰ মিশ্ৰণ ইতিমধ্যে ব্যৱহাৰ হৈ আছে। অন্য এটা কী ব্যৱহাৰ কৰি চাওক।"</string> + <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="3325858369539848162">"কীৰ মিশ্ৰণ ইতিমধ্যে ব্যৱহাৰ হৈ আছে। অন্য এটা মিশ্ৰণ ব্যৱহাৰ কৰি চাওক।"</string> <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"শ্বৰ্টকাট ছেট কৰিব নোৱাৰি।"</string> <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string> <string name="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"শ্বৰ্টকাট যোগ দিয়ক"</string> @@ -1476,6 +1478,7 @@ <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> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"এপ্সমূহ সলনি কৰক"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"হ’ল"</string> <string name="gesture_error_title" msgid="469064941635578511">"পুনৰ চেষ্টা কৰক!"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"উভতি যাওক"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"বঢ়িয়া!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"আপুনি শেহতীয়া এপ্ চোৱাৰ নিৰ্দেশনাটো সম্পূৰ্ণ কৰিছে।"</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"শেহতীয়া এপ্সমূহ চাবলৈ, আপোনাৰ টাচ্চপেডত তিনিটা আঙুলি ব্যৱহাৰ কৰি ওপৰলৈ ছোৱাইপ কৰি ধৰি ৰাখক"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"এপ্সমূহ সলনি কৰক"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"বঢ়িয়া!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"আপুনি এপ্ সলনি কৰাৰ নিৰ্দেশটো সম্পূৰ্ণ কৰিলে।"</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"আটাইবোৰ এপ্ চাওক"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"আপোনাৰ কীব’ৰ্ডৰ কাৰ্য কীটোত টিপক"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"বঢ়িয়া!"</string> diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml index dbeacf0bf277..64b2726f26d8 100644 --- a/packages/SystemUI/res/values-az/strings.xml +++ b/packages/SystemUI/res/values-az/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"Güncəllənir"</string> <string name="status_bar_work" msgid="5238641949837091056">"İş profili"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Təyyarə rejimi"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"<xliff:g id="WHEN">%1$s</xliff:g> zaman növbəti xəbərdarlığınızı eşitməyəcəksiniz"</string> <string name="alarm_template" msgid="2234991538018805736">"<xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"<xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Peyk, bağlantı yaxşıdır"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Peyk, bağlantı var"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Təcili peyk bağlantısı"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"Təcili zəng və ya sadəcə SOS"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"siqnal yoxdur"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"bir zolaq"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"Geri"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"Bildirişlər"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"Klaviatura qısa yolları"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"Klaviatura düzümünü dəyişin"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"və ya"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"Axtarış sorğusunu silin"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"Klaviatura qısayolları"</string> @@ -1432,6 +1432,8 @@ <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_split_screen" msgid="1159669813444812244">"Bölünmüş ekran"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <string name="shortcut_helper_category_input" msgid="8674018654124839566">"Daxiletmə"</string> <string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Tətbiq qısayolları"</string> <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Cari tətbiq"</string> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"Qısayolları fərdiləşdirin"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Qısayol silinsin?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"Defolt vəziyyətə qaytarılsın?"</string> - <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_add_shortcut_description" msgid="7636040209946696120">"Bu qısayolu yaratmaq üçün Fəaliyyət düyməsini, habelə bir və ya bir neçə digər düyməni birlikdə 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> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"Bu, bütün fərdi qısayollarınızı həmişəlik siləcək."</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Axtarış qısayolları"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"Bəli, sıfırlayın"</string> <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_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_key_combination_in_use_error_message" msgid="3325858369539848162">"Düymə kombinasiyası artıq istifadə olunur. Başqa kombinasiyanı 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="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"Qısayol əlavə edin"</string> @@ -1476,6 +1478,7 @@ <string name="touchpad_tutorial_back_gesture_button" msgid="3104716365403620315">"Geri qayıdın"</string> <string name="touchpad_tutorial_home_gesture_button" msgid="8023973153559885624">"Əsas səhifəyə keçin"</string> <string name="touchpad_tutorial_recent_apps_gesture_button" msgid="8919227647650347359">"Son tətbiqlərə baxın"</string> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"Başqa tətbiqə keçin"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"Hazırdır"</string> <string name="gesture_error_title" msgid="469064941635578511">"Yenidən cəhd edin!"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"Geri qayıdın"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"Əla!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"Son tətbiqlərə baxmaq jestini tamamladınız."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"Ən son tətbiqlərə baxmaq üçün taçpeddə üç barmağınızla yuxarı çəkin və saxlayın"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"Başqa tətbiqə keçin"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"Əla!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"Tətbiqlərarası keçid jestini tamamladınız."</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"Bütün tətbiqlərə baxın"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"Klaviaturada fəaliyyət açarına basın"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"Əla!"</string> diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml index ca01a3d89ebe..31566929dae0 100644 --- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml +++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"Ažurira se"</string> <string name="status_bar_work" msgid="5238641949837091056">"Poslovni profil"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Režim rada u avionu"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"Nećete čuti sledeći alarm u <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template" msgid="2234991538018805736">"u <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"u <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satelit, veza je dobra"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satelit, veza je dostupna"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Hitna pomoć preko satelita"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"Samo hitni pozivi ili hitna pomoć"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"nema signala"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"jedna crta"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"Nazad"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"Obaveštenja"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"Tasterske prečice"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"Promeni raspored tastature"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"ili"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"Obriši upit za pretragu"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"Tasterske prečice"</string> @@ -1432,6 +1432,8 @@ <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_split_screen" msgid="1159669813444812244">"Podeljeni ekran"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <string name="shortcut_helper_category_input" msgid="8674018654124839566">"Unos"</string> <string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Prečice za aplikacije"</string> <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Aktuelna aplikacija"</string> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"Prilagodite prečice"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Želite da uklonite prečicu?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"Želite da resetujete na podrazumevano?"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Pritisnite taster da biste dodelili prečicu"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"Da biste napravili ovu prečicu, pritisnite zajedno taster radnji i jedan ili više drugih tastera"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Ovim ćete trajno izbrisati prilagođenu prečicu."</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"Time ćete trajno izbrisati sve prilagođene prečice."</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Pretražite prečice"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"Da, resetuj"</string> <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_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Kombinacija tastera se već koristi. Probajte sa drugim tasterom."</string> + <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="3325858369539848162">"Kombinacija tastera se već koristi. Probajte sa drugom kombinacijom."</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="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"Dodajte prečicu"</string> @@ -1476,6 +1478,7 @@ <string name="touchpad_tutorial_back_gesture_button" msgid="3104716365403620315">"Nazad"</string> <string name="touchpad_tutorial_home_gesture_button" msgid="8023973153559885624">"Idi na početni ekran"</string> <string name="touchpad_tutorial_recent_apps_gesture_button" msgid="8919227647650347359">"Prikaži nedavno korišćene aplikacije"</string> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"Pređi na drugu aplikaciju"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"Gotovo"</string> <string name="gesture_error_title" msgid="469064941635578511">"Probajte ponovo."</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"Nazad"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"Odlično!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"Dovršili ste pokret za prikazivanje nedavno korišćenih aplikacija."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"Da biste pregledali nedavne aplikacije, prevucite nagore i zadržite sa tri prsta na tačpedu"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"Pređi na drugu aplikaciju"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"Odlično!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"Dovršili ste pokret za promenu aplikacija."</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"Prikaži sve aplikacije"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"Pritisnite taster radnji na tastaturi"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"Odlično!"</string> diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml index 40d78c20859b..5e339a4ea07e 100644 --- a/packages/SystemUI/res/values-be/strings.xml +++ b/packages/SystemUI/res/values-be/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"Абнаўленне…"</string> <string name="status_bar_work" msgid="5238641949837091056">"Працоўны профіль"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Рэжым палёту"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"Вы не пачуеце наступны будзільнік <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template" msgid="2234991538018805736">"у <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"у <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Спадарожнікавая сувязь, добрае падключэнне"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Спадарожнікавая сувязь, падключэнне даступнае"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Экстраннае спадарожнікавае падключэнне"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"Экстранныя выклікі або толькі экстраннае спадарожнікавае падключэнне"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"няма сігналу"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"адзiн слупок"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"Назад"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"Апавяшчэнні"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"Спалучэнні клавіш"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"Пераключыць раскладку клавіятуры"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"або"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"Ачысціць пошукавы запыт"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"Спалучэнні клавіш"</string> @@ -1432,6 +1432,8 @@ <string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"Сістэмныя праграмы"</string> <string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Шматзадачнасць"</string> <string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Падзелены экран"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <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> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"Наладзіць спалучэнні клавіш"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Выдаліць спалучэнне клавіш?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"Скінуць налады да стандартных?"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Націсніце клавішу, каб прызначыць спалучэнне клавіш"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"Каб стварыць гэта спалучэнне клавіш, націсніце клавішу дзеяння разам з яшчэ адной ці некалькімі клавішамі"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Гэта дзеянне назаўсёды выдаліць прызначанае вамі спалучэнне клавіш."</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"Усе карыстальніцкія спалучэнні клавіш будуць назаўсёды выдалены."</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Пошук спалучэнняў клавіш"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"Так, скінуць"</string> <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_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Гэта спалучэнне клавіш ужо выкарыстоўваецца. Паспрабуйце іншую клавішу."</string> + <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="3325858369539848162">"Гэта спалучэнне клавіш ужо выкарыстоўваецца. Паспрабуйце іншае спалучэнне."</string> <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Не ўдаецца наладзіць спалучэнне клавіш."</string> <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string> <string name="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"Дадаць ярлык"</string> @@ -1476,6 +1478,7 @@ <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> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"Пераключэнне праграм"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"Гатова"</string> <string name="gesture_error_title" msgid="469064941635578511">"Паспрабуйце яшчэ раз!"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"Назад"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"Выдатная праца!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"Вы скончылі вывучэнне жэсту для прагляду нядаўніх праграм."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"Каб праглядзець нядаўнія праграмы, правядзіце трыма пальцамі ўверх па сэнсарным экране і затрымайце пальцы"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"Пераключэнне праграм"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"Выдатная праца!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"Вы навучыліся рабіць жэст пераключэння праграм."</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"Глядзець усе праграмы"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"Націсніце клавішу дзеяння на клавіятуры"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"Выдатна!"</string> diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml index 95037ab4d29e..245711f831b5 100644 --- a/packages/SystemUI/res/values-bg/strings.xml +++ b/packages/SystemUI/res/values-bg/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"Актуализира се"</string> <string name="status_bar_work" msgid="5238641949837091056">"Потребителски профил в Work"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Самолетен режим"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"Няма да чуете следващия си будилник в <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template" msgid="2234991538018805736">"в <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"в/ъв <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Сателит, добра връзка"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Сателит, налице е връзка"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS чрез сателит"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"Само спешни обаждания или SOS"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"няма сигнал"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"една чертичка"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"Назад"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"Известия"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"Клавишни комбинации"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"Превкл. на клавиат. подредба"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"или"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"Изчистване на заявката за търсене"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"Клавишни комбинации"</string> @@ -1432,6 +1432,8 @@ <string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"Системни приложения"</string> <string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Няколко задачи едновременно"</string> <string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Разделен екран"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <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> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"Персонализиране на преките пътища"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Да се премахне ли клавишната комбинация?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"Да се възстановят ли стандартните настройки?"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Натиснете клавиш, за да зададете клавишна комбинация"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"За да създадете тази клавишна комбинация, натиснете клавиша за действия заедно с един или повече други клавиши"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Това ще изтрие персонализираната клавишна комбинация за постоянно."</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"Това ще изтрие всичките ви персонализирани преки пътища за постоянно."</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Търсете клавишни комбинации"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"Да, нулиране"</string> <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_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Клавишната комбинация вече се използва. Опитайте с друг клавиш."</string> + <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="3325858369539848162">"Клавишната комбинация вече се използва. Опитайте с друга."</string> <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Прекият път не може да се зададе."</string> <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string> <string name="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"Добавяне на пряк път"</string> @@ -1476,6 +1478,7 @@ <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> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"Превключване на приложенията"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"Готово"</string> <string name="gesture_error_title" msgid="469064941635578511">"Опитайте отново!"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"Назад"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"Отлично!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"Изпълнихте жеста за преглед на скорошните приложения."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"За да прегледате скорошните приложения, плъзнете три пръста нагоре по сензорния панел и задръжте"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"Превключване на приложенията"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"Отлично!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"Изпълнихте жеста за превключване между приложения."</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"Преглед на всички приложения"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"Натиснете клавиша за действия на клавиатурата си"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"Браво!"</string> diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml index 73eddf28bbed..42a4d540c0c8 100644 --- a/packages/SystemUI/res/values-bn/strings.xml +++ b/packages/SystemUI/res/values-bn/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"আপডেট করা হচ্ছে"</string> <string name="status_bar_work" msgid="5238641949837091056">"কাজের প্রোফাইল"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"বিমান মোড"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"আপনি আপনার পরবর্তী <xliff:g id="WHEN">%1$s</xliff:g> অ্যালার্ম শুনতে পাবেন না"</string> <string name="alarm_template" msgid="2234991538018805736">"<xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"<xliff:g id="WHEN">%1$s</xliff:g> -তে"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"স্যাটেলাইট, ভালো কানেকশন"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"স্যাটেলাইট, কানেকশন উপলভ্য আছে"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"স্যাটেলাইট SOS"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"শুধুমাত্র জরুরি কল বা SOS"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>।"</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"কোনও সিগন্যাল নেই"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"একটি বার"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"পিছনে"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"বিজ্ঞপ্তি"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"কীবোর্ড শর্টকাট"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"কীবোর্ড লে-আউট পাল্টান"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"অথবা"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"সার্চ কোয়েরি মুছুন"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"কীবোর্ড শর্টকাট"</string> @@ -1432,6 +1432,8 @@ <string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"সিস্টেম অ্যাপ"</string> <string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"মাল্টিটাস্কিং"</string> <string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"স্প্লিট স্ক্রিন"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <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> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"শর্টকাট কাস্টমাইজ করুন"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"শর্টকাট সরাবেন?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"ডিফল্ট শর্টকার্ট আবার রিসেট করতে চান?"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"শর্টকাট অ্যাসাইন করতে কী প্রেস করুন"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"এই শর্টকার্ট তৈরি করতে, অ্যাকশন \'কী\' এবং এক বা আরও বেশি \'কী\' একসাথে প্রেস করুন"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"এটি আপনার কাস্টম শর্টকাট স্থায়ীভাবে মুছে ফেলবে।"</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"এটি আপনার সব কাস্টম শর্টকার্ট স্থায়ীভাবে মুছে দেবে।"</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"শর্টকাট সার্চ করুন"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"হ্যাঁ, রিসেট করতে চাই"</string> <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_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"কী কম্বিনেশন আগে থেকে ব্যবহার হচ্ছে। অন্য কী ব্যবহার করে দেখুন।"</string> + <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="3325858369539848162">"\'কী\' শর্টকার্ট আগে থেকে ব্যবহার হচ্ছে। অন্য শর্টকার্ট ব্যবহার করে দেখুন।"</string> <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"শর্টকাট সেট করা যায়নি।"</string> <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string> <string name="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"শর্টকাট যোগ করুন"</string> @@ -1476,6 +1478,7 @@ <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> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"অ্যাপ পরিবর্তন করুন"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"হয়ে গেছে"</string> <string name="gesture_error_title" msgid="469064941635578511">"আবার চেষ্টা করুন!"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"ফিরে যান"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"অসাধারণ!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"সম্প্রতি ব্যবহার করা হয়েছে এমন অ্যাপের জেসচার দেখা সম্পূর্ণ করেছেন।"</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"সাম্প্রতিক অ্যাপ দেখতে, নিজের টাচপ্যাডে তিনটি আঙুল ব্যবহার করে উপরের দিকে সোয়াইপ করে হোল্ড করুন"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"অ্যাপ পরিবর্তন করুন"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"অসাধারণ!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"আপনি অ্যাপ পরিবর্তন করার জেসচার সম্পূর্ণ করেছেন।"</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"সব অ্যাপ দেখুন"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"আপনার কীবোর্ডে অ্যাকশন কী প্রেস করুন"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"দারুণ!"</string> diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml index c16627f86820..69f163512987 100644 --- a/packages/SystemUI/res/values-bs/strings.xml +++ b/packages/SystemUI/res/values-bs/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"Ažuriranje"</string> <string name="status_bar_work" msgid="5238641949837091056">"Radni profil"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Način rada u avionu"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"Nećete čuti sljedeći alarm u <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template" msgid="2234991538018805736">"u <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"u <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satelit, dobra veza"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satelit, veza je dostupna"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Hitna pomoć putem satelita"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"Samo hitni pozivi ili pomoć"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"nema signala"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"jedna crtica"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"Nazad"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"Obavještenja"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"Prečice tastature"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"Zamijeni raspored tastature"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"ili"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"Brisanje upita za pretraživanje"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"Prečice na tastaturi"</string> @@ -1432,6 +1432,8 @@ <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_split_screen" msgid="1159669813444812244">"Podijeljeni ekran"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <string name="shortcut_helper_category_input" msgid="8674018654124839566">"Unos"</string> <string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Prečice aplikacije"</string> <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Trenutna aplikacija"</string> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"Prilagodite prečice"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Ukloniti prečicu?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"Vratiti na zadano?"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Pritisnite tipku da dodijelite prečicu"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"Da kreirate ovu prečicu, istovremeno pritisnite tipku radnji i jednu ili više tipki"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Ovo će trajno izbrisati prilagođenu prečicu."</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"Ovo će trajno izbrisati sve vaše prilagođene prečice."</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Pretražite prečice"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"Da, vrati na zadano"</string> <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_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_key_combination_in_use_error_message" msgid="3325858369539848162">"Ta se kombinacija tipki već koristi. Pokušajte s drugom kombinacijom."</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="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"Dodavanje prečice"</string> @@ -1476,6 +1478,7 @@ <string name="touchpad_tutorial_back_gesture_button" msgid="3104716365403620315">"Nazad"</string> <string name="touchpad_tutorial_home_gesture_button" msgid="8023973153559885624">"Odlazak na početni ekran"</string> <string name="touchpad_tutorial_recent_apps_gesture_button" msgid="8919227647650347359">"Prikaži nedavne aplikacije"</string> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"Promijenite aplikaciju"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"Gotovo"</string> <string name="gesture_error_title" msgid="469064941635578511">"Pokušajte ponovo!"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"Nazad"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"Sjajno!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"Izvršili ste pokret za prikaz nedavnih aplikacija."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"Da pogledate nedavne aplikacije, prevucite nagore i zadržite s tri prsta na dodirnoj podlozi"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"Promijenite aplikaciju"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"Sjajno!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"Izvršili ste pokret za promjenu aplikacije."</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"Pogledajte sve aplikacije"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"Pritisnite tipku radnji na tastaturi"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"Odlično!"</string> diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml index 43ba8fac3dad..d90c4fffd5aa 100644 --- a/packages/SystemUI/res/values-ca/strings.xml +++ b/packages/SystemUI/res/values-ca/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"S\'està actualitzant"</string> <string name="status_bar_work" msgid="5238641949837091056">"Perfil de treball"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Mode d\'avió"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"<xliff:g id="WHEN">%1$s</xliff:g> no sentiràs la pròxima alarma"</string> <string name="alarm_template" msgid="2234991538018805736">"Hora: <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"Dia: <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satèl·lit, bona connexió"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satèl·lit, connexió disponible"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS per satèl·lit"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"Només SOS o trucades d\'emergència"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"no hi ha senyal"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"una barra"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"Enrere"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"Notificacions"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"Tecles de drecera"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"Canvia disposició de teclat"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"o"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"Esborra la consulta de cerca"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"Tecles de drecera"</string> @@ -1432,6 +1432,8 @@ <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_split_screen" msgid="1159669813444812244">"Pantalla dividida"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <string name="shortcut_helper_category_input" msgid="8674018654124839566">"Entrada"</string> <string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Dreceres d\'aplicacions"</string> <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Aplicació actual"</string> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"Personalitza les dreceres"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Vols suprimir la drecera?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"Vols restablir els valors predeterminats?"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Prem la tecla per assignar la drecera"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"Per crear aquesta drecera, prem la tecla d\'acció i una o més tecles alhora"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Aquesta acció suprimirà la drecera personalitzada permanentment."</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"Aquesta acció suprimirà totes les dreceres personalitzades permanentment."</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Dreceres de cerca"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"Sí, restableix"</string> <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_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_key_combination_in_use_error_message" msgid="3325858369539848162">"La combinació de tecles ja s\'està utilitzant. Prova-ho amb una altra combinació."</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="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"Afegeix una drecera"</string> @@ -1476,6 +1478,7 @@ <string name="touchpad_tutorial_back_gesture_button" msgid="3104716365403620315">"Torna"</string> <string name="touchpad_tutorial_home_gesture_button" msgid="8023973153559885624">"Ves a la pantalla d\'inici"</string> <string name="touchpad_tutorial_recent_apps_gesture_button" msgid="8919227647650347359">"Mostra les aplicacions recents"</string> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"Canviar d\'aplicació"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"Fet"</string> <string name="gesture_error_title" msgid="469064941635578511">"Torna-ho a provar"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"Torna"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"Ben fet!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"Has completat el gest per veure les aplicacions recents."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"Per veure les aplicacions recents, llisca cap amunt amb tres dits i mantén-los premuts al ratolí tàctil"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"Canviar d\'aplicació"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"Ben fet!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"Has completat el gest per canviar d\'aplicació."</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"Mostra totes les aplicacions"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"Prem la tecla d\'acció al teclat"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"Enhorabona!"</string> diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml index 17945a3856d6..2cfc4dab8e48 100644 --- a/packages/SystemUI/res/values-cs/strings.xml +++ b/packages/SystemUI/res/values-cs/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"Probíhá aktualizace"</string> <string name="status_bar_work" msgid="5238641949837091056">"Pracovní profil"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Režim Letadlo"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"Svůj další budík <xliff:g id="WHEN">%1$s</xliff:g> neuslyšíte"</string> <string name="alarm_template" msgid="2234991538018805736">"v <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"<xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satelit, dobré připojení"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satelit, připojení je k dispozici"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS přes satelit"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"Pouze tísňové hovory nebo SOS"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"není signál"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"jedna čárka"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"Zpět"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"Oznámení"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"Klávesové zkratky"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"Přepnout rozložení klávesnice"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"nebo"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"Vymazat vyhledávaný dotaz"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"Klávesové zkratky"</string> @@ -1432,16 +1432,17 @@ <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_split_screen" msgid="1159669813444812244">"Rozdělená obrazovka"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <string name="shortcut_helper_category_input" msgid="8674018654124839566">"Vstup"</string> <string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Zkratky aplikací"</string> <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Aktuální aplikace"</string> <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Přístupnost"</string> <string name="shortcut_helper_title" msgid="8567500639300970049">"Klávesové zkratky"</string> - <!-- no translation found for shortcut_helper_customize_mode_title (8327297960035006036) --> - <skip /> + <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"Přizpůsobení zkratek"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Odstranit zkratku?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"Resetovat do výchozího nastavení?"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Nastavte zkratku stisknutím klávesy"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"K vytvoření této zkratky stiskněte současně akční klávesu a jednu nebo více dalších kláves"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Vlastní zkratka se trvale smaže."</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"Tím trvale odstraníte všechny své vlastní zkratky."</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Vyhledat zkratky"</string> @@ -1463,13 +1464,11 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"Ano, resetovat"</string> <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_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_key_combination_in_use_error_message" msgid="3325858369539848162">"Kombinace kláves se už používá. Zkuste jinou kombinaci."</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> - <!-- no translation found for shortcut_helper_add_shortcut_button_label (7655779534665954910) --> - <skip /> - <!-- no translation found for shortcut_helper_delete_shortcut_button_label (3148773472696137052) --> - <skip /> + <string name="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"Přidat zkratku"</string> + <string name="shortcut_helper_delete_shortcut_button_label" msgid="3148773472696137052">"Smazat zkratku"</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> @@ -1479,6 +1478,7 @@ <string name="touchpad_tutorial_back_gesture_button" msgid="3104716365403620315">"Zpět"</string> <string name="touchpad_tutorial_home_gesture_button" msgid="8023973153559885624">"Přejít na domovskou stránku"</string> <string name="touchpad_tutorial_recent_apps_gesture_button" msgid="8919227647650347359">"Zobrazit nedávné aplikace"</string> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"Přepnout aplikace"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"Hotovo"</string> <string name="gesture_error_title" msgid="469064941635578511">"Zkuste to znovu."</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"Zpět"</string> @@ -1496,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"Výborně!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"Provedli jste gesto pro zobrazení nedávných aplikací."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"Pokud chcete zobrazit poslední aplikace, přejeďte na touchpadu třemi prsty nahoru a podržte je"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"Přepnout aplikace"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"Výborně!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"Dokončili jste gesto přepínání aplikací."</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"Zobrazit všechny aplikace"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"Stiskněte akční klávesu na klávesnici"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"Výborně!"</string> diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml index 1f8c8659b9cf..29961e340408 100644 --- a/packages/SystemUI/res/values-da/strings.xml +++ b/packages/SystemUI/res/values-da/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"Opdaterer"</string> <string name="status_bar_work" msgid="5238641949837091056">"Arbejdsprofil"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Flytilstand"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"Du vil ikke kunne høre din næste alarm <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template" msgid="2234991538018805736">"kl. <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"på <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satellit – god forbindelse"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satellit – forbindelsen er tilgængelig"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS-meldinger via satellit"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"Kun nødopkald eller SOS-meldinger via satellit"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"intet signal"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"én bjælke"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"Tilbage"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"Notifikationer"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"Tastaturgenveje"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"Skift tastaturlayout"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"eller"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"Ryd søgeforespørgsel"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"Tastaturgenveje"</string> @@ -1432,6 +1432,8 @@ <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_split_screen" msgid="1159669813444812244">"Opdelt skærm"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <string name="shortcut_helper_category_input" msgid="8674018654124839566">"Input"</string> <string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Appgenveje"</string> <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Aktuel app"</string> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"Tilpas genveje"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Skal genvejen fjernes?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"Vil du nulstille til standard?"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Tryk på en tast for at tildele genvej"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"Opret denne genvej ved at trykke på handlingstasten og én eller flere andre taster samtidigt"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Denne handling sletter din tilpassede genvej permanent."</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"Denne handling sletter alle dine tilpassede genveje permanent."</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Søg efter genveje"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"Ja, nulstil"</string> <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_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Tastekombinationen er allerede i brug. Prøv en anden tast."</string> + <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="3325858369539848162">"Tastekombinationen er allerede i brug. Prøv en anden kombination."</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="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"Tilføj genvej"</string> @@ -1476,6 +1478,7 @@ <string name="touchpad_tutorial_back_gesture_button" msgid="3104716365403620315">"Gå tilbage"</string> <string name="touchpad_tutorial_home_gesture_button" msgid="8023973153559885624">"Gå til startsiden"</string> <string name="touchpad_tutorial_recent_apps_gesture_button" msgid="8919227647650347359">"Se seneste apps"</string> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"Skift mellem apps"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"Udfør"</string> <string name="gesture_error_title" msgid="469064941635578511">"Prøv igen!"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"Gå tilbage"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"Godt klaret!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"Du har udført bevægelsen for at se de seneste apps."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"Du kan se nyligt brugte apps ved at stryge opad og holde tre fingre nede på touchpladen"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"Skift mellem apps"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"Godt klaret!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"Du har fuldført bevægelsen for at skifte mellem apps."</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"Se alle apps"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"Tryk på handlingstasten på dit tastatur"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"Flot klaret!"</string> diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml index 213955839a71..2e452dda0f34 100644 --- a/packages/SystemUI/res/values-de/strings.xml +++ b/packages/SystemUI/res/values-de/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"Wird aktualisiert…"</string> <string name="status_bar_work" msgid="5238641949837091056">"Arbeitsprofil"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Flugmodus"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"Lautloser Weckruf <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template" msgid="2234991538018805736">"um <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"am <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satellit, Verbindung gut"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satellit, Verbindung verfügbar"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Notruf über Satellit"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"Nur Notrufe oder SOS"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"kein Empfang"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"ein Balken"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"Zurück"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"Benachrichtigungen"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"Tastenkürzel"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"Tastaturlayout wechseln"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"oder"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"Suchanfrage löschen"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"Tastenkombinationen"</string> @@ -1432,6 +1432,8 @@ <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_split_screen" msgid="1159669813444812244">"Splitscreen"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <string name="shortcut_helper_category_input" msgid="8674018654124839566">"Eingabe"</string> <string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Tastaturkürzel für Apps"</string> <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Aktuelle App"</string> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"Tastenkombinationen anpassen"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Tastaturkürzel entfernen?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"Auf Standardeinstellung zurücksetzen?"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Drücke eine Taste, um das Tastaturkürzel einzurichten"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"Drücke zum Erstellen der Tastenkombination gleichzeitig die Aktionstaste und eine oder mehrere andere Tasten"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Das benutzerdefinierte Tastenkürzel wird endgültig gelöscht."</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"Alle deine benutzerdefinierten Tastenkürzel werden endgültig gelöscht."</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Tastenkürzel suchen"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"Ja, zurücksetzen"</string> <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_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_key_combination_in_use_error_message" msgid="3325858369539848162">"Diese Tastenkombination wird bereits verwendet. Versuche es mit einer anderen Kombination."</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="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"Verknüpfung hinzufügen"</string> @@ -1476,6 +1478,7 @@ <string name="touchpad_tutorial_back_gesture_button" msgid="3104716365403620315">"Zurück"</string> <string name="touchpad_tutorial_home_gesture_button" msgid="8023973153559885624">"Zum Startbildschirm"</string> <string name="touchpad_tutorial_recent_apps_gesture_button" msgid="8919227647650347359">"Letzte Apps aufrufen"</string> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"Zwischen Apps wechseln"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"Fertig"</string> <string name="gesture_error_title" msgid="469064941635578511">"Noch einmal versuchen"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"Zurück"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"Gut gemacht!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"Du hast das Tutorial für die Touch-Geste zum Aufrufen der zuletzt verwendeten Apps abgeschlossen."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"Wenn du zuletzt verwendete Apps aufrufen möchtest, wische mit drei Fingern nach oben und halte das Touchpad gedrückt"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"Zwischen Apps wechseln"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"Gut gemacht!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"Du hast die Touch-Geste „Zwischen Apps wechseln\" abgeschlossen."</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"Alle Apps anzeigen"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"Drücke die Aktionstaste auf deiner Tastatur"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"Perfekt!"</string> diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml index 589298ea616b..c5aa667e9c74 100644 --- a/packages/SystemUI/res/values-el/strings.xml +++ b/packages/SystemUI/res/values-el/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"Ενημέρωση"</string> <string name="status_bar_work" msgid="5238641949837091056">"Προφίλ εργασίας"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Λειτουργία πτήσης"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"Δεν θα ακούσετε το επόμενο ξυπνητήρι σας <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template" msgid="2234991538018805736">"στις <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"στις <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Δορυφορική, καλή σύνδεση"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Δορυφορική, διαθέσιμη σύνδεση"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Δορυφορικό SOS"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"Μόνο κλήσεις έκτακτης ανάγκης ή SOS"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"δεν υπάρχει σήμα"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"μία γραμμή"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"Πίσω"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"Ειδοποιήσεις"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"Συντομεύσεις πληκτρολογίου"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"Αλλαγή διάταξης πληκτρολογίου"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"ή"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"Διαγραφή ερωτήματος αναζήτησης"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"Συντομεύσ. πληκτρολογίου"</string> @@ -1432,6 +1432,8 @@ <string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"Εφαρμογές συστήματος"</string> <string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Πολυδιεργασία"</string> <string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Διαχωρισμός οθόνης"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <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> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"Προσαρμογή συντομεύσεων"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Κατάργηση συντόμευσης;"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"Επαναφορά στις προεπιλογές;"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Πατήστε το πλήκτρο για ανάθεση της συντόμευσης"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"Για να δημιουργήσετε αυτή τη συντόμευση, πατήστε ταυτόχρονα το πλήκτρο ενέργειας και ένα ή περισσότερα άλλα πλήκτρα"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Με αυτή την ενέργεια, η προσαρμοσμένη συντόμευση θα διαγραφεί οριστικά."</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"Με αυτή την ενέργεια θα διαγραφούν οριστικά όλες οι προσαρμοσμένες συντομεύσεις."</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Συντομεύσεις αναζήτησης"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"Ναι, επαναφορά"</string> <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_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Ο συνδυασμός πλήκτρων χρησιμοποιείται ήδη. Δοκιμάστε άλλο πλήκτρο."</string> + <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="3325858369539848162">"Ο συνδυασμός πλήκτρων χρησιμοποιείται ήδη. Δοκιμάστε έναν άλλο συνδυασμό."</string> <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Δεν είναι δυνατή η ρύθμιση της συντόμευσης."</string> <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string> <string name="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"Προσθήκη συντόμευσης"</string> @@ -1476,6 +1478,7 @@ <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> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"Εναλλαγή μεταξύ εφαρμογών"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"Τέλος"</string> <string name="gesture_error_title" msgid="469064941635578511">"Δοκιμάστε ξανά!"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"Επιστροφή"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"Μπράβο!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"Ολοκληρώσατε την κίνηση για την προβολή πρόσφατων εφαρμογών."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"Για προβολή πρόσφατων εφαρμογών, σύρετε προς τα επάνω με τρία δάχτυλα και κρατήστε τα δάχτυλά σας στην επιφάνεια αφής"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"Εναλλαγή μεταξύ εφαρμογών"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"Μπράβο!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"Ολοκληρώσατε την κίνηση εναλλαγής εφαρμογών."</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"Προβολή όλων των εφαρμογών"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"Πατήστε το πλήκτρο ενέργειας στο πληκτρολόγιό σας"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"Μπράβο!"</string> diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml index f31e51f7f3da..4144dc300a58 100644 --- a/packages/SystemUI/res/values-en-rAU/strings.xml +++ b/packages/SystemUI/res/values-en-rAU/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"Updating"</string> <string name="status_bar_work" msgid="5238641949837091056">"Work profile"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Aeroplane mode"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"You won\'t hear your next alarm <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template" msgid="2234991538018805736">"at <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"on <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satellite, good connection"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satellite, connection available"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Satellite SOS"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"Emergency calls or SOS only"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"No signal"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"one bar"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"Back"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"Notifications"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"Keyboard shortcuts"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"Switch keyboard layout"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"or"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"Clear search query"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"Keyboard shortcuts"</string> @@ -1432,6 +1432,8 @@ <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_split_screen" msgid="1159669813444812244">"Split screen"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <string name="shortcut_helper_category_input" msgid="8674018654124839566">"Input"</string> <string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"App shortcuts"</string> <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Current app"</string> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"Customise shortcuts"</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_add_shortcut_description" msgid="7636040209946696120">"To create this shortcut, press the action key and one or more other keys together"</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> @@ -1462,7 +1464,7 @@ <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_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Key combination already in use. Try another key."</string> + <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="3325858369539848162">"Key combination already in use. Try another combination."</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="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"Add shortcut"</string> @@ -1476,6 +1478,7 @@ <string name="touchpad_tutorial_back_gesture_button" msgid="3104716365403620315">"Go back"</string> <string name="touchpad_tutorial_home_gesture_button" msgid="8023973153559885624">"Go home"</string> <string name="touchpad_tutorial_recent_apps_gesture_button" msgid="8919227647650347359">"View recent apps"</string> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"Switch apps"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"Done"</string> <string name="gesture_error_title" msgid="469064941635578511">"Try again."</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"Go back"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"Well done!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"You completed the view recent apps gesture."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"To view recent apps, swipe up and hold using three fingers on your touchpad"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"Switch apps"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"Well done!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"You completed the switch apps gesture."</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"View all apps"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"Press the action key on your keyboard"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"Well done!"</string> diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml index be09504a5cd6..b00b01d373f3 100644 --- a/packages/SystemUI/res/values-en-rCA/strings.xml +++ b/packages/SystemUI/res/values-en-rCA/strings.xml @@ -750,6 +750,7 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"Updating"</string> <string name="status_bar_work" msgid="5238641949837091056">"Work profile"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Airplane mode"</string> + <string name="status_bar_supervision" msgid="6735015942701134125">"Parental controls"</string> <string name="zen_alarm_warning" msgid="7844303238486849503">"You won\'t hear your next alarm <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template" msgid="2234991538018805736">"at <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"on <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -759,8 +760,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satellite, good connection"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satellite, connection available"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Satellite SOS"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"Emergency calls or SOS only"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"no signal"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"one bar"</string> @@ -857,7 +857,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"Back"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"Notifications"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"Keyboard Shortcuts"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"Switch keyboard layout"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"or"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"Clear search query"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"Keyboard Shortcuts"</string> @@ -1432,6 +1431,8 @@ <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_split_screen" msgid="1159669813444812244">"Split screen"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <string name="shortcut_helper_category_input" msgid="8674018654124839566">"Input"</string> <string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"App shortcuts"</string> <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Current App"</string> @@ -1440,7 +1441,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"Customize shortcuts"</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_add_shortcut_description" msgid="7636040209946696120">"To create this shortcut, press the Action key and one or more other keys together"</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> @@ -1462,7 +1463,7 @@ <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_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Key combination already in use. Try another key."</string> + <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="3325858369539848162">"Key combination already in use. Try another combination."</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="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"Add shortcut"</string> @@ -1476,6 +1477,7 @@ <string name="touchpad_tutorial_back_gesture_button" msgid="3104716365403620315">"Go back"</string> <string name="touchpad_tutorial_home_gesture_button" msgid="8023973153559885624">"Go home"</string> <string name="touchpad_tutorial_recent_apps_gesture_button" msgid="8919227647650347359">"View recent apps"</string> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"Switch apps"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"Done"</string> <string name="gesture_error_title" msgid="469064941635578511">"Try again!"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"Go back"</string> @@ -1493,6 +1495,11 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"Great job!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"You completed the view recent apps gesture."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"To view recent apps, swipe up and hold using three fingers on your touchpad"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"Switch apps"</string> + <string name="touchpad_switch_apps_gesture_guidance" msgid="2751565200937541667">"Swipe left using four fingers on your touchpad"</string> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"Great job!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"You completed the switch apps gesture."</string> + <string name="touchpad_switch_gesture_error_body" msgid="5508381152326379652">"Swipe left using four fingers on your touchpad to switch apps"</string> <string name="tutorial_action_key_title" msgid="8172535792469008169">"View all apps"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"Press the action key on your keyboard"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"Well done!"</string> diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml index f31e51f7f3da..4144dc300a58 100644 --- a/packages/SystemUI/res/values-en-rGB/strings.xml +++ b/packages/SystemUI/res/values-en-rGB/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"Updating"</string> <string name="status_bar_work" msgid="5238641949837091056">"Work profile"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Aeroplane mode"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"You won\'t hear your next alarm <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template" msgid="2234991538018805736">"at <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"on <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satellite, good connection"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satellite, connection available"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Satellite SOS"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"Emergency calls or SOS only"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"No signal"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"one bar"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"Back"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"Notifications"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"Keyboard shortcuts"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"Switch keyboard layout"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"or"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"Clear search query"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"Keyboard shortcuts"</string> @@ -1432,6 +1432,8 @@ <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_split_screen" msgid="1159669813444812244">"Split screen"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <string name="shortcut_helper_category_input" msgid="8674018654124839566">"Input"</string> <string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"App shortcuts"</string> <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Current app"</string> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"Customise shortcuts"</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_add_shortcut_description" msgid="7636040209946696120">"To create this shortcut, press the action key and one or more other keys together"</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> @@ -1462,7 +1464,7 @@ <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_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Key combination already in use. Try another key."</string> + <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="3325858369539848162">"Key combination already in use. Try another combination."</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="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"Add shortcut"</string> @@ -1476,6 +1478,7 @@ <string name="touchpad_tutorial_back_gesture_button" msgid="3104716365403620315">"Go back"</string> <string name="touchpad_tutorial_home_gesture_button" msgid="8023973153559885624">"Go home"</string> <string name="touchpad_tutorial_recent_apps_gesture_button" msgid="8919227647650347359">"View recent apps"</string> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"Switch apps"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"Done"</string> <string name="gesture_error_title" msgid="469064941635578511">"Try again."</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"Go back"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"Well done!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"You completed the view recent apps gesture."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"To view recent apps, swipe up and hold using three fingers on your touchpad"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"Switch apps"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"Well done!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"You completed the switch apps gesture."</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"View all apps"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"Press the action key on your keyboard"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"Well done!"</string> diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml index f31e51f7f3da..4144dc300a58 100644 --- a/packages/SystemUI/res/values-en-rIN/strings.xml +++ b/packages/SystemUI/res/values-en-rIN/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"Updating"</string> <string name="status_bar_work" msgid="5238641949837091056">"Work profile"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Aeroplane mode"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"You won\'t hear your next alarm <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template" msgid="2234991538018805736">"at <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"on <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satellite, good connection"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satellite, connection available"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Satellite SOS"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"Emergency calls or SOS only"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"No signal"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"one bar"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"Back"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"Notifications"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"Keyboard shortcuts"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"Switch keyboard layout"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"or"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"Clear search query"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"Keyboard shortcuts"</string> @@ -1432,6 +1432,8 @@ <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_split_screen" msgid="1159669813444812244">"Split screen"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <string name="shortcut_helper_category_input" msgid="8674018654124839566">"Input"</string> <string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"App shortcuts"</string> <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Current app"</string> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"Customise shortcuts"</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_add_shortcut_description" msgid="7636040209946696120">"To create this shortcut, press the action key and one or more other keys together"</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> @@ -1462,7 +1464,7 @@ <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_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Key combination already in use. Try another key."</string> + <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="3325858369539848162">"Key combination already in use. Try another combination."</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="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"Add shortcut"</string> @@ -1476,6 +1478,7 @@ <string name="touchpad_tutorial_back_gesture_button" msgid="3104716365403620315">"Go back"</string> <string name="touchpad_tutorial_home_gesture_button" msgid="8023973153559885624">"Go home"</string> <string name="touchpad_tutorial_recent_apps_gesture_button" msgid="8919227647650347359">"View recent apps"</string> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"Switch apps"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"Done"</string> <string name="gesture_error_title" msgid="469064941635578511">"Try again."</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"Go back"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"Well done!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"You completed the view recent apps gesture."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"To view recent apps, swipe up and hold using three fingers on your touchpad"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"Switch apps"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"Well done!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"You completed the switch apps gesture."</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"View all apps"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"Press the action key on your keyboard"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"Well done!"</string> diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml index dacea9ff1b61..85ff751d28ec 100644 --- a/packages/SystemUI/res/values-es-rUS/strings.xml +++ b/packages/SystemUI/res/values-es-rUS/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"Actualizando"</string> <string name="status_bar_work" msgid="5238641949837091056">"Perfil de trabajo"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Modo de avión"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"No oirás la próxima alarma a la(s) <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template" msgid="2234991538018805736">"a la(s) <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"el <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satélite, buena conexión"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satélite, conexión disponible"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS por satélite"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"Solo llamadas de emergencia o SOS"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"sin señal"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"una barra"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"Atrás"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"Notificaciones"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"Ver combinaciones de teclas"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"Cambiar diseño del teclado"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"o"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"Borrar búsqueda"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"Combinaciones de teclas"</string> @@ -1432,6 +1432,8 @@ <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_split_screen" msgid="1159669813444812244">"Pantalla dividida"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <string name="shortcut_helper_category_input" msgid="8674018654124839566">"Entrada"</string> <string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Accesos directos a aplicaciones"</string> <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"App actual"</string> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"Personalizar combinaciones de teclas"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"¿Quieres quitar la combinación?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"¿Quieres restablecer la configuración predeterminada?"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Presiona una tecla para asignar la combinación"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"Para crear esta combinación de teclas, presiona la tecla de acción y una o más teclas"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Esta acción borrará tu combinación personalizada de forma permanente."</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"Esta acción borrará todas tus combinaciones personalizadas de forma permanente."</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Buscar combinaciones de teclas"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"Sí, restablecer"</string> <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_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_key_combination_in_use_error_message" msgid="3325858369539848162">"La combinación de teclas ya está en uso. Prueba con otra combinación."</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="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"Agregar combinación de teclas"</string> @@ -1476,6 +1478,7 @@ <string name="touchpad_tutorial_back_gesture_button" msgid="3104716365403620315">"Atrás"</string> <string name="touchpad_tutorial_home_gesture_button" msgid="8023973153559885624">"Ir a la página principal"</string> <string name="touchpad_tutorial_recent_apps_gesture_button" msgid="8919227647650347359">"Ver apps recientes"</string> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"Cambiar de app"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"Listo"</string> <string name="gesture_error_title" msgid="469064941635578511">"Vuelve a intentarlo"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"Atrás"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"¡Bien hecho!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"Completaste el gesto para ver las apps recientes."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"Para ver las apps recientes, desliza tres dedos hacia arriba y mantenlos presionados en el panel táctil"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"Cambia de app"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"¡Bien hecho!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"Completaste el gesto para cambiar de app."</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"Ver todas las apps"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"Presiona la tecla de acción en el teclado"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"¡Bien hecho!"</string> diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml index 1799055627c1..62c629074ce1 100644 --- a/packages/SystemUI/res/values-es/strings.xml +++ b/packages/SystemUI/res/values-es/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"Actualizando"</string> <string name="status_bar_work" msgid="5238641949837091056">"Perfil de trabajo"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Modo Avión"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"No oirás la próxima alarma (<xliff:g id="WHEN">%1$s</xliff:g>)"</string> <string name="alarm_template" msgid="2234991538018805736">"a las <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"<xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satélite, buena conexión"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satélite, conexión disponible"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS por satélite"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"Solo llamadas de emergencia o SOS"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"no hay señal"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"una barra"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"Atrás"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"Notificaciones"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"Ver combinaciones de teclas"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"Cambiar diseño del teclado"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"o"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"Borrar la consulta de búsqueda"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"Combinaciones de teclas"</string> @@ -1432,6 +1432,8 @@ <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_split_screen" msgid="1159669813444812244">"Pantalla dividida"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <string name="shortcut_helper_category_input" msgid="8674018654124839566">"Entrada"</string> <string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Accesos directos a aplicaciones"</string> <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Aplicación en uso"</string> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"Personalizar combinaciones de teclas"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"¿Eliminar combinación de teclas?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"¿Restablecer valores predeterminados?"</string> - <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_add_shortcut_description" msgid="7636040209946696120">"Para crear esta combinación de teclas, pulsa la tecla de acción y una o varias teclas a la vez"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Se eliminará tu combinación de teclas personalizada de forma permanente."</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"Se eliminarán todos tus accesos directos personalizados de forma permanente."</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Buscar accesos directos"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"Sí, restablecer"</string> <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_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_key_combination_in_use_error_message" msgid="3325858369539848162">"La combinación de teclas ya se está usando. Prueba con otra combinación."</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="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"Añadir combinación de teclas"</string> @@ -1476,6 +1478,7 @@ <string name="touchpad_tutorial_back_gesture_button" msgid="3104716365403620315">"Volver"</string> <string name="touchpad_tutorial_home_gesture_button" msgid="8023973153559885624">"Ir a Inicio"</string> <string name="touchpad_tutorial_recent_apps_gesture_button" msgid="8919227647650347359">"Ver aplicaciones recientes"</string> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"Cambiar de aplicación"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"Hecho"</string> <string name="gesture_error_title" msgid="469064941635578511">"Vuelve a intentarlo."</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"Atrás"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"¡Bien hecho!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"Has completado el gesto para ver las aplicaciones recientes."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"Para ver las aplicaciones recientes, desliza tres dedos hacia arriba y mantén pulsado en el panel táctil"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"Cambiar de aplicación"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"¡Bien hecho!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"Has completado el gesto para cambiar de aplicación."</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"Ver todas las aplicaciones"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"Pulsa la tecla de acción de tu teclado"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"¡Muy bien!"</string> diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml index a91e3dcc7b2f..920af5c4d353 100644 --- a/packages/SystemUI/res/values-et/strings.xml +++ b/packages/SystemUI/res/values-et/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"Värskendamine"</string> <string name="status_bar_work" msgid="5238641949837091056">"Tööprofiil"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Lennukirežiim"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"Te ei kuule järgmist äratust kell <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template" msgid="2234991538018805736">"kell <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"kell <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satelliit, hea ühendus"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satelliit, ühendus on saadaval"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Satelliit-SOS"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"Ainult hädaabikõned või satelliit-SOS"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"signaal puudub"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"üks pulk"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"Tagasi"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"Märguanded"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"Klaviatuuri otseteed"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"Klaviatuuripaigutuse vahetus"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"või"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"Otsingupäringu tühjendamine"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"Klaviatuuri otseteed"</string> @@ -1432,6 +1432,8 @@ <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_split_screen" msgid="1159669813444812244">"Jagatud ekraanikuva"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <string name="shortcut_helper_category_input" msgid="8674018654124839566">"Sisend"</string> <string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Rakenduse otseteed"</string> <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Praegune rakendus"</string> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"Otseteede kohandamine"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Kas soovite otsetee eemaldada?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"Kas lähtestada vaikeseadele?"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Otsetee lisamiseks vajutage klahvi"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"Selle otsetee loomiseks vajutage toiminguklahvi ja ühte või mitut muud klahvi korraga."</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"See kustutab teie kohandatud otsetee jäädavalt."</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"See kustutab kõik teie kohandatud otseteed jäädavalt."</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Otsige otseteid"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"Lähtesta"</string> <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_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Kombinatsioon on juba kasutusel. Proovige mõnda muud klahvi."</string> + <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="3325858369539848162">"Kombinatsioon on juba kasutusel. Proovige mõnda muud kombinatsiooni."</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="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"Otsetee lisamine"</string> @@ -1476,6 +1478,7 @@ <string name="touchpad_tutorial_back_gesture_button" msgid="3104716365403620315">"Mine tagasi"</string> <string name="touchpad_tutorial_home_gesture_button" msgid="8023973153559885624">"Avakuvale"</string> <string name="touchpad_tutorial_recent_apps_gesture_button" msgid="8919227647650347359">"Hiljutiste rakenduste vaatamine"</string> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"Rakenduste vahetamine"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"Valmis"</string> <string name="gesture_error_title" msgid="469064941635578511">"Proovige uuesti!"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"Tagasi"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"Väga hea!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"Tegite hiljutiste rakenduste vaatamise liigutuse."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"Hiljutiste rakenduste kuvamiseks pühkige puuteplaadil kolme sõrmega üles ja hoidke sõrmi puuteplaadil"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"Rakenduste vahetamine"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"Väga hea!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"Tegite rakenduste vahetamise liigutuse."</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"Kõigi rakenduste kuvamine"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"Vajutage klaviatuuril toiminguklahvi"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"Hästi tehtud!"</string> diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml index 3a3c9cc144a6..912da58efc44 100644 --- a/packages/SystemUI/res/values-eu/strings.xml +++ b/packages/SystemUI/res/values-eu/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"Eguneratzen"</string> <string name="status_bar_work" msgid="5238641949837091056">"Laneko profila"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Hegaldi modua"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"Ez duzu entzungo hurrengo alarma (<xliff:g id="WHEN">%1$s</xliff:g>)"</string> <string name="alarm_template" msgid="2234991538018805736">"ordua: <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"data: <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satelitea, konexio ona"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satelitea, konexioa erabilgarri"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Satelite bidezko SOS komunikazioa"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"Larrialdi-deiak edo SOS komunikazioa soilik"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"ez dago seinalerik"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"barra bat"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"Atzera"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"Jakinarazpenak"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"Lasterbideak"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"Aldatu tekl. diseinua"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"edo"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"Garbitu bilaketa-kontsulta"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"Lasterbideak"</string> @@ -1432,6 +1432,8 @@ <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_split_screen" msgid="1159669813444812244">"Pantaila zatitzea"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <string name="shortcut_helper_category_input" msgid="8674018654124839566">"Sarrera"</string> <string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Aplikazioetarako lasterbideak"</string> <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Oraingo aplikazioa"</string> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"Pertsonalizatu lasterbideak"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Lasterbidea kendu nahi duzu?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"Balio lehenetsia berrezarri nahi duzu?"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Sakatu tekla lasterbidea esleitzeko"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"Lasterbide hau sortzeko, sakatu ekintza-tekla eta beste tekla bat edo gehiago batera"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Betiko ezabatuko da lasterbide pertsonalizatua."</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"Lasterbide pertsonalizatu guztiak betiko ezabatuko dira."</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Bilatu lasterbideak"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"Bai, berrezarri"</string> <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_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Tekla-konbinazio hori erabili da dagoeneko. Probatu beste tekla bat."</string> + <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="3325858369539848162">"Tekla-konbinazio hori erabili da dagoeneko. Probatu beste konbinazio batekin."</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="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"Gehitu lasterbide bat"</string> @@ -1476,6 +1478,7 @@ <string name="touchpad_tutorial_back_gesture_button" msgid="3104716365403620315">"Egin atzera"</string> <string name="touchpad_tutorial_home_gesture_button" msgid="8023973153559885624">"Joan orri nagusira"</string> <string name="touchpad_tutorial_recent_apps_gesture_button" msgid="8919227647650347359">"Ikusi azkenaldiko aplikazioak"</string> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"Aldatu aplikazioa"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"Eginda"</string> <string name="gesture_error_title" msgid="469064941635578511">"Saiatu berriro!"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"Egin atzera"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"Bikain!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"Osatu duzu azkenaldiko aplikazioak ikusteko keinua."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"Azkenaldiko aplikazioak ikusteko, pasatu 3 hatz gora eta eduki sakatuta ukipen-panelean"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"Aldatu aplikazioa"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"Bikain!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"Ikasi duzu aplikazio batetik bestera aldatzeko keinua."</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"Ikusi aplikazio guztiak"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"Sakatu teklatuko ekintza-tekla"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"Bikain!"</string> diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml index 2caaf1415470..3612b16de761 100644 --- a/packages/SystemUI/res/values-fa/strings.xml +++ b/packages/SystemUI/res/values-fa/strings.xml @@ -167,7 +167,7 @@ <string name="issuerecord_start_error" msgid="3402782952722871190">"هنگام شروع ضبط مشکل، خطایی پیش آمد"</string> <string name="immersive_cling_title" msgid="8372056499315585941">"درحال مشاهده در حالت تمامصفحه"</string> <string name="immersive_cling_description" msgid="2717426731830851921">"برای خروج، از بالای صفحه تند به پایین بکشید"</string> - <string name="immersive_cling_positive" msgid="3076681691468978568">"متوجهام"</string> + <string name="immersive_cling_positive" msgid="3076681691468978568">"متوجهم"</string> <string name="accessibility_back" msgid="6530104400086152611">"برگشت"</string> <string name="accessibility_home" msgid="5430449841237966217">"صفحهٔ اصلی"</string> <string name="accessibility_menu" msgid="2701163794470513040">"منو"</string> @@ -534,7 +534,7 @@ <string name="accessibility_action_label_expand_widget" msgid="9190524260912211759">"افزایش ارتفاع"</string> <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="communal_widgets_disclaimer_button" msgid="4423059765740780753">"متوجهم"</string> <string name="glanceable_hub_lockscreen_affordance_label" msgid="1461611028615752141">"ابزارهها"</string> <string name="glanceable_hub_lockscreen_affordance_disabled_text" msgid="599170482297578735">"برای افزودن میانبر «ابزارهها»، مطمئن شوید «نمایش ابزارهها در صفحه قفل» در تنظیمات فعال باشد."</string> <string name="glanceable_hub_lockscreen_affordance_action_button_label" msgid="7636151133344609375">"تنظیمات"</string> @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"درحال بهروزرسانی"</string> <string name="status_bar_work" msgid="5238641949837091056">"نمایه کاری"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"حالت هواپیما"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"در ساعت <xliff:g id="WHEN">%1$s</xliff:g>، دیگر صدای زنگ ساعت را نمیشنوید"</string> <string name="alarm_template" msgid="2234991538018805736">"در <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"در <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"ماهواره، اتصال خوب است"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"ماهواره، اتصال دردسترس است"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"درخواست کمک ماهوارهای"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"فقط تماس اضطراری یا درخواست کمک اضطراری"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>، <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"سیگنال وجود ندارد"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"یک خط"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"برگشت"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"اعلانها"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"میانبرهای صفحهکلید"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"تغییر جانمایی صفحهکلید"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"یا"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"پاک کردن پُرسمان جستجو"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"میانبرهای صفحهکلید"</string> @@ -1432,6 +1432,8 @@ <string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"برنامههای سیستم"</string> <string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"چندوظیفگی"</string> <string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"صفحهٔ دونیمه"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <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> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"سفارشیسازی میانبرها"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"میانبر حذف شود؟"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"به تنظیم پیشفرض بازنشانی میکنید؟"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"برای اختصاص دادن میانبر، کلید را فشار دهید"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"برای ایجاد این میانبر، دکمه «کنش» و یک یا چند دکمه دیگر را همزمان فشار دهید."</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"با این کار، میانبر سفارشی شما برای همیشه حذف میشود."</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"با این کار، همه میانبرهای سفارشی برای همیشه حذف خواهند شد."</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"جستجوی میانبرها"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"بله، بازنشانی شود"</string> <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_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"ترکیب کلید ازقبل درحال استفاده است. کلید دیگری را امتحان کنید."</string> + <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="3325858369539848162">"ترکیب کلید ازقبل درحال استفاده است. ترکیب دیگری را امتحان کنید."</string> <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"میانبر تنظیم نشد."</string> <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string> <string name="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"افزودن میانبر"</string> @@ -1476,6 +1478,7 @@ <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> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"جابهجایی بین برنامهها"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"تمام"</string> <string name="gesture_error_title" msgid="469064941635578511">"دوباره امتحان کنید!"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"برگشتن"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"عالی است!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"اشاره «مشاهده برنامههای اخیر» را تمام کردید"</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"برای مشاهده برنامههای اخیر، با سه انگشت روی صفحه لمسی تند بهبالا بکشید و نگه دارید"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"جابهجایی بین برنامهها"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"عالی است!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"اشاره جابهجایی بین برنامهها را تکمیل کردید."</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"مشاهده همه برنامهها"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"دکمه کنش را روی صفحه لمسی فشار دهید"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"عالی بود!"</string> diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml index 0bd79100a959..9f1c72306b22 100644 --- a/packages/SystemUI/res/values-fi/strings.xml +++ b/packages/SystemUI/res/values-fi/strings.xml @@ -752,6 +752,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"Päivitetään"</string> <string name="status_bar_work" msgid="5238641949837091056">"Työprofiili"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Lentokonetila"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"Et kuule seuraavaa hälytystäsi (<xliff:g id="WHEN">%1$s</xliff:g>)."</string> <string name="alarm_template" msgid="2234991538018805736">"kello <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"ajankohtana <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -761,8 +763,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satelliitti, hyvä yhteys"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satelliitti, yhteys saatavilla"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Satellite SOS"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"Vain hätäpuhelut tai Satellite SOS"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"ei signaalia"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"yksi palkki"</string> @@ -859,7 +860,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"Takaisin"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"Ilmoitukset"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"Pikanäppäimet"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"Vaihda näppäimistöasettelu"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"tai"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"Tyhjennä hakulauseke"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"Pikanäppäimet"</string> @@ -1434,6 +1434,8 @@ <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_split_screen" msgid="1159669813444812244">"Jaettu näyttö"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <string name="shortcut_helper_category_input" msgid="8674018654124839566">"Syöte"</string> <string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Sovellusten pikakuvakkeet"</string> <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Nykyinen sovellus"</string> @@ -1442,7 +1444,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"Muokkaa pikanäppäimiä"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Poistetaanko pikanäppäin?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"Palautetaanko oletusasetukset?"</string> - <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_add_shortcut_description" msgid="7636040209946696120">"Voit luoda tämän pikanäppäinyhdistelmän painamalla samaan aikaan toimintonäppäintä ja yhtä tai useampaa muuta näppäintä"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Oma pikanäppäin poistetaan pysyvästi."</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"Kaikki omat pikanäppäimet poistetaan pysyvästi."</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Pikahaut"</string> @@ -1464,7 +1466,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"Kyllä, nollaa"</string> <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_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_key_combination_in_use_error_message" msgid="3325858369539848162">"Näppäinyhdistelmä on jo käytössä. Kokeile toista yhdistelmää."</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="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"Lisää pikanäppäin"</string> @@ -1478,6 +1480,7 @@ <string name="touchpad_tutorial_back_gesture_button" msgid="3104716365403620315">"Takaisin"</string> <string name="touchpad_tutorial_home_gesture_button" msgid="8023973153559885624">"Siirry etusivulle"</string> <string name="touchpad_tutorial_recent_apps_gesture_button" msgid="8919227647650347359">"Katso viimeisimmät sovellukset"</string> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"Vaihda sovellusta"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"Valmis"</string> <string name="gesture_error_title" msgid="469064941635578511">"Yritä uudelleen."</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"Takaisin"</string> @@ -1495,6 +1498,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"Hienoa!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"Olet oppinut Katso viimeisimmät sovellukset ‑eleen."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"Näet äskeiset sovellukset, kun pyyhkäiset ylös ja pidät kosketuslevyä painettuna kolmella sormella."</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"Vaihda sovellusta"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"Hienoa!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"Olet oppinut sovelluksenvaihtoeleen."</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"Näytä kaikki sovellukset"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"Paina näppäimistön toimintonäppäintä"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"Hienoa!"</string> diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml index f93ceb498f4c..6bcce5300d38 100644 --- a/packages/SystemUI/res/values-fr-rCA/strings.xml +++ b/packages/SystemUI/res/values-fr-rCA/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"Mise à jour en cours…"</string> <string name="status_bar_work" msgid="5238641949837091056">"Profil professionnel"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Mode Avion"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"Vous n\'entendrez pas votre prochaine alarme à <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template" msgid="2234991538018805736">"à <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"le <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Bonne connexion satellite"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Connexion satellite accessible"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS par satellite"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"Appels d\'urgence ou SOS seulement"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"aucun signal"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"une barre"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"Précédent"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"Notifications"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"Raccourcis clavier"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"Changer la disposition du clavier"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"ou"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"Effacez la requête de recherche"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"Raccourcis-clavier"</string> @@ -1432,6 +1432,8 @@ <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_split_screen" msgid="1159669813444812244">"Écran divisé"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <string name="shortcut_helper_category_input" msgid="8674018654124839566">"Entrée"</string> <string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Raccourcis des applis"</string> <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Appli actuelle"</string> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"Personnaliser les raccourcis"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Supprimer le raccourci?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"Réinitialiser aux raccourcis par défaut?"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Appuyez sur la touche pour attribuer un raccourci"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"Pour créer ce raccourci, appuyez simultanément sur la touche d\'action et une ou plusieurs autres touches"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Cela supprimera définitivement votre raccourci personnalisé."</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"Cette action supprimera définitivement tous vos raccourcis personnalisés."</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Rechercher des raccourcis"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"Oui, réinitialiser"</string> <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_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_key_combination_in_use_error_message" msgid="3325858369539848162">"La combinaison de touches est déjà utilisée. Essayez une autre combinaison."</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="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"Ajouter un raccourci"</string> @@ -1476,6 +1478,7 @@ <string name="touchpad_tutorial_back_gesture_button" msgid="3104716365403620315">"Retour"</string> <string name="touchpad_tutorial_home_gesture_button" msgid="8023973153559885624">"Retour à la page d\'accueil"</string> <string name="touchpad_tutorial_recent_apps_gesture_button" msgid="8919227647650347359">"Afficher les applis récentes"</string> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"Changer d\'appli"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"OK"</string> <string name="gesture_error_title" msgid="469064941635578511">"Réessayez!"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"Retour"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"Bon travail!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"Vous avez effectué le geste pour afficher les applis récentes."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"Pour afficher vos applis récentes, balayez votre pavé tactile vers le haut avec trois doigts et maintenez-les en place"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"Changer d\'appli"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"Bon travail!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"Vous avez terminé le geste de changement d\'appli."</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"Afficher toutes les applis"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"Appuyez sur la touche d\'action de votre clavier"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"Félicitations!"</string> diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml index f0cc3b52fa02..33628260ff49 100644 --- a/packages/SystemUI/res/values-fr/strings.xml +++ b/packages/SystemUI/res/values-fr/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"Mise à jour"</string> <string name="status_bar_work" msgid="5238641949837091056">"Profil professionnel"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Mode Avion"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"Vous n\'entendrez pas votre prochaine alarme <xliff:g id="WHEN">%1$s</xliff:g>."</string> <string name="alarm_template" msgid="2234991538018805736">"à <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"le <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Bonne connexion satellite"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Connexion satellite disponible"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS par satellite"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"Appels d\'urgence ou SOS uniquement"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"aucun signal"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"faible"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"Précédent"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"Notifications"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"Raccourcis clavier"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"Changer disposition du clavier"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"ou"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"Effacer la requête de recherche"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"Raccourcis clavier"</string> @@ -1432,6 +1432,8 @@ <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_split_screen" msgid="1159669813444812244">"Écran partagé"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <string name="shortcut_helper_category_input" msgid="8674018654124839566">"Saisie"</string> <string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Raccourcis d\'application"</string> <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Appli actuelle"</string> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"Personnaliser les raccourcis"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Supprimer le raccourci ?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"Rétablir les paramètres par défaut ?"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Appuyez sur une touche pour attribuer un raccourci"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"Pour créer ce raccourci, appuyez simultanément sur la touche d\'action et une ou plusieurs autres touches"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Votre raccourci personnalisé sera définitivement supprimé."</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"Tous vos raccourcis personnalisés seront définitivement supprimés."</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Rechercher des raccourcis"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"Oui, rétablir"</string> <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_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_key_combination_in_use_error_message" msgid="3325858369539848162">"Combinaison de touches déjà utilisée. Essayez une autre combinaison."</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="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"Ajouter un raccourci"</string> @@ -1476,6 +1478,7 @@ <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> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"Passer d\'une application à l\'autre"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"OK"</string> <string name="gesture_error_title" msgid="469064941635578511">"Essayez encore."</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"Retour"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"Bravo !"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"Vous avez appris le geste pour afficher les applis récentes"</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"Pour afficher les applis récentes, balayez le pavé tactile vers le haut avec trois doigts et maintenez la position"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"Passer d\'une application à l\'autre"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"Bravo !"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"Vous avez appris le geste pour passer d\'une appli à l\'autre."</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"Afficher toutes les applications"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"Appuyez sur la touche d\'action de votre clavier"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"Bravo !"</string> diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml index a449530050e5..60fe0b098d01 100644 --- a/packages/SystemUI/res/values-gl/strings.xml +++ b/packages/SystemUI/res/values-gl/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"Actualizando"</string> <string name="status_bar_work" msgid="5238641949837091056">"Perfil de traballo"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Modo avión"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"Non escoitarás a alarma seguinte <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template" msgid="2234991538018805736">"ás <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"o <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satélite, boa conexión"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satélite, conexión dispoñible"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS por satélite"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"Só chamadas de emerxencia ou SOS"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"non hai cobertura"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"unha barra"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"Volver"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"Notificacións"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"Atallos de teclado"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"Cambiar deseño do teclado"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"ou"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"Borrar a busca"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"Atallos de teclado"</string> @@ -1432,6 +1432,8 @@ <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_split_screen" msgid="1159669813444812244">"Pantalla dividida"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <string name="shortcut_helper_category_input" msgid="8674018654124839566">"Entrada"</string> <string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Atallos de aplicacións"</string> <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Aplicación actual"</string> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"Personalizar os atallos"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Queres quitar o atallo?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"Queres restablecer a opción predeterminada?"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Preme a tecla para asignar o atallo"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"Para crear este atallo, preme a tecla de acción e unha ou máis teclas á vez"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Eliminarase de forma permanente o teu atallo personalizado."</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"Eliminaranse permanentemente todos os teus atallos personalizados."</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Busca atallos"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"Si, restablecer"</string> <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_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_key_combination_in_use_error_message" msgid="3325858369539848162">"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="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"Engadir un atallo"</string> @@ -1476,6 +1478,7 @@ <string name="touchpad_tutorial_back_gesture_button" msgid="3104716365403620315">"Volver"</string> <string name="touchpad_tutorial_home_gesture_button" msgid="8023973153559885624">"Ir ao inicio"</string> <string name="touchpad_tutorial_recent_apps_gesture_button" msgid="8919227647650347359">"Consultar aplicacións recentes"</string> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"Cambiar de aplicación"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"Feito"</string> <string name="gesture_error_title" msgid="469064941635578511">"Téntao de novo."</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"Volver"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"Moi ben!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"Completaches o titorial do xesto de consultar aplicacións recentes."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"Para ver as aplicacións recentes, pasa tres dedos cara arriba e mantenos premidos no panel táctil"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"Cambiar de aplicación"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"Bravo!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"Completaches o xesto para cambiar de aplicación."</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"Ver todas as aplicacións"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"Preme a tecla de acción do teclado"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"Ben feito!"</string> diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml index 727d11248e8c..f9c5b81a615d 100644 --- a/packages/SystemUI/res/values-gu/strings.xml +++ b/packages/SystemUI/res/values-gu/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"અપડેટ કરી રહ્યાં છીએ"</string> <string name="status_bar_work" msgid="5238641949837091056">"ઑફિસની પ્રોફાઇલ"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"એરપ્લેન મોડ"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"તમે <xliff:g id="WHEN">%1$s</xliff:g> એ તમારો આગલો એલાર્મ સાંભળશો નહીં"</string> <string name="alarm_template" msgid="2234991538018805736">"<xliff:g id="WHEN">%1$s</xliff:g> વાગ્યે"</string> <string name="alarm_template_far" msgid="3561752195856839456">"<xliff:g id="WHEN">%1$s</xliff:g> એ"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"સૅટલાઇટ, સારું કનેક્શન"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"સૅટલાઇટ, કનેક્શન ઉપલબ્ધ છે"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"ઇમર્જન્સી સૅટલાઇટ સહાય"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"માત્ર ઇમર્જન્સી કૉલ કે ઇમર્જન્સી સૅટલાઇટ સહાય"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"કોઈ સિગ્નલ નથી"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"એક બાર"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"પાછળ"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"નોટિફિકેશન"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"કીબોર્ડ શૉર્ટકટ"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"કીબોર્ડ લેઆઉટ સ્વિચ કરો"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"અથવા"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"શોધ ક્વેરી સાફ કરો"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"કીબોર્ડ શૉર્ટકટ"</string> @@ -1432,6 +1432,8 @@ <string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"સિસ્ટમ ઍપ"</string> <string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"એકથી વધુ કાર્યો કરવા"</string> <string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"સ્ક્રીનને વિભાજિત કરો"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <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> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"શૉર્ટકટ કસ્ટમાઇઝ કરો"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"શું શૉર્ટકટ કાઢી નાખીએ?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"પાછા ડિફૉલ્ટ પર રીસેટ કરીએ?"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"શૉર્ટકટ સોંપવા માટે કી દબાવો"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"આ શૉર્ટકટ બનાવવા માટે, ઍક્શન કી અને એક કે તેથી વધુ કી એકસાથે દબાવો"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"આ તમારા કસ્ટમ શૉર્ટકટને કાયમી રીતે ડિલીટ કરશે."</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"આ તમારા બધા કસ્ટમ શૉર્ટકટને કાયમ માટે ડિલીટ કરશે."</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"શૉર્ટકટ શોધો"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"હા, રીસેટ કરો"</string> <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_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"કી સંયોજન પહેલેલેથી ઉપયોગમાં છે. અન્ય કી અજમાવી જુઓ."</string> + <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="3325858369539848162">"કી સંયોજન પહેલેથી ઉપયોગમાં છે. અન્ય સંયોજન અજમાવી જુઓ."</string> <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"શૉર્ટકટ સેટ કરી શકાતો નથી."</string> <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string> <string name="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"શૉર્ટકટ ઉમેરો"</string> @@ -1476,6 +1478,7 @@ <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> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"ઍપ સ્વિચ કરો"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"થઈ ગયું"</string> <string name="gesture_error_title" msgid="469064941635578511">"ફરી પ્રયાસ કરો!"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"પાછા જાઓ"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"ખૂબ સરસ કામ!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"તમે \'તાજેતરની ઍપ જુઓ\' સંકેત પૂર્ણ કર્યો."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"તાજેતરની ઍપ જોવા માટે, તમારા ટચપૅડ પર ત્રણ આંગળી વડે ઉપરની તરફ સ્વાઇપ કરો અને દબાવી રાખો"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"ઍપ સ્વિચ કરો"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"ખૂબ સરસ કામ!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"તમે ઍપ સ્વિચ કરવાનો સંકેત પૂર્ણ કર્યો છે."</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"બધી ઍપ જુઓ"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"તમારા કીબોર્ડ પરની ઍક્શન કી દબાવો"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"વાહ!"</string> diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml index f58512db0863..efb080ab0204 100644 --- a/packages/SystemUI/res/values-hi/strings.xml +++ b/packages/SystemUI/res/values-hi/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"अपडेट हो रहा है"</string> <string name="status_bar_work" msgid="5238641949837091056">"वर्क प्रोफ़ाइल"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"फ़्लाइट मोड"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"आपको <xliff:g id="WHEN">%1$s</xliff:g> पर अपना अगला अलार्म नहीं सुनाई देगा"</string> <string name="alarm_template" msgid="2234991538018805736">"<xliff:g id="WHEN">%1$s</xliff:g> बजे"</string> <string name="alarm_template_far" msgid="3561752195856839456">"<xliff:g id="WHEN">%1$s</xliff:g> पर"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"सैटलाइट कनेक्शन अच्छा है"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"सैटलाइट कनेक्शन उपलब्ध है"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"सैटलाइट एसओएस"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"सिर्फ़ आपातकालीन कॉल या एसओएस का इस्तेमाल करें"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"सिग्नल नहीं है"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"एक सिग्नल बार"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"वापस जाएं"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"सूचनाएं"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"कीबोर्ड शॉर्टकट"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"कीबोर्ड लेआउट बदलें"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"या"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"सर्च क्वेरी साफ़ करें"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"कीबोर्ड शॉर्टकट"</string> @@ -1432,6 +1432,8 @@ <string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"सिस्टम के ऐप्लिकेशन"</string> <string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"मल्टीटास्किंग (एक साथ कई काम करना)"</string> <string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"स्प्लिट स्क्रीन"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <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> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"पसंद के मुताबिक शॉर्टकट बनाएं"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"क्या आपको शॉर्टकट हटाना है?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"क्या आपको फिर से डिफ़ॉल्ट सेटिंग चालू करनी है?"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"शॉर्टकट असाइन करने के लिए बटन दबाएं"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"यह शॉर्टकट बनाने के लिए, ऐक्शन बटन और एक या उससे ज़्यादा अन्य बटन एक साथ दबाएं"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"ऐसा करने पर, पसंद के मुताबिक बनाया गया आपका शॉर्टकट हमेशा के लिए मिट जाएगा."</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"ऐसा करने पर, पसंद के मुताबिक बनाए गए आपके सभी शॉर्टकट हमेशा के लिए मिट जाएंगे."</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"शॉर्टकट खोजें"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"हां, रीसेट करें"</string> <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_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"बटन का यह कॉम्बिनेशन पहले से इस्तेमाल किया जा रहा है. कोई दूसरा कॉम्बिनेशन आज़माएं."</string> + <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="3325858369539848162">"बटन का यह कॉम्बिनेशन पहले से इस्तेमाल किया जा रहा है. कोई दूसरा कॉम्बिनेशन आज़माएं."</string> <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"शॉर्टकट सेट नहीं किया जा सकता."</string> <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string> <string name="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"शॉर्टकट जोड़ें"</string> @@ -1476,6 +1478,7 @@ <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> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"ऐप्लिकेशन के बीच स्विच करें"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"हो गया"</string> <string name="gesture_error_title" msgid="469064941635578511">"फिर से कोशिश करें!"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"वापस जाएं"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"बहुत बढ़िया!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"अब आपको हाथ के जेस्चर का इस्तेमाल करके, हाल ही में इस्तेमाल किए गए ऐप्लिकेशन देखने का तरीका पता चल गया है."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"हाल ही में इस्तेमाल किए गए ऐप्लिकेशन देखने के लिए, अपने टचपैड पर तीन उंगलियों से ऊपर की ओर स्वाइप करें और दबाकर रखें"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"ऐप्लिकेशन के बीच स्विच करें"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"बहुत बढ़िया!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"आपने जान लिया है कि हाथ का जेस्चर इस्तेमाल करके ऐप्लिकेशन के बीच स्विच कैसे करें."</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"सभी ऐप्लिकेशन देखें"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"अपने कीबोर्ड पर ऐक्शन बटन दबाएं"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"बहुत खूब!"</string> diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml index b8b5f82dbf71..5078bfffeff9 100644 --- a/packages/SystemUI/res/values-hr/strings.xml +++ b/packages/SystemUI/res/values-hr/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"Ažuriranje"</string> <string name="status_bar_work" msgid="5238641949837091056">"Poslovni profil"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Način rada u zrakoplovu"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"Nećete čuti sljedeći alarm <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template" msgid="2234991538018805736">"u <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"u <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satelit, dobra veza"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satelit, veza je dostupna"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS putem satelita"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"Samo hitni pozivi ili SOS"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"nema signala"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"jedna crtica"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"Natrag"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"Obavijesti"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"Tipkovni prečaci"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"Promjena rasporeda tipkovnice"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"ili"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"Brisanje upita za pretraživanje"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"Tipkovni prečaci"</string> @@ -1432,6 +1432,8 @@ <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_split_screen" msgid="1159669813444812244">"Podijeljeni zaslon"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <string name="shortcut_helper_category_input" msgid="8674018654124839566">"Unos"</string> <string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Prečaci aplikacija"</string> <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Trenutačna aplikacija"</string> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"Prilagodite prečace"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Želite li ukloniti prečac?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"Želite li vratiti na zadano?"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Pritisnite tipku da biste dodijelili prečac"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"Da biste izradili taj prečac, pritisnite tipku za radnju i jednu ili više drugih tipki zajedno"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Time će se vaš prilagođeni prečac trajno izbrisati."</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"Time će se trajno izbrisati svi vaši prilagođeni prečaci."</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Prečaci za pretraživanje"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"Da, vrati"</string> <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_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Ta se kombinacija već koristi. Pokušajte s nekom drugom."</string> + <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="3325858369539848162">"Ta se kombinacija već koristi. Pokušajte s nekom drugom kombinacijom."</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="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"Dodaj prečac"</string> @@ -1476,6 +1478,7 @@ <string name="touchpad_tutorial_back_gesture_button" msgid="3104716365403620315">"Natrag"</string> <string name="touchpad_tutorial_home_gesture_button" msgid="8023973153559885624">"Na početni zaslon"</string> <string name="touchpad_tutorial_recent_apps_gesture_button" msgid="8919227647650347359">"Pregled nedavnih aplikacija"</string> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"Promjena aplikacije"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"Gotovo"</string> <string name="gesture_error_title" msgid="469064941635578511">"Pokušajte ponovno!"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"Natrag"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"Sjajno!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"Napravili ste pokret za prikaz nedavno korištenih aplikacija."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"Za prikaz nedavnih aplikacija prijeđite trima prstima prema gore na dodirnoj podlozi i zadržite pritisak"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"Promjena aplikacije"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"Sjajno!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"Izvršili ste pokret za promjenu aplikacije."</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"Prikaži sve aplikacije"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"Pritisnite tipku za radnju na tipkovnici"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"Izvrsno!"</string> diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml index e914e755a934..cce273260779 100644 --- a/packages/SystemUI/res/values-hu/strings.xml +++ b/packages/SystemUI/res/values-hu/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"Frissítés…"</string> <string name="status_bar_work" msgid="5238641949837091056">"Munkahelyi profil"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Repülős üzemmód"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"Nem fogja hallani az ébresztést ekkor: <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template" msgid="2234991538018805736">"ekkor: <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"ezen a napon: <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Műhold, jó kapcsolat"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Műhold, van rendelkezésre álló kapcsolat"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Műholdas SOS"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"Csak segélyhívás vagy SOS"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"nincs jel"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"egy sáv"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"Vissza"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"Értesítések"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"Billentyűkódok"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"Billentyűzetkiosztás váltása"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"vagy"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"Keresőkifejezés törlése"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"Billentyűparancsok"</string> @@ -1432,6 +1432,8 @@ <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_split_screen" msgid="1159669813444812244">"Osztott képernyő"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <string name="shortcut_helper_category_input" msgid="8674018654124839566">"Bevitel"</string> <string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Alkalmazásikonok"</string> <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Jelenlegi alkalmazás"</string> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"Gyorsparancsok személyre szabása"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Eltávolítja a billentyűparancsot?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"Visszaállítja az alapértelmezett beállításokat?"</string> - <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_add_shortcut_description" msgid="7636040209946696120">"A billentyűparancs létrehozásához nyomja le egyszerre a műveletbillentyűt és egy vagy több másik billentyűt"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Ezzel véglegesen törli az egyéni billentyűparancsot."</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"Ezzel véglegesen törli az összes egyéni billentyűparancsot."</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Billentyűparancsok keresése"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"Igen, visszaállítom"</string> <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_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_key_combination_in_use_error_message" msgid="3325858369539848162">"A billentyűkombináció már használatban van. Próbálkozzon másik kombinációval."</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="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"Billentyűparancs hozzáadása"</string> @@ -1476,6 +1478,7 @@ <string name="touchpad_tutorial_back_gesture_button" msgid="3104716365403620315">"Vissza"</string> <string name="touchpad_tutorial_home_gesture_button" msgid="8023973153559885624">"Ugrás a főoldalra"</string> <string name="touchpad_tutorial_recent_apps_gesture_button" msgid="8919227647650347359">"Legutóbbi alkalmazások megtekintése"</string> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"Váltás az alkalmazások között"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"Kész"</string> <string name="gesture_error_title" msgid="469064941635578511">"Próbálja újra"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"Vissza"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"Kiváló!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"Teljesítette a legutóbbi alkalmazások megtekintésének kézmozdulatát."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"A legutóbbi appokért csúsztasson gyorsan három ujjal felfelé az érintőpadon, és tartsa lenyomva ujjait."</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"Váltás az alkalmazások között"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"Kiváló!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"Teljesítette az appváltó gesztust."</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"Összes alkalmazás megtekintése"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"Nyomja meg a műveletbillentyűt az érintőpadon."</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"Szép munka!"</string> diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml index 262c3dd93cdd..10cf6703e3e0 100644 --- a/packages/SystemUI/res/values-hy/strings.xml +++ b/packages/SystemUI/res/values-hy/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"Թարմացում"</string> <string name="status_bar_work" msgid="5238641949837091056">"Android for Work-ի պրոֆիլ"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Ավիառեժիմ"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"Ժամը <xliff:g id="WHEN">%1$s</xliff:g>-ի զարթուցիչը չի զանգի"</string> <string name="alarm_template" msgid="2234991538018805736">"<xliff:g id="WHEN">%1$s</xliff:g>-ին"</string> <string name="alarm_template_far" msgid="3561752195856839456">"<xliff:g id="WHEN">%1$s</xliff:g>-ին"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Արբանյակային լավ կապ"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Հասանելի է արբանյակային կապ"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Satellite SOS"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"Միայն շտապ կանչեր կամ SOS"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>։"</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"ազդանշան չկա"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"մեկ գիծ"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"Հետ"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"Ծանուցումներ"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"Ստեղնային դյուրանցումներ"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"Դասավորության փոխարկում"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"կամ"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"Ջնջել որոնման հարցումը"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"Ստեղնային դյուրանցումներ"</string> @@ -1432,6 +1432,8 @@ <string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"Համակարգային հավելվածներ"</string> <string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Բազմախնդրություն"</string> <string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Տրոհված էկրան"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <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> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"Դյուրանցումների անհատականացում"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Հեռացնե՞լ դյուրանցումը"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"Վերականգնե՞լ կանխադրվածները"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Սեղմեք որևէ ստեղն՝ դյուրանցում նշանակելու համար"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"Այս դյուրանցումը ստեղծելու համար սեղմեք գործողության ստեղնը ու ևս մեկ կամ մի քանի ստեղներ"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Ձեր հատուկ դյուրանցումն ընդմիշտ կջնջվի։"</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"Բոլոր հատուկ դյուրանցումներն ընդմիշտ կջնջվեն։"</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Դյուրանցումների որոնում"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"Վերականգնել"</string> <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_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Ստեղների համակցությունն արդեն օգտագործվում է։ Ընտրեք ուրիշը։"</string> + <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="3325858369539848162">"Ստեղների համակցությունն արդեն օգտագործվում է։ Ընտրեք ուրիշը։"</string> <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Դյուրանցումը հնարավոր չէ ստեղծել։"</string> <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string> <string name="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"Ավելացնել դյուրանցում"</string> @@ -1476,6 +1478,7 @@ <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> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"Անցում մեկ հավելվածից մյուսին"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"Պատրաստ է"</string> <string name="gesture_error_title" msgid="469064941635578511">"Նորից փորձեք։"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"Հետ գնալ"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"Կեցցե՛ք"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"Դուք կատարեցիք վերջին օգտագործված հավելվածների դիտման ժեստը։"</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"Վերջին հավելվածները տեսնելու համար երեք մատը սահեցրեք վերև և սեղմած պահեք հպահարթակին"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"Անցում մեկ հավելվածից մյուսին"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"Կեցցե՛ք։"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"Դուք սովորեցիք ուրիշ հավելված անցնելու ժեստը։"</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"Ինչպես դիտել բոլոր հավելվածները"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"Սեղմեք գործողության ստեղնը ստեղնաշարի վրա"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"Հիանալի՛ է"</string> diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml index 8e1118c76e24..a5f01997654a 100644 --- a/packages/SystemUI/res/values-in/strings.xml +++ b/packages/SystemUI/res/values-in/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"Update berlangsung"</string> <string name="status_bar_work" msgid="5238641949837091056">"Profil kerja"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Mode pesawat"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"Anda tidak akan mendengar alarm berikutnya <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template" msgid="2234991538018805736">"pukul <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"pada <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satelit, koneksi baik"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satelit, koneksi tersedia"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS via Satelit"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"Panggilan darurat atau SOS saja"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"tidak ada sinyal"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"satu batang"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"Kembali"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"Notifikasi"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"Pintasan keyboard"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"Ganti tata letak keyboard"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"atau"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"Hapus kueri penelusuran"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"Pintasan Keyboard"</string> @@ -1432,6 +1432,8 @@ <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_split_screen" msgid="1159669813444812244">"Layar terpisah"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <string name="shortcut_helper_category_input" msgid="8674018654124839566">"Input"</string> <string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Pintasan aplikasi"</string> <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Aplikasi Saat Ini"</string> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"Sesuaikan pintasan"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Hapus pintasan?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"Reset kembali ke pintasan default?"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Tekan tombol untuk menetapkan pintasan"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"Untuk membuat pintasan ini, tekan tombol Tindakan dan satu atau beberapa tombol lainnya secara bersamaan"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Tindakan ini akan menghapus pintasan kustom Anda secara permanen."</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"Tindakan ini akan menghapus semua pintasan kustom Anda secara permanen."</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Telusuri pintasan"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"Ya, reset"</string> <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_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Kombinasi tombol sudah digunakan. Coba tombol lain."</string> + <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="3325858369539848162">"Kombinasi tombol sudah digunakan. Coba kombinasi 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="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"Tambahkan pintasan"</string> @@ -1476,6 +1478,7 @@ <string name="touchpad_tutorial_back_gesture_button" msgid="3104716365403620315">"Kembali"</string> <string name="touchpad_tutorial_home_gesture_button" msgid="8023973153559885624">"Buka layar utama"</string> <string name="touchpad_tutorial_recent_apps_gesture_button" msgid="8919227647650347359">"Lihat aplikasi terbaru"</string> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"Beralih aplikasi"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"Selesai"</string> <string name="gesture_error_title" msgid="469064941635578511">"Coba lagi"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"Kembali"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"Bagus!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"Anda telah menyelesaikan gestur untuk melihat aplikasi terbaru."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"Untuk melihat aplikasi terbaru, geser ke atas dan tahan menggunakan tiga jari di touchpad"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"Beralih aplikasi"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"Bagus!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"Anda telah menyelesaikan gestur beralih aplikasi."</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"Lihat semua aplikasi"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"Tekan tombol tindakan di keyboard"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"Oke!"</string> diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml index f5488e0939de..576a22742573 100644 --- a/packages/SystemUI/res/values-is/strings.xml +++ b/packages/SystemUI/res/values-is/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"Uppfærir"</string> <string name="status_bar_work" msgid="5238641949837091056">"Vinnusnið"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Flugstilling"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"Ekki mun heyrast í vekjaranum <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template" msgid="2234991538018805736">"<xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"<xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Gervihnöttur, góð tenging"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Gervihnöttur, tenging tiltæk"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Gervihnattar-SOS"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"Neyðarsímtöl eða SOS eingöngu"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"ekkert samband"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"eitt strik"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"Til baka"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"Tilkynningar"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"Flýtilyklar"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"Skipta um lyklaskipan"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"eða"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"Hreinsa leitarfyrirspurn"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"Flýtilyklar"</string> @@ -1432,6 +1432,8 @@ <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_split_screen" msgid="1159669813444812244">"Skjáskipting"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <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> <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Núverandi forrit"</string> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"Sérsníða flýtilykla"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Fjarlægja flýtileið?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"Endurstilla á sjálfgefið?"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Ýttu á lykil til að stilla flýtileið"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"Til að búa til þessa flýtileið skaltu ýta á aðgerðalykilinn og einn eða fleiri lykla saman"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Þetta eyðir sérsniðnu flýtileiðinni varanlega."</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"Þetta verður til þess að öllum sérsniðnu flýtileiðunum þínum verður eytt varanlega."</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Leita að flýtileiðum"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"Já, endurstilla"</string> <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_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Lyklasamsetningin er þegar í notkun. Prófaðu annan lykil."</string> + <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="3325858369539848162">"Lyklasamsetningin er þegar í notkun. Prófaðu aðra samsetningu."</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="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"Bæta flýtileið við"</string> @@ -1476,6 +1478,7 @@ <string name="touchpad_tutorial_back_gesture_button" msgid="3104716365403620315">"Til baka"</string> <string name="touchpad_tutorial_home_gesture_button" msgid="8023973153559885624">"Fara á heimaskjá"</string> <string name="touchpad_tutorial_recent_apps_gesture_button" msgid="8919227647650347359">"Sjá nýleg forrit"</string> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"Að skipta á milli forrita"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"Lokið"</string> <string name="gesture_error_title" msgid="469064941635578511">"Reyndu aftur!"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"Til baka"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"Vel gert!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"Þú framkvæmdir bendinguna til að sjá nýleg forrit."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"Strjúktu upp og haltu þremur fingrum inni á snertifletinum til að sjá nýleg forrit"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"Að skipta á milli forrita"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"Vel gert!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"Þú framkvæmdir bendinguna til að skipta á milli forrita."</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"Sjá öll forrit"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"Ýttu á aðgerðalykilinn á lyklaborðinu"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"Vel gert!"</string> diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml index 81367f369159..9b847c4c344d 100644 --- a/packages/SystemUI/res/values-it/strings.xml +++ b/packages/SystemUI/res/values-it/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"Aggiornamento in corso…"</string> <string name="status_bar_work" msgid="5238641949837091056">"Profilo di lavoro"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Modalità aereo"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"Non sentirai la tua prossima sveglia <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template" msgid="2234991538018805736">"alle <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"<xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satellitare, connessione buona"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satellitare, connessione disponibile"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS satellitare"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"Solo chiamate di emergenza o SOS"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"nessun segnale"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"una barra"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"Indietro"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"Notifiche"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"Scorciatoie da tastiera"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"Cambia layout della tastiera"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"o"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"Cancella la query di ricerca"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"Scorciatoie da tastiera"</string> @@ -1432,6 +1432,8 @@ <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_split_screen" msgid="1159669813444812244">"Schermo diviso"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <string name="shortcut_helper_category_input" msgid="8674018654124839566">"Input"</string> <string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Scorciatoie app"</string> <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"App corrente"</string> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"Personalizza scorciatoie"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Rimuovere scorciatoia?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"Vuoi ripristinare le impostazioni predefinite?"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Premi un tasto per assegnare una scorciatoia"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"Per creare questa scorciatoia, premi il tasto Azione e uno o più altri tasti contemporaneamente"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"La scorciatoia personalizzata verrà eliminata definitivamente."</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"Tutte le tue scorciatoie personalizzate verranno eliminate definitivamente."</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Cerca scorciatoie"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"Sì, ripristina"</string> <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_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_key_combination_in_use_error_message" msgid="3325858369539848162">"Combinazione di tasti già in uso. Prova un\'altra combinazione."</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="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"Aggiungi scorciatoia"</string> @@ -1476,6 +1478,7 @@ <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> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"Cambia app"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"Fine"</string> <string name="gesture_error_title" msgid="469064941635578511">"Riprova."</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"Indietro"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"Ottimo lavoro!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"Hai completato il gesto Visualizza app recenti."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"Per visualizzare le app recenti, scorri verso l\'alto e tieni premuto con tre dita sul touchpad"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"Cambia app"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"Ottimo lavoro."</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"Hai completato il gesto Cambia app."</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"Visualizza tutte le app"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"Premi il tasto azione sulla tastiera"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"Ben fatto!"</string> diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml index a40dd62c4620..afa1af584c44 100644 --- a/packages/SystemUI/res/values-iw/strings.xml +++ b/packages/SystemUI/res/values-iw/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"מתבצע עדכון"</string> <string name="status_bar_work" msgid="5238641949837091056">"פרופיל עבודה"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"מצב טיסה"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"לא ניתן יהיה לשמוע את השעון המעורר הבא שלך <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template" msgid="2234991538018805736">"בשעה <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"ב-<xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"לוויין, חיבור באיכות טובה"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"לוויין, יש חיבור זמין"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"תקשורת לוויינית למצב חירום"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"רק שיחות חירום או תקשורת לוויינית למצב חירום"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"אין קליטה"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"פס אחד"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"חזרה"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"התראות"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"מקשי קיצור במקלדת"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"החלפה של פריסת מקלדת"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"או"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"ניקוי שאילתת החיפוש"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"מקשי קיצור"</string> @@ -1432,16 +1432,17 @@ <string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"אפליקציות מערכת"</string> <string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"ריבוי משימות"</string> <string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"מסך מפוצל"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <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> - <!-- no translation found for shortcut_helper_customize_mode_title (8327297960035006036) --> - <skip /> + <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"התאמה אישית של מקשי הקיצור"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"להסיר את קיצור הדרך?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"לאפס לברירת המחדל?"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"צריך להקיש על מקש כדי להקצות מקש קיצור"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"כדי ליצור את קיצור הדרך הזה, מקישים על מקש הפעולה ועל עוד מקש אחד או יותר ביחד"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"קיצור הדרך יימחק לתמיד."</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"הפעולה הזו תמחק לתמיד את כל קיצורי הדרך שמותאמים אישית."</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"חיפוש מקשי קיצור"</string> @@ -1463,13 +1464,11 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"כן, לאפס"</string> <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_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"שילוב המקשים הזה כבר בשימוש. אפשר לנסות מקש אחר."</string> + <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="3325858369539848162">"שילוב המקשים הזה תפוס. צריך לנסות שילוב אחר."</string> <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"לא ניתן להגדיר את קיצור הדרך."</string> <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string> - <!-- no translation found for shortcut_helper_add_shortcut_button_label (7655779534665954910) --> - <skip /> - <!-- no translation found for shortcut_helper_delete_shortcut_button_label (3148773472696137052) --> - <skip /> + <string name="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"הוספת קיצור דרך"</string> + <string name="shortcut_helper_delete_shortcut_button_label" msgid="3148773472696137052">"מחיקת קיצור הדרך"</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> @@ -1479,6 +1478,8 @@ <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> + <!-- no translation found for touchpad_tutorial_switch_apps_gesture_button (7768255095423767779) --> + <skip /> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"סיום"</string> <string name="gesture_error_title" msgid="469064941635578511">"צריך לנסות שוב."</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"חזרה"</string> @@ -1496,6 +1497,16 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"מעולה!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"סיימת לתרגל את התנועה להצגת האפליקציות האחרונות."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"כדי לראות את האפליקציות האחרונות, צריך להחליק למעלה וללחוץ לחיצה ארוכה עם שלוש אצבעות על לוח המגע"</string> + <!-- no translation found for touchpad_switch_apps_gesture_action_title (6835222344612924512) --> + <skip /> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <!-- no translation found for touchpad_switch_apps_gesture_success_title (4894947244328032458) --> + <skip /> + <!-- no translation found for touchpad_switch_apps_gesture_success_body (8151089866035126312) --> + <skip /> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"צפייה בכל האפליקציות"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"צריך להקיש על מקש הפעולה במקלדת"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"כל הכבוד!"</string> diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml index 8941217e8cbd..87c6aedfd4a0 100644 --- a/packages/SystemUI/res/values-ja/strings.xml +++ b/packages/SystemUI/res/values-ja/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"更新しています"</string> <string name="status_bar_work" msgid="5238641949837091056">"仕事用プロファイル"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"機内モード"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"次回のアラーム(<xliff:g id="WHEN">%1$s</xliff:g>)は鳴りません"</string> <string name="alarm_template" msgid="2234991538018805736">"<xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"<xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"衛生、接続状態良好"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"衛生、接続利用可能"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"衛星 SOS"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"緊急通報または SOS のみ"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>、<xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>。"</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"圏外"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"レベル 1"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"戻る"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"通知"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"キーボード ショートカット"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"キーボード レイアウトの切り替え"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"または"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"検索クエリをクリア"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"キーボード ショートカット"</string> @@ -1432,6 +1432,8 @@ <string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"システムアプリ"</string> <string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"マルチタスク"</string> <string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"分割画面"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <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> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"ショートカットのカスタマイズ"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"ショートカットを削除しますか?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"デフォルトにリセットしますか?"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"ショートカットを割り当てるキーを押してください"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"このショートカットを作成するには、アクションキーと他のキー(複数可)を同時に押してください"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"この操作を行うと、カスタム ショートカットが完全に削除されます。"</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"この操作を行うと、すべてのカスタム ショートカットが完全に削除されます。"</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"ショートカットの検索"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"リセット"</string> <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_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"このキーの組み合わせはすでに使用されています。別のキーを試してください。"</string> + <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="3325858369539848162">"このキーの組み合わせはすでに使用されています。別の組み合わせをお試しください。"</string> <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"ショートカットを設定できません。"</string> <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string> <string name="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"ショートカットを追加"</string> @@ -1476,6 +1478,7 @@ <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> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"アプリの切り替え"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"完了"</string> <string name="gesture_error_title" msgid="469064941635578511">"もう一度お試しください。"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"戻る"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"よくできました!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"「最近使ったアプリを表示する」ジェスチャーを学習しました。"</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"最近使ったアプリを表示するには、3 本の指でタッチパッドを上にスワイプして長押しします"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"アプリの切り替え"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"よくできました!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"アプリを切り替えるジェスチャーを学習しました。"</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"すべてのアプリを表示"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"キーボードのアクションキーを押します"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"完了です!"</string> diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml index 6937048c1826..31f486f05373 100644 --- a/packages/SystemUI/res/values-ka/strings.xml +++ b/packages/SystemUI/res/values-ka/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"მიმდინარეობს განახლება"</string> <string name="status_bar_work" msgid="5238641949837091056">"სამსახურის პროფილი"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"თვითმფრინავის რეჟიმი"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"ვერ გაიგონებთ მომდევნო მაღვიძარას <xliff:g id="WHEN">%1$s</xliff:g>-ზე"</string> <string name="alarm_template" msgid="2234991538018805736">"<xliff:g id="WHEN">%1$s</xliff:g>-ზე"</string> <string name="alarm_template_far" msgid="3561752195856839456">"<xliff:g id="WHEN">%1$s</xliff:g>-ზე"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"კარგი სატელიტური კავშირი"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"ხელმისაწვდომია სატელიტური კავშირი"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"სატელიტური SOS"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"გადაუდებელი ზარი ან მხოლოდ SOS"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"სიგნალი არ არის"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"ერთი ხაზი"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"უკან"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"შეტყობინებები"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"კლავიატურის მალსახმობები"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"კლავიატურის განლაგების გადართვა"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"ან"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"საძიებო ფრაზის გასუფთავება"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"კლავიატურის მალსახმობები"</string> @@ -1432,6 +1432,8 @@ <string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"სისტემის აპები"</string> <string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"მრავალამოცანიანი რეჟიმი"</string> <string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"ეკრანის გაყოფა"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <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> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"მალსახმობების მორგება"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"გსურთ მალსახმობის წაშლა?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"გსურთ ნაგულისხმევზე გადაყენება?"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"მალსახმობის მინიჭებისთვის დააჭირეთ კლავიშს"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"ამ მალსახმობის შესაქმნელად ერთად დააჭირეთ მოქმედების კლავიშს და ერთ ან მეტ სხვა კლავიშს"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"ეს თქვენს მორგებულ მალსახმობებს სამუდამოდ წაშლის."</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"ეს სამუდამოდ წაშლის თქვენს ყველა მორგებულ მალსახმობს."</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"ძიების მალსახმობები"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"დიახ, გადაყენდეს"</string> <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_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"კლავიშების კომბინაცია უკვე გამოიყენება. ცადეთ სხვა კლავიში."</string> + <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="3325858369539848162">"კლავიშების კომბინაცია უკვე გამოიყენება. ცადეთ სხვა კომბინაცია."</string> <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"მალსახმობის დაყენება ვერ ხერხდება."</string> <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string> <string name="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"მალსახმობის დამატება"</string> @@ -1476,6 +1478,7 @@ <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> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"აპების გადართვა"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"მზადაა"</string> <string name="gesture_error_title" msgid="469064941635578511">"ცადეთ ხელახლა!"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"უკან დაბრუნება"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"შესანიშნავია!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"თქვენ დაასრულეთ ბოლო აპების ხედის ჟესტი."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"ბოლო აპების სანახავად თქვენს სენსორულ პანელზე სამი თითით გადაფურცლეთ ზევით და ხანგრძლივად დააჭირეთ"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"აპების გადართვა"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"შესანიშნავია!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"თქვენ შეასრულეთ აპების გადართვის ჟესტი."</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"ყველა აპის ნახვა"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"დააჭირეთ მოქმედების კლავიშს თქვენს კლავიატურაზე"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"ყოჩაღ!"</string> diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml index d6f0a006f588..0b6c7b9919b2 100644 --- a/packages/SystemUI/res/values-kk/strings.xml +++ b/packages/SystemUI/res/values-kk/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"Жаңартылып жатыр"</string> <string name="status_bar_work" msgid="5238641949837091056">"Жұмыс профилі"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Ұшақ режимі"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"Келесі <xliff:g id="WHEN">%1$s</xliff:g> дабылыңызды есітпейсіз"</string> <string name="alarm_template" msgid="2234991538018805736">"<xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"<xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Жерсерік, байланыс жақсы."</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Жерсерік, байланыс бар."</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Satellite SOS"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"Тек құтқару қызметіне қоңырау шалу немесе SOS сигналын жіберу"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"сигнал жоқ"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"бір жолақ"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"Артқа"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"Хабарландырулар"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"Перне тіркесімдері"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"Пернетақта форматын ауыстыру"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"немесе"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"Іздеу сұрауын өшіру"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"Перне тіркесімдері"</string> @@ -1432,6 +1432,8 @@ <string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"Жүйелік қолданбалар"</string> <string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Мультитаскинг"</string> <string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Экранды бөлу"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <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> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"Пернелер тіркесімін бейімдеу"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Тіркесімді өшіру керек пе?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"Әдепкі тіркесімге қайтару керек пе?"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Тіркесім тағайындау үшін пернені басыңыз."</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"Осы тіркесімді жасау үшін әрекет пернесін және басқа бір не бірнеше пернені бірге басыңыз."</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Арнаулы тіркесіміңіз біржола жойылады."</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"Мұндайда барлық арнаулы тіркесім біржола жойылады."</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Тіркесімдерді іздеу"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"Иә, қайтару"</string> <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_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Бұл пернелер тіркесімі қазір қолданыста. Басқа перне таңдаңыз."</string> + <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="3325858369539848162">"Бұл пернелер тіркесімі бұрыннан қолданылады. Басқа тіркесімді пайдаланып көріңіз."</string> <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Перне тіркесімін орнату мүмкін емес."</string> <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string> <string name="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"Жылдам пәрмен қосу"</string> @@ -1476,6 +1478,7 @@ <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> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"Қолданба ауыстыру"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"Дайын"</string> <string name="gesture_error_title" msgid="469064941635578511">"Қайталап көріңіз"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"Артқа"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"Жарайсыз!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"Соңғы қолданбаларды көру қимылын орындадыңыз."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"Соңғы қолданбаларды көру үшін сенсорлық тақтада үш саусақпен жоғары сырғытып, ұстап тұрыңыз."</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"Қолданба ауыстыру"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"Жарайсыз!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"Қолданба ауыстыру қимылын аяқтадыңыз."</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"Барлық қолданбаны көру"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"Пернетақтадағы әрекет пернесін басыңыз."</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"Жарайсыз!"</string> diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml index efbc0aa0d68c..a2f5b01c81de 100644 --- a/packages/SystemUI/res/values-km/strings.xml +++ b/packages/SystemUI/res/values-km/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"កំពុងដំឡើងកំណែ"</string> <string name="status_bar_work" msgid="5238641949837091056">"កម្រងព័ត៌មានការងារ"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"ពេលជិះយន្តហោះ"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"អ្នកនឹងមិនលឺម៉ោងរោទ៍ <xliff:g id="WHEN">%1$s</xliff:g> បន្ទាប់របស់អ្នកទេ"</string> <string name="alarm_template" msgid="2234991538018805736">"នៅ <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"នៅ <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"ផ្កាយរណប មានការតភ្ជាប់ល្អ"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"ផ្កាយរណប អាចតភ្ជាប់បាន"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"ការប្រកាសអាសន្នតាមផ្កាយរណប"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"សម្រាប់ការហៅទៅលេខសង្គ្រោះបន្ទាន់ ឬការប្រកាសអាសន្នតែប៉ុណ្ណោះ"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>។"</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"គ្មានសញ្ញា"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"មួយកាំ"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"ថយក្រោយ"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"ការជូនដំណឹង"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"ផ្លូវកាត់ក្ដារចុច"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"ប្ដូរប្លង់ក្ដារចុច"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"ឬ"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"សម្អាតសំណួរស្វែងរក"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"ផ្លូវកាត់ក្ដារចុច"</string> @@ -1432,6 +1432,8 @@ <string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"កម្មវិធីប្រព័ន្ធ"</string> <string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"ការធ្វើកិច្ចការច្រើន"</string> <string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"មុខងារបំបែកអេក្រង់"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <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> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"ប្ដូរផ្លូវកាត់តាមបំណង"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"ដកផ្លូវកាត់ចេញឬ?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"កំណត់ឡើងវិញទៅលំនាំដើមឬ?"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"ចុចគ្រាប់ចុច ដើម្បីកំណត់ផ្លូវកាត់"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"ដើម្បីបង្កើតផ្លូវកាត់នេះ សូមចុចគ្រាប់ចុចសកម្មភាព និងគ្រាប់ចុចមួយ ឬច្រើនផ្សេងទៀតជាមួយគ្នា"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"ការធ្វើបែបនេះនឹងលុបផ្លូវកាត់ផ្ទាល់ខ្លួនរបស់អ្នកជាអចិន្ត្រៃយ៍។"</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"សកម្មភាពនេះនឹងលុបផ្លូវកាត់ផ្ទាល់ខ្លួនរបស់អ្នកទាំងអស់ជាអចិន្ត្រៃយ៍។"</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"ស្វែងរកផ្លូវកាត់"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"បាទ/ចាស កំណត់ឡើងវិញ"</string> <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_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"បន្សំគ្រាប់ចុចនេះត្រូវបានប្រើប្រាស់ហើយ។ សាកល្បងគ្រាប់ចុចផ្សេង។"</string> + <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="3325858369539848162">"កំពុងប្រើបន្សំគ្រាប់ចុចស្រាប់ហើយ។ សូមសាកល្បងបន្សំគ្រាប់ចុចផ្សេង។"</string> <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"មិនអាចកំណត់ផ្លូវកាត់បានទេ។"</string> <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string> <string name="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"បញ្ចូលផ្លូវកាត់"</string> @@ -1476,6 +1478,7 @@ <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> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"ប្ដូរកម្មវិធី"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"រួចរាល់"</string> <string name="gesture_error_title" msgid="469064941635578511">"សូមព្យាយាមម្ដងទៀត!"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"ថយក្រោយ"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"ធ្វើបានល្អ!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"អ្នកបានបញ្ចប់ការមើលចលនាកម្មវិធីថ្មីៗ។"</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"ដើម្បីមើលកម្មវិធីថ្មីៗ សូមអូសឡើងលើ រួចសង្កត់ឱ្យជាប់លើផ្ទាំងប៉ះរបស់អ្នក ដោយប្រើម្រាមដៃបី"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"ប្ដូរកម្មវិធី"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"ធ្វើបានល្អ!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"អ្នកបានបញ្ចប់ចលនាប្ដូរកម្មវិធីហើយ។"</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"មើលកម្មវិធីទាំងអស់"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"ចុចគ្រាប់ចុចសកម្មភាពលើក្ដារចុចរបស់អ្នក"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"ធ្វើបានល្អ!"</string> diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml index ded267feee65..76f38c0337db 100644 --- a/packages/SystemUI/res/values-kn/strings.xml +++ b/packages/SystemUI/res/values-kn/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"ಅಪ್ಡೇಟ್ ಮಾಡಲಾಗುತ್ತಿದೆ"</string> <string name="status_bar_work" msgid="5238641949837091056">"ಕೆಲಸದ ಪ್ರೊಫೈಲ್"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"ಏರ್ಪ್ಲೇನ್ ಮೋಡ್"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"ನಿಮ್ಮ ಮುಂದಿನ <xliff:g id="WHEN">%1$s</xliff:g> ಅಲಾರಮ್ ಅನ್ನು ನೀವು ಆಲಿಸುವುದಿಲ್ಲ"</string> <string name="alarm_template" msgid="2234991538018805736">"<xliff:g id="WHEN">%1$s</xliff:g> ರಲ್ಲಿ"</string> <string name="alarm_template_far" msgid="3561752195856839456">"<xliff:g id="WHEN">%1$s</xliff:g> ರಂದು"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"ಸ್ಯಾಟಲೈಟ್, ಕನೆಕ್ಷನ್ ಉತ್ತಮವಾಗಿದೆ"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"ಸ್ಯಾಟಲೈಟ್, ಕನೆಕ್ಷನ್ ಲಭ್ಯವಿದೆ"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"ಸ್ಯಾಟಲೈಟ್ SOS"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"ತುರ್ತು ಕರೆಗಳು ಅಥವಾ SOS ಮಾತ್ರ"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"ಸಿಗ್ನಲ್ ಇಲ್ಲ"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"ಒಂದು ಬಾರ್"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"ಹಿಂದೆ"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"ನೋಟಿಫಿಕೇಶನ್ಗಳು"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"ಕೀಬೋರ್ಡ್ ಶಾರ್ಟ್ಕಟ್ಗಳು"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"ಕೀಬೋರ್ಡ್ ಲೇಔಟ್ ಬದಲಾಯಿಸಿ"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"ಅಥವಾ"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"ಹುಡುಕಾಟದ ಪ್ರಶ್ನೆಯನ್ನು ತೆರವುಗೊಳಿಸಿ"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"ಕೀಬೋರ್ಡ್ ಶಾರ್ಟ್ಕಟ್ಗಳು"</string> @@ -1432,6 +1432,8 @@ <string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"ಸಿಸ್ಟಂ ಆ್ಯಪ್ಗಳು"</string> <string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"ಮಲ್ಟಿಟಾಸ್ಕಿಂಗ್"</string> <string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"ಸ್ಪ್ಲಿಟ್ ಸ್ಕ್ರೀನ್"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <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> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"ಶಾರ್ಟ್ಕಟ್ಗಳನ್ನು ಕಸ್ಟಮೈಸ್ ಮಾಡಿ"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"ಶಾರ್ಟ್ಕಟ್ ಅನ್ನು ತೆಗೆದುಹಾಕಬೇಕೇ?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"ಡೀಫಾಲ್ಟ್ಗೆ ರೀಸೆಟ್ ಮಾಡಬೇಕೆ?"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"ಶಾರ್ಟ್ಕಟ್ ಅನ್ನು ನಿಯೋಜಿಸಲು ಕೀಯನ್ನು ಒತ್ತಿರಿ"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"ಈ ಶಾರ್ಟ್ಕಟ್ ಅನ್ನು ರಚಿಸಲು, ಆಕ್ಷನ್ ಕೀ ಮತ್ತು ಒಂದು ಅಥವಾ ಹೆಚ್ಚಿನ ಇತರ ಕೀಗಳನ್ನು ಒಟ್ಟಿಗೆ ಒತ್ತಿರಿ"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"ಇದು ನಿಮ್ಮ ಕಸ್ಟಮ್ ಶಾರ್ಟ್ಕಟ್ ಅನ್ನು ಶಾಶ್ವತವಾಗಿ ಅಳಿಸುತ್ತದೆ."</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"ಇದು ನಿಮ್ಮ ಎಲ್ಲಾ ಕಸ್ಟಮ್ ಶಾರ್ಟ್ಕಟ್ಗಳನ್ನು ಶಾಶ್ವತವಾಗಿ ಅಳಿಸುತ್ತದೆ."</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"ಹುಡುಕಾಟದ ಶಾರ್ಟ್ಕಟ್ಗಳು"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"ಹೌದು, ರೀಸೆಟ್ ಮಾಡಿ"</string> <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_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"ಕೀ ಸಂಯೋಜನೆಯು ಈಗಾಗಲೇ ಬಳಕೆಯಲ್ಲಿದೆ. ಮತ್ತೊಂದು ಕೀ ಬಳಸಿ ನೋಡಿ."</string> + <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="3325858369539848162">"ಕೀ ಸಂಯೋಜನೆಯು ಈಗಾಗಲೇ ಬಳಕೆಯಲ್ಲಿದೆ. ಮತ್ತೊಂದು ಸಂಯೋಜನೆಯನ್ನು ನೋಡಿ."</string> <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"ಶಾರ್ಟ್ಕಟ್ ಅನ್ನು ಸೆಟ್ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ."</string> <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string> <string name="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"ಶಾರ್ಟ್ಕಟ್ ಸೇರಿಸಿ"</string> @@ -1476,6 +1478,7 @@ <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> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"ಆ್ಯಪ್ಗಳನ್ನು ಬದಲಿಸಿ"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"ಮುಗಿದಿದೆ"</string> <string name="gesture_error_title" msgid="469064941635578511">"ಪುನಃ ಪ್ರಯತ್ನಿಸಿ!"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"ಹಿಂತಿರುಗಿ"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"ಭೇಷ್!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"ನೀವು ಇತ್ತೀಚಿನ ಆ್ಯಪ್ಗಳ ಜೆಸ್ಚರ್ ವೀಕ್ಷಣೆಯನ್ನು ಪೂರ್ಣಗೊಳಿಸಿದ್ದೀರಿ."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"ಇತ್ತೀಚಿನ ಆ್ಯಪ್ಗಳನ್ನು ವೀಕ್ಷಿಸಲು, ನಿಮ್ಮ ಟಚ್ಪ್ಯಾಡ್ನಲ್ಲಿ ಮೂರು ಬೆರಳುಗಳನ್ನು ಬಳಸಿ ಮೇಲಕ್ಕೆ ಸ್ವೈಪ್ ಮಾಡಿ ಮತ್ತು ಹೋಲ್ಡ್ ಮಾಡಿ"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"ಆ್ಯಪ್ಗಳನ್ನು ಬದಲಿಸಿ"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"ಭೇಷ್!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"ನೀವು ಆ್ಯಪ್ಗಳನ್ನು ಬದಲಾಯಿಸುವ ಗೆಸ್ಚರ್ ಅನ್ನು ಪೂರ್ಣಗೊಳಿಸಿದ್ದೀರಿ."</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"ಎಲ್ಲಾ ಆ್ಯಪ್ಗಳನ್ನು ವೀಕ್ಷಿಸಿ"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"ನಿಮ್ಮ ಕೀಬೋರ್ಡ್ನಲ್ಲಿ ಆ್ಯಕ್ಷನ್ ಕೀಯನ್ನು ಒತ್ತಿ"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"ಭೇಷ್!"</string> diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml index 732c9bc8620c..c087e5891229 100644 --- a/packages/SystemUI/res/values-ko/strings.xml +++ b/packages/SystemUI/res/values-ko/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"업데이트 중"</string> <string name="status_bar_work" msgid="5238641949837091056">"직장 프로필"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"비행기 모드"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"<xliff:g id="WHEN">%1$s</xliff:g>에 다음 알람을 들을 수 없습니다."</string> <string name="alarm_template" msgid="2234991538018805736">"시간: <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"일시: <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"위성, 연결 상태 양호"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"위성, 연결 가능"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"위성 긴급 SOS"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"긴급 전화 또는 SOS만 허용"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"신호가 없습니다"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"신호 막대가 1개입니다"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"뒤로"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"알림"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"단축키"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"키보드 레이아웃 전환"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"또는"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"검색어 삭제"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"단축키"</string> @@ -1432,6 +1432,8 @@ <string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"시스템 앱"</string> <string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"멀티태스킹"</string> <string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"화면 분할"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <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> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"단축키 맞춤설정"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"단축키를 삭제하시겠어요?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"기본값으로 재설정하시겠어요?"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"키를 눌러 단축키 지정"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"이 단축키를 만들려면 작업 키와 다른 키 하나 이상을 함께 누르세요"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"맞춤 단축키가 영구적으로 삭제됩니다."</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"모든 맞춤 단축키가 완전히 삭제됩니다."</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"단축키 검색"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"재설정"</string> <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_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"이미 사용 중인 키 조합입니다. 다른 키를 사용해 보세요."</string> + <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="3325858369539848162">"이미 사용 중인 키 조합입니다. 다른 조합을 사용해 보세요."</string> <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"단축키를 설정할 수 없습니다."</string> <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string> <string name="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"단축키 추가"</string> @@ -1476,6 +1478,7 @@ <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> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"앱 전환"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"완료"</string> <string name="gesture_error_title" msgid="469064941635578511">"다시 시도해 보세요"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"뒤로"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"아주 좋습니다"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"최근 앱 보기 동작을 완료했습니다."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"최근 앱을 보려면 터치패드에서 세 손가락을 사용해 위로 스와이프한 채로 유지하세요."</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"앱 전환"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"잘하셨습니다"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"앱 전환 동작을 완료했습니다."</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"모든 앱 보기"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"키보드의 작업 키를 누르세요."</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"잘하셨습니다"</string> diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml index eb5960fa23cd..5d1b604fa152 100644 --- a/packages/SystemUI/res/values-ky/strings.xml +++ b/packages/SystemUI/res/values-ky/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"Жаңырууда"</string> <string name="status_bar_work" msgid="5238641949837091056">"Жумуш профили"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Учак режими"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"<xliff:g id="WHEN">%1$s</xliff:g> боло турган кийинки эскертмени укпайсыз"</string> <string name="alarm_template" msgid="2234991538018805736">"<xliff:g id="WHEN">%1$s</xliff:g> болгондо"</string> <string name="alarm_template_far" msgid="3561752195856839456">"<xliff:g id="WHEN">%1$s</xliff:g> болгондо"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Спутник, байланыш жакшы"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Спутник, байланыш бар"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Спутник SOS"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"Шашылыш чалуулар же SOS гана"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"сигнал жок"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"бир мамыча"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"Артка"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"Билдирмелер"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"Ыкчам баскычтар"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"Баскычтоп калыбын которуштуруу"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"же"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"Изделген суроону тазалоо"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"Ыкчам баскычтар"</string> @@ -1432,6 +1432,8 @@ <string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"Системанын колдонмолору"</string> <string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Бир нече тапшырма аткаруу"</string> <string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Экранды бөлүү"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <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> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"Ыкчам баскычтарды тууралоо"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Ыкчам баскыч өчүрүлсүнбү?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"Баштапкы абалга келтиресизби?"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Ыкчам баскычты дайындоо үчүн баскычты басыңыз"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"Бул ыкчам баскычты түзүү үчүн Аракет баскычын жана бир же бир нече башка баскычты чогуу басыңыз"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Ушуну менен жеке ыкчам баскычыңыз биротоло өчүрүлөт."</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"Ушуну менен жеке ыкчам баскычтарыңыздын баары биротоло өчүрүлөт."</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Ыкчам баскычтарды издөө"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"Ооба, баштапкы абалга келтирилсин"</string> <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_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Ачкычтардын айкалышы колдонулууда. Башка ачкычты байкап көрүңүз."</string> + <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="3325858369539848162">"Ачкычтардын айкалышы колдонулууда. Башка айкалышты колдонуп көрүңүз."</string> <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Ыкчам баскычты коюу мүмкүн эмес."</string> <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string> <string name="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"Ыкчам баскыч кошуу"</string> @@ -1476,6 +1478,7 @@ <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> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"Колдонмолорду которуштуруу"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"Бүттү"</string> <string name="gesture_error_title" msgid="469064941635578511">"Кайталап көрүңүз!"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"Артка кайтуу"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"Азаматсыз!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"Акыркы колдонмолорду көрүү жаңсоосун аткардыңыз."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"Соңку колдонмолорду көрүү үчүн сенсордук тактаны үч манжаңыз менен жогору сүрүп, кармап туруңуз"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"Колдонмолорду которуштуруу"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"Азаматсыз!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"\"Башка колдонмого которулуу жаңсоосу боюнча үйрөткүчтү бүтүрдүңүз."</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"Бардык колдонмолорду көрүү"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"Баскычтобуңуздагы аракет баскычын басыңыз"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"Эң жакшы!"</string> diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml index 19ddc7aa5c9a..37713b32ac9d 100644 --- a/packages/SystemUI/res/values-lo/strings.xml +++ b/packages/SystemUI/res/values-lo/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"ກຳລັງອັບເດດ"</string> <string name="status_bar_work" msgid="5238641949837091056">"ໂປຣໄຟລ໌ບ່ອນເຮັດວຽກ"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"ໂໝດຢູ່ໃນຍົນ"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"ທ່ານຈະບໍ່ໄດ້ຍິນສຽງໂມງປ <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template" msgid="2234991538018805736">"ເວລາ <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"ວັນ <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"ດາວທຽມ, ການເຊື່ອມຕໍ່ດີ"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"ດາວທຽມ, ການເຊື່ອມຕໍ່ທີ່ພ້ອມນຳໃຊ້"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS ດາວທຽມ"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"ໂທສຸກເສີນ ຫຼື SOS ເທົ່ານັ້ນ"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"ບໍ່ມີສັນຍານ"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"1 ຂີດ"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"ກັບຄືນ"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"ການແຈ້ງເຕືອນ"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"ປຸ່ມລັດແປ້ນພິມ"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"ສະຫຼັບແປ້ນພິມ"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"ຫຼື"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"ລຶບລ້າງຄຳຊອກຫາ"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"ຄີລັດ"</string> @@ -1432,6 +1432,8 @@ <string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"ແອັບລະບົບ"</string> <string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"ການເຮັດຫຼາຍໜ້າວຽກພ້ອມກັນ"</string> <string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"ແບ່ງໜ້າຈໍ"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <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> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"ປັບແຕ່ງທາງລັດ"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"ລຶບທາງລັດອອກບໍ?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"ຣີເຊັດກັບຄືນເປັນຄ່າເລີ່ມຕົ້ນບໍ?"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"ກົດປຸ່ມເພື່ອກຳນົດທາງລັດ"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"ເພື່ອສ້າງທາງລັດນີ້, ໃຫ້ກົດປຸ່ມຄຳສັ່ງ ແລະ ໜຶ່ງ ຫຼື ຫຼາຍປຸ່ມອື່ນໆຮ່ວມກັນ"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"ການດຳເນີນການນີ້ຈະລຶບທາງລັດທີ່ກຳນົດເອງຂອງທ່ານຢ່າງຖາວອນ."</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"ການດຳເນີນການນີ້ຈະລຶບທາງລັດທີ່ກຳນົດເອງທັງໝົດຂອງທ່ານຢ່າງຖາວອນ."</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"ທາງລັດການຊອກຫາ"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"ແມ່ນ, ຣີເຊັດ"</string> <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_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"ນໍາໃຊ້ປຸ່ມປະສົມຢູ່ແລ້ວ. ໃຫ້ລອງປຸ່ມອື່ນ."</string> + <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="3325858369539848162">"ນໍາໃຊ້ປຸ່ມປະສົມຢູ່ແລ້ວ. ລອງໃຊ້ການປະສົມປະສານອື່ນໆ."</string> <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"ຕັ້ງທາງລັດບໍ່ໄດ້."</string> <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string> <string name="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"ເພີ່ມທາງລັດ"</string> @@ -1476,6 +1478,7 @@ <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> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"ສະຫຼັບແອັບ"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"ແລ້ວໆ"</string> <string name="gesture_error_title" msgid="469064941635578511">"ກະລຸນາລອງໃໝ່!"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"ກັບຄືນ"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"ດີຫຼາຍ!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"ທ່ານເບິ່ງທ່າທາງຂອງແອັບຫຼ້າສຸດສຳເລັດແລ້ວ."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"ເພື່ອເບິ່ງແອັບຫຼ້າສຸດ, ໃຫ້ປັດຂຶ້ນແລ້ວຄ້າງໄວ້ໂດຍໃຊ້ສາມນິ້ວເທິງແຜ່ນສຳຜັດຂອງທ່ານ"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"ສະຫຼັບແອັບ"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"ດີຫຼາຍ!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"ທ່ານເຮັດທ່າທາງສະຫຼັບແອັບສຳເລັດແລ້ວ."</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"ເບິ່ງແອັບທັງໝົດ"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"ກົດປຸ່ມຄຳສັ່ງຢູ່ແປ້ນພິມຂອງທ່ານ"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"ດີຫຼາຍ!"</string> diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml index 71ce30db0a02..4292574de55e 100644 --- a/packages/SystemUI/res/values-lt/strings.xml +++ b/packages/SystemUI/res/values-lt/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"Atnaujinama"</string> <string name="status_bar_work" msgid="5238641949837091056">"Darbo profilis"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Lėktuvo režimas"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"Negirdėsite kito signalo <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template" msgid="2234991538018805736">"<xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"<xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Palydovas, geras ryšys"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Palydovas, pasiekiamas ryšys"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Prisijungimas prie palydovo kritiniu atveju"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"Tik skambučiai pagalbos numeriu arba prisijungimas prie palydovo kritiniu atveju"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"„<xliff:g id="CARRIER_NAME">%1$s</xliff:g>“, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"nėra signalo"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"viena juosta"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"Atgal"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"Pranešimai"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"Spartieji klavišai"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"Perjungti klaviat. išdėstymą"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"arba"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"Išvalyti paieškos užklausą"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"Spartieji klavišai"</string> @@ -1432,6 +1432,8 @@ <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_split_screen" msgid="1159669813444812244">"Išskaidyto ekrano režimas"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <string name="shortcut_helper_category_input" msgid="8674018654124839566">"Įvestis"</string> <string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Programos spartieji klavišai"</string> <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Esama programa"</string> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"Sparčiųjų klavišų tinkinimas"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Pašalinti spartųjį klavišą?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"Iš naujo nustatyti numatytąjį nustatymą?"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Paspauskite klavišą, kad priskirtumėte spartųjį klavišą"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"Jei norite sukurti šį spartųjį klavišą, paspauskite veiksmų klavišą ir vieną ar daugiau kitų klavišų"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Bus visam laikui ištrintas tinkintas spartusis klavišas."</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"Bus visam laikui ištrinti visi tinkinti šaukiniai."</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Ieškoti sparčiųjų klavišų"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"Taip, nustatyti iš naujo"</string> <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_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Klavišų derinys jau naudojamas. Bandykite naudoti kitą klavišą."</string> + <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="3325858369539848162">"Klavišų derinys jau naudojamas. Bandykite naudoti kitą derinį."</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="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"Pridėti spartųjį klavišą"</string> @@ -1476,6 +1478,7 @@ <string name="touchpad_tutorial_back_gesture_button" msgid="3104716365403620315">"Grįžti"</string> <string name="touchpad_tutorial_home_gesture_button" msgid="8023973153559885624">"Eikite į pagrindinį puslapį"</string> <string name="touchpad_tutorial_recent_apps_gesture_button" msgid="8919227647650347359">"Peržiūrėti naujausias programas"</string> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"Perjungti programas"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"Atlikta"</string> <string name="gesture_error_title" msgid="469064941635578511">"Bandykite dar kartą!"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"Grįžti"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"Puiku!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"Atlikote naujausių programų peržiūros gestą."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"Peržiūrėkite naujausias programas, jutiklinėje dalyje perbraukę aukštyn trimis pirštais ir palaikę"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"Perjungti programas"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"Puiku!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"Atlikote programų perjungimo gestą."</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"Žr. visas programas"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"Paspauskite klaviatūros veiksmų klavišą"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"Puikiai padirbėta!"</string> diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml index 271c081ea3a9..cac42758f8bf 100644 --- a/packages/SystemUI/res/values-lv/strings.xml +++ b/packages/SystemUI/res/values-lv/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"Notiek atjaunināšana"</string> <string name="status_bar_work" msgid="5238641949837091056">"Darba profils"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Lidojuma režīms"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"Nākamais signāls (<xliff:g id="WHEN">%1$s</xliff:g>) netiks atskaņots."</string> <string name="alarm_template" msgid="2234991538018805736">"plkst. <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"<xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satelīts, labs savienojums"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satelīts, ir pieejams savienojums"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Satelīta SOS"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"Tikai ārkārtas izsaukumi vai SOS"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"nav signāla"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"viena josla"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"Atpakaļ"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"Paziņojumi"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"Īsinājumtaustiņi"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"Mainīt tastatūras izkārtojumu"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"vai"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"Notīrīt meklēšanas vaicājumu"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"Īsinājumtaustiņi"</string> @@ -1432,6 +1432,8 @@ <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_split_screen" msgid="1159669813444812244">"Ekrāna sadalīšana"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <string name="shortcut_helper_category_input" msgid="8674018654124839566">"Ievade"</string> <string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Lietotņu saīsnes"</string> <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Pašreizējā lietotne"</string> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"Īsinājumtaustiņu pielāgošana"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Vai noņemt saīsni?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"Vai atiestatīt noklusējuma vērtības?"</string> - <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_add_shortcut_description" msgid="7636040209946696120">"Lai izveidotu šo īsinājumtaustiņu, vienlaikus nospiediet darbību taustiņu un vienu vai vairākus citus taustiņus"</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> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"Veicot šo darbību, visas jūsu pielāgotās saīsnes tiks neatgriezeniski dzēstas."</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Meklēt saīsnes"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"Jā, atiestatīt"</string> <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_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_key_combination_in_use_error_message" msgid="3325858369539848162">"Taustiņu kombinācija jau tiek izmantota. Izmēģiniet citu kombināciju."</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="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"Pievienot saīsni"</string> @@ -1476,6 +1478,7 @@ <string name="touchpad_tutorial_back_gesture_button" msgid="3104716365403620315">"Atpakaļ"</string> <string name="touchpad_tutorial_home_gesture_button" msgid="8023973153559885624">"Pāriet uz sākuma ekrānu"</string> <string name="touchpad_tutorial_recent_apps_gesture_button" msgid="8919227647650347359">"Skatīt nesen izmantotās lietotnes"</string> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"Pārslēgties starp lietotnēm"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"Gatavs"</string> <string name="gesture_error_title" msgid="469064941635578511">"Mēģiniet vēlreiz."</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"Atpakaļ"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"Lieliski!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"Jūs sekmīgi veicāt nesen izmantoto lietotņu skatīšanas žestu."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"Lai skatītu nesenās lietotnes, skārienpaliktnī ar trīs pirkstiem velciet augšup un turiet"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"Pārslēgšanās starp lietotnēm"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"Lieliski!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"Jūs sekmīgi veicāt pārslēgšanās starp lietotnēm žestu."</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"Skatīt visas lietotnes"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"Tastatūrā nospiediet darbību taustiņu."</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"Lieliski!"</string> diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml index b478a5b38c4b..b53cec3636e3 100644 --- a/packages/SystemUI/res/values-mk/strings.xml +++ b/packages/SystemUI/res/values-mk/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"Се ажурира"</string> <string name="status_bar_work" msgid="5238641949837091056">"Работен профил"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Авионски режим"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"Нема да го слушнете следниот аларм <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template" msgid="2234991538018805736">"во <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"во <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Добра сателитска врска"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Достапна е сателитска врска"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Сателитски SOS"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"Само итни повици или SOS"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"нема сигнал"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"една цртичка"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"Назад"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"Известувања"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"Кратенки на тастатурата"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"Промени јазик на тастатура"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"или"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"Бришење поим за пребарување"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"Кратенки од тастатура"</string> @@ -1432,6 +1432,8 @@ <string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"Системски апликации"</string> <string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Мултитаскинг"</string> <string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Поделен екран"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <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> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"Приспособете ги кратенките"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Да се отстрани кратенката?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"Да се ресетира на стандардните поставки?"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Притиснете копче за да доделите кратенка"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"За да ја создадете кратенкава, притиснете го копчето за дејство и едно или повеќе други копчиња заедно"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Ова ќе ја избрише вашата приспособена кратенка трајно."</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"Ова ќе ги избрише трајно сите ваши приспособени кратенки."</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Пребарувајте кратенки"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"Да, ресетирај"</string> <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_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Комбинацијата на копчиња веќе се користи. Обидете се со друго копче."</string> + <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="3325858369539848162">"Комбинацијата на копчиња веќе се користи. Обидете се со друга комбинација."</string> <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Кратенката не може да се постави."</string> <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string> <string name="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"Додај кратенка"</string> @@ -1476,6 +1478,7 @@ <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> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"Сменете ги апликациите"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"Готово"</string> <string name="gesture_error_title" msgid="469064941635578511">"Обидете се повторно!"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"Назад"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"Одлично!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"Го завршивте движењето за прегледување на неодамнешните апликации."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"Повлечете нагоре со три прста на допирната подлога и задржете за да ги видите неодамнешните апликации"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"Сменете ги апликациите"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"Одлично!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"Го завршивте движењето за менување апликации."</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"Прегледајте ги сите апликации"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"Притиснете го копчето за дејство на тастатурата"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"Браво!"</string> diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml index 4885595c860b..48ec2c05e95b 100644 --- a/packages/SystemUI/res/values-ml/strings.xml +++ b/packages/SystemUI/res/values-ml/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"അപ്ഡേറ്റ് ചെയ്യുന്നു"</string> <string name="status_bar_work" msgid="5238641949837091056">"ഔദ്യോഗിക പ്രൊഫൈൽ"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"ഫ്ലൈറ്റ് മോഡ്"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"<xliff:g id="WHEN">%1$s</xliff:g>-നുള്ള നിങ്ങളുടെ അടുത്ത അലാറം കേൾക്കില്ല"</string> <string name="alarm_template" msgid="2234991538018805736">"<xliff:g id="WHEN">%1$s</xliff:g>-ന്"</string> <string name="alarm_template_far" msgid="3561752195856839456">"<xliff:g id="WHEN">%1$s</xliff:g>-ന്"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"സാറ്റലൈറ്റ്, മികച്ച കണക്ഷൻ"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"സാറ്റലൈറ്റ്, കണക്ഷൻ ലഭ്യമാണ്"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"സാറ്റലൈറ്റ് SOS"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"എമർജൻസി കോളുകൾ അല്ലെങ്കിൽ SOS മാത്രം"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"സിഗ്നൽ ഇല്ല"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"ഒരു ബാർ"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"മടങ്ങുക"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"അറിയിപ്പുകൾ"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"കീബോർഡ് കുറുക്കുവഴികൾ"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"കീബോർഡ് ലേഔട്ട് മാറുക"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"അല്ലെങ്കിൽ"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"തിരയൽ ചോദ്യം മായ്ക്കുക"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"കീബോർഡ് കുറുക്കുവഴികൾ"</string> @@ -1432,6 +1432,8 @@ <string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"സിസ്റ്റം ആപ്പുകൾ"</string> <string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"മൾട്ടിടാസ്കിംഗ്"</string> <string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"സ്ക്രീൻ വിഭജന മോഡ്"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <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> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"കുറുക്കുവഴികൾ ഇഷ്ടാനുസൃതമാക്കുക"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"കുറുക്കുവഴി നീക്കം ചെയ്യണോ?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"ഡിഫോൾട്ടിലേക്ക് തിരികെ റീസെറ്റ് ചെയ്യണോ?"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"കുറുക്കുവഴി അസൈൻ ചെയ്യാൻ കീ അമർത്തുക"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"ഈ കുറുക്കുവഴി സൃഷ്ടിക്കാൻ, ആക്ഷൻ കീയും ഒന്നോ അതിലധികമോ മറ്റ് കീകളും ഒരുമിച്ച് അമർത്തുക"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"ഇത് നിങ്ങളുടെ ഇഷ്ടാനുസൃത കുറുക്കുവഴി ശാശ്വതമായി ഇല്ലാതാക്കും."</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"നിങ്ങളുടെ എല്ലാ ഇഷ്ടാനുസൃത കുറുക്കുവഴികളും ശാശ്വതമായി ഇത് ഇല്ലാതാക്കും."</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"തിരയൽ കുറുക്കുവഴികൾ"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"ഉവ്വ്, റീസെറ്റ് ചെയ്യുക"</string> <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_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"കീ കോമ്പിനേഷൻ ഇതിനകം ഉപയോഗത്തിലുണ്ട്. മറ്റൊരു കീ പരീക്ഷിക്കുക."</string> + <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="3325858369539848162">"കീ കോമ്പിനേഷൻ ഇതിനകം ഉപയോഗത്തിലുണ്ട്. മറ്റൊരു കോമ്പിനേഷൻ ശ്രമിക്കുക."</string> <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"കുറുക്കുവഴി സജ്ജീകരിക്കാനാകില്ല."</string> <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string> <string name="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"കുറുക്കുവഴി ചേർക്കുക"</string> @@ -1476,6 +1478,7 @@ <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> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"ആപ്പുകൾ മാറുക"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"പൂർത്തിയായി"</string> <string name="gesture_error_title" msgid="469064941635578511">"വീണ്ടും ശ്രമിക്കുക!"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"മടങ്ങുക"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"കൊള്ളാം!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"അടുത്തിടെയുള്ള ആപ്പുകൾ കാണുക എന്ന ജെസ്ച്ചർ നിങ്ങൾ പൂർത്തിയാക്കി."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"സമീപകാല ആപ്പുകൾ കാണുന്നതിന്, നിങ്ങളുടെ ടച്ച്പാഡിൽ മൂന്ന് വിരലുകൾ ഉപയോഗിച്ച് മുകളിലേക്ക് സ്വൈപ്പ് ചെയ്ത് പിടിക്കുക"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"ആപ്പുകൾ മാറുക"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"കൊള്ളാം!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"ആപ്പുകൾ മാറൽ ജെസ്ച്ചർ നിങ്ങൾ പൂർത്തിയാക്കി."</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"എല്ലാ ആപ്പുകളും കാണുക"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"നിങ്ങളുടെ കീബോർഡിലെ ആക്ഷൻ കീ അമർത്തുക"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"അഭിനന്ദനങ്ങൾ!"</string> diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml index 52dff9e8afdd..6effa0a5234b 100644 --- a/packages/SystemUI/res/values-mn/strings.xml +++ b/packages/SystemUI/res/values-mn/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"Шинэчилж байна"</string> <string name="status_bar_work" msgid="5238641949837091056">"Ажлын профайл"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Нислэгийн горим"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"<xliff:g id="WHEN">%1$s</xliff:g>-т та дараагийн сэрүүлгээ сонсохгүй"</string> <string name="alarm_template" msgid="2234991538018805736">"<xliff:g id="WHEN">%1$s</xliff:g> цагт"</string> <string name="alarm_template_far" msgid="3561752195856839456">"<xliff:g id="WHEN">%1$s</xliff:g>-т"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Хиймэл дагуул, холболт сайн байна"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Хиймэл дагуул, холболт боломжтой"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Хиймэл дагуул SOS"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"Зөвхөн яаралтай дуудлага эсвэл SOS"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"дохио байхгүй"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"нэг шон"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"Буцах"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"Мэдэгдэл"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"Гарын товчлол"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"Гарын бүдүүвч рүү сэлгэх"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"эсвэл"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"Хайлтын асуулгыг арилгах"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"Товчлуурын шууд холбоос"</string> @@ -1432,6 +1432,8 @@ <string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"Системийн аппууд"</string> <string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Олон ажил зэрэг хийх"</string> <string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Дэлгэцийг хуваах"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <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> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"Товчлолыг өөрчлөх"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Товчлолыг хасах уу?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"Өгөгдмөл рүү буцааж шинэчлэх үү?"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Товчлол оноохын тулд товч дарна уу"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"Энэ товчлолыг үүсгэхийн тулд Тусгай товч болон өөр нэг эсвэл түүнээс олон товчийг хамтад нь дарна уу"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Энэ нь таны захиалгат товчлолыг бүрмөсөн устгана."</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"Энэ нь таны бүх захиалгат товчлолыг бүрмөсөн устгана."</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Товчлолууд хайх"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"Тэгье, шинэчилье"</string> <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_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Товчийн хослолыг аль хэдийн ашиглаж байна. Өөр товч туршиж үзнэ үү."</string> + <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="3325858369539848162">"Товчийн хослолыг аль хэдийн ашиглаж байна. Өөр хослол туршиж үзнэ үү."</string> <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Товчлол тохируулах боломжгүй."</string> <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string> <string name="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"Товчлол нэмэх"</string> @@ -1476,6 +1478,7 @@ <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> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"Апп сэлгэх"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"Болсон"</string> <string name="gesture_error_title" msgid="469064941635578511">"Дахин оролдоно уу!"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"Буцах"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"Сайн байна!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"Та саяхны аппуудыг харах зангааг гүйцэтгэсэн."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"Саяхны аппуудыг харахын тулд мэдрэгч самбар дээрээ гурван хуруугаараа дээш шудраад, удаан дарна уу"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"Апп сэлгэх"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"Үнэхээр сайн ажиллалаа!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"Та апп хооронд сэлгэх зангааг гүйцэтгэлээ."</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"Бүх аппыг харах"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"Гар дээрх тусгай товчлуурыг дарна уу"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"Сайн байна!"</string> diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml index 85ae835df9fe..77fd538ed398 100644 --- a/packages/SystemUI/res/values-mr/strings.xml +++ b/packages/SystemUI/res/values-mr/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"अपडेट करत आहे"</string> <string name="status_bar_work" msgid="5238641949837091056">"कार्य प्रोफाईल"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"विमान मोड"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"तुम्ही तुमचा <xliff:g id="WHEN">%1$s</xliff:g> वाजता होणारा पुढील अलार्म ऐकणार नाही"</string> <string name="alarm_template" msgid="2234991538018805736">"<xliff:g id="WHEN">%1$s</xliff:g> वाजता"</string> <string name="alarm_template_far" msgid="3561752195856839456">"<xliff:g id="WHEN">%1$s</xliff:g> रोजी"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"सॅटेलाइट, चांगले कनेक्शन"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"सॅटेलाइट, कनेक्शन उपलब्ध"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"सॅटेलाइट SOS"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"फक्त आणीबाणी कॉल किंवा SOS"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"कोणताही सिग्नल नाही"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"एक बार"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"परत"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"सूचना"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"कीबोर्ड शॉर्टकट"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"कीबोर्ड लेआउट स्विच करा"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"किंवा"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"शोध क्वेरी साफ करा"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"कीबोर्ड शॉर्टकट"</string> @@ -1432,6 +1432,8 @@ <string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"सिस्टीम अॅप्स"</string> <string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"मल्टिटास्किंग"</string> <string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"स्प्लिट स्क्रीन"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <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> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"शॉर्टकट कस्टमाइझ करा"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"शॉर्टकट काढून टाकायचा आहे का?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"डीफॉल्टवर पुन्हा रीसेट करायचे आहे का?"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"शॉर्टकट असाइन करण्यासाठी की प्रेस करा"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"हा शॉर्टकट तयार करण्यासाठी, ॲक्शन की आणि एक किंवा त्याहून अधिक की एकत्र प्रेस करा"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"यामुळे तुमचा कस्टम शॉर्टकट कायमचा हटवला जाईल."</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"हे तुमचे सर्व कस्टम शॉर्टकट कायमचे हटवेल."</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"शोधण्यासाठी शॉर्टकट"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"होय, रीसेट करा"</string> <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_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"की कॉम्बिनेशन आधीपासून वापरले जात आहे. दुसरी की वापरून पहा."</string> + <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="3325858369539848162">"की कॉम्बिनेशन आधीपासून वापरले जात आहे. दुसरे कॉम्बिनेशन वापरून पहा."</string> <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"शॉर्टकट सेट करू शकत नाही."</string> <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string> <string name="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"शॉर्टकट जोडा"</string> @@ -1476,6 +1478,7 @@ <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> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"अॅप्स स्विच करा"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"पूर्ण झाले"</string> <string name="gesture_error_title" msgid="469064941635578511">"पुन्हा प्रयत्न करा!"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"मागे जा"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"उत्तम कामगिरी!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"तुम्ही अलीकडील ॲप्स पाहण्याचे जेश्चर पूर्ण केले आहे."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"अलीकडील अॅप्स पाहण्यासाठी, तुमच्या टचपॅडवर तीन बोटांनी वर स्वाइप करून धरून ठेवा"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"अॅप्स स्विच करा"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"उत्तम कामगिरी!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"तुम्ही अॅप्स स्विच करणे जेश्चर पूर्ण केले आहे."</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"सर्व अॅप्स पहा"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"तुमच्या कीबोर्डवर अॅक्शन की प्रेस करा"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"खूप छान!"</string> diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml index b5f0d147f972..7aa95f09404d 100644 --- a/packages/SystemUI/res/values-ms/strings.xml +++ b/packages/SystemUI/res/values-ms/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"Mengemaskinikan"</string> <string name="status_bar_work" msgid="5238641949837091056">"Profil kerja"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Mod pesawat"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"Anda tidak akan mendengar penggera yang seterusnya <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template" msgid="2234991538018805736">"pada <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"pada <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satelit, sambungan yang baik"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satelit, sambungan tersedia"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS via Satelit"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"Panggilan kecemasan atau SOS sahaja"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"tiada isyarat"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"satu bar"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"Kembali"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"Pemberitahuan"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"Pintasan Papan Kekunci"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"Tukar reka letak papan kekunci"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"atau"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"Kosongkan pertanyaan carian"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"Pintasan Papan Kekunci"</string> @@ -1432,6 +1432,8 @@ <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_split_screen" msgid="1159669813444812244">"Skrin pisah"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <string name="shortcut_helper_category_input" msgid="8674018654124839566">"Input"</string> <string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Pintasan apl"</string> <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Apl Semasa"</string> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"Sesuaikan pintasan"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Alih keluar pintasan?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"Tetapkan kembali kepada lalai?"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Tekan kekunci untuk menetapkan pintasan"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"Untuk membuat pintasan ini, tekan kekunci Tindakan dan sekurang-kurangnya satu kekunci lain bersama"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Tindakan ini akan memadamkan pintasan tersuai anda secara kekal."</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"Tindakan ini akan memadamkan semua pintasan tersuai anda secara kekal."</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Pintasan carian"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"Ya, tetapkan semula"</string> <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_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Gabungan kekunci sudah digunakan. Cuba kekunci lain."</string> + <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="3325858369539848162">"Gabungan kekunci sudah digunakan. Cuba kombinasi 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="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"Tambahkan pintasan"</string> @@ -1476,6 +1478,7 @@ <string name="touchpad_tutorial_back_gesture_button" msgid="3104716365403620315">"Kembali"</string> <string name="touchpad_tutorial_home_gesture_button" msgid="8023973153559885624">"Akses laman utama"</string> <string name="touchpad_tutorial_recent_apps_gesture_button" msgid="8919227647650347359">"Lihat apl terbaharu"</string> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"Tukar apl"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"Selesai"</string> <string name="gesture_error_title" msgid="469064941635578511">"Cuba lagi!"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"Kembali"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"Syabas!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"Anda telah melengkapkan gerak isyarat lihat apl terbaharu."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"Untuk melihat apl terbaharu, leret ke atas dan tahan menggunakan tiga jari pada pad sentuh anda"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"Tukar apl"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"Bagus!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"Anda telah melengkapkan gerak isyarat menukar apl."</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"Lihat semua apl"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"Tekan kekunci tindakan pada papan kekunci anda"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"Syabas!"</string> diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml index d24c4a0b1631..d973d4066774 100644 --- a/packages/SystemUI/res/values-my/strings.xml +++ b/packages/SystemUI/res/values-my/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"အပ်ဒိတ်လုပ်နေသည်"</string> <string name="status_bar_work" msgid="5238641949837091056">"အလုပ် ပရိုဖိုင်"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"လေယာဉ်ပျံမုဒ်"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"<xliff:g id="WHEN">%1$s</xliff:g> ၌သင့်နောက်ထပ် နှိုးစက်ကို ကြားမည်မဟုတ်ပါ"</string> <string name="alarm_template" msgid="2234991538018805736">"<xliff:g id="WHEN">%1$s</xliff:g> ၌"</string> <string name="alarm_template_far" msgid="3561752195856839456">"<xliff:g id="WHEN">%1$s</xliff:g> တွင်"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"ဂြိုဟ်တု၊ ချိတ်ဆက်မှု ကောင်းသည်"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"ဂြိုဟ်တု၊ ချိတ်ဆက်မှု ရနိုင်သည်"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Satellite SOS"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"အရေးပေါ်ဖုန်းခေါ်ခြင်း (သို့) SOS သီးသန့်"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>၊ <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>။"</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"လိုင်းမရှိပါ"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"တစ်ဘား"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"နောက်သို့"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"အကြောင်းကြားချက်များ"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"ကီးဘုတ် ဖြတ်လမ်းများ"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"ကီးဘုတ်အပြင်အဆင် ပြောင်းခြင်း"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"သို့မဟုတ်"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"ရှာဖွေစာလုံး ရှင်းထုတ်ရန်"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"လက်ကွက်ဖြတ်လမ်းများ"</string> @@ -1432,6 +1432,8 @@ <string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"စနစ် အက်ပ်များ"</string> <string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"တစ်ပြိုင်နက် များစွာလုပ်ခြင်း"</string> <string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"မျက်နှာပြင် ခွဲ၍ပြသခြင်း"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <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> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"ဖြတ်လမ်းများ စိတ်ကြိုက်ပြင်ဆင်ခြင်း"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"ဖြတ်လမ်းလင့်ခ် ဖယ်ရှားမလား။"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"မူရင်းသို့ ပြန်လည်ပြင်ဆင်သတ်မှတ်မလား။"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"ဖြတ်လမ်းလင့်ခ်သတ်မှတ်ရန် ကီးကို နှိပ်ပါ"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"ဤဖြတ်လမ်းလင့်ခ် ပြုလုပ်ရန်အတွက် ‘လုပ်ဆောင်ချက်ကီး’ နှင့် တစ်ခု (သို့) တစ်ခုထက်ပိုသော အခြားကီးကို အတူနှိပ်ပါ"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"၎င်းသည် သင့်စိတ်ကြိုက် ဖြတ်လမ်းလင့်ခ်ကို အပြီးဖျက်ပါမည်။"</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"၎င်းသည် သင့်စိတ်ကြိုက်ဖြတ်လမ်းလင့်ခ်အားလုံးကို အပြီးဖျက်မည်။"</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"ရှာဖွေစာလုံး ဖြတ်လမ်း"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"ပြင်ဆင်ရန်"</string> <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_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"ကီးပေါင်းစပ်ခြင်းကို သုံးနေပြီးဖြစ်သည်။ အခြားကီးကို စမ်းကြည့်ပါ။"</string> + <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="3325858369539848162">"ကီးပေါင်းစပ်ခြင်းကို သုံးနေပြီးဖြစ်သည်။ အခြားပေါင်းစပ်မှုတစ်ခု စမ်းကြည့်ပါ။"</string> <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"ဖြတ်လမ်းလင့်ခ် သတ်မှတ်၍မရပါ။"</string> <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string> <string name="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"ဖြတ်လမ်းလင့်ခ် ထည့်ရန်"</string> @@ -1476,6 +1478,7 @@ <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> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"အက်ပ်များကူးပြောင်းခြင်း"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"ပြီးပြီ"</string> <string name="gesture_error_title" msgid="469064941635578511">"ထပ်စမ်းကြည့်ပါ။"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"ပြန်သွားရန်"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"တော်ပါပေသည်။"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"မကြာသေးမီကအက်ပ်များကို ကြည့်ခြင်းလက်ဟန် သင်ခန်းစာပြီးပါပြီ။"</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"လတ်တလောအက်ပ်များကြည့်ရန် တာ့ချ်ပက်တွင် လက်သုံးချောင်းဖြင့် အပေါ်သို့ပွတ်ဆွဲပြီး ဖိထားပါ"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"အက်ပ်များကူးပြောင်းခြင်း"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"တော်ပါပေသည်။"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"အက်ပ်ပြောင်းလက်ဟန် လုပ်ပြီးပါပြီ။"</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"အက်ပ်အားလုံးကို ကြည့်ခြင်း"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"ကီးဘုတ်တွင် လုပ်ဆောင်ချက်ကီး နှိပ်ပါ"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"အလွန်ကောင်းပါသည်။"</string> diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml index 871ffe921f89..0d9e2760614c 100644 --- a/packages/SystemUI/res/values-nb/strings.xml +++ b/packages/SystemUI/res/values-nb/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"Oppdaterer"</string> <string name="status_bar_work" msgid="5238641949837091056">"Work-profil"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Flymodus"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"Du hører ikke neste innstilte alarm <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template" msgid="2234991538018805736">"kl. <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"<xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satellitt – god tilkobling"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satellitt – tilkobling tilgjengelig"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS-alarm via satellitt"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"Bare nødanrop eller SOS"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"ikke noe signal"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"én strek"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"Tilbake"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"Varsler"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"Hurtigtaster"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"Bytt tastaturoppsett"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"eller"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"Fjern søket"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"Hurtigtaster"</string> @@ -1432,6 +1432,8 @@ <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_split_screen" msgid="1159669813444812244">"Delt skjerm"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <string name="shortcut_helper_category_input" msgid="8674018654124839566">"Inndata"</string> <string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"App-snarveier"</string> <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Aktiv app"</string> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"Tilpass snarveier"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Vil du fjerne hurtigtasten?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"Vil du tilbakestille til standard?"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Trykk på en tast for å tilordne hurtigtasten"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"For å opprette denne snarveien, trykk på handlingstasten og én eller flere andre taster samtidig"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Dette fører til at den egendefinerte hurtigtasten slettes permanent."</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"Dette fører til at alle de egendefinerte snarveiene dine slettes permanent."</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Søk etter snarveier"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"Tilbakestill"</string> <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_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Tastekombinasjonen brukes allerede. Prøv en annen tast."</string> + <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="3325858369539848162">"Tastekombinasjonen brukes allerede. Prøv en annen kombinasjon."</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="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"Legg til snarvei"</string> @@ -1476,6 +1478,7 @@ <string name="touchpad_tutorial_back_gesture_button" msgid="3104716365403620315">"Gå tilbake"</string> <string name="touchpad_tutorial_home_gesture_button" msgid="8023973153559885624">"Gå til startsiden"</string> <string name="touchpad_tutorial_recent_apps_gesture_button" msgid="8919227647650347359">"Se nylige apper"</string> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"Bytt app"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"Ferdig"</string> <string name="gesture_error_title" msgid="469064941635578511">"Prøv på nytt."</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"Gå tilbake"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"Bra jobbet!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"Du har fullført bevegelsen for å se nylige apper."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"For å se nylige apper, sveip opp og hold med tre fingre på styreflaten"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"Bytt app"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"Bra jobbet!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"Du har fullført bytt-app-bevegelsen."</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"Se alle apper"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"Trykk på handlingstasten på tastaturet"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"Bra!"</string> diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml index 58307c49ada7..b1911596b2a1 100644 --- a/packages/SystemUI/res/values-ne/strings.xml +++ b/packages/SystemUI/res/values-ne/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"अपडेट गरिँदै छ"</string> <string name="status_bar_work" msgid="5238641949837091056">"कार्य प्रोफाइल"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"हवाइजहाज मोड"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"तपाईँले आफ्नो अर्को अलार्म <xliff:g id="WHEN">%1$s</xliff:g> सुन्नुहुने छैन"</string> <string name="alarm_template" msgid="2234991538018805736">"<xliff:g id="WHEN">%1$s</xliff:g> मा"</string> <string name="alarm_template_far" msgid="3561752195856839456">"<xliff:g id="WHEN">%1$s</xliff:g> मा"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"स्याटलाइट, राम्रो कनेक्सन"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"स्याटलाइट, कनेक्सन उपलब्ध छ"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"स्याटलाइट SOS"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"आपत्कालीन कल वा SOS मात्र"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>।"</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"सिग्नल छैन"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"एउटा बार"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"पछाडि"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"सूचनाहरू"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"किबोर्ड सर्टकटहरू"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"किबोर्डको लेआउट बदल्नुहोस्"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"वा"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"किवर्ड हटाउनुहोस्"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"किबोर्डका सर्टकटहरू"</string> @@ -1432,6 +1432,8 @@ <string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"सिस्टम एपहरू"</string> <string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"मल्टिटास्किङ"</string> <string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"स्प्लिट स्क्रिन"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <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> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"सर्टकटहरू कस्टमाइज गर्नुहोस्"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"सर्टकट हटाउने हो?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"सर्टकट रिसेट गरी डिफल्ट बनाउने हो?"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"सर्टकट असाइन गर्न की थिच्नुहोस्"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"यो सर्टकट बनाउन एक्सन की र एउटा वा सोभन्दा बढी की एकैचोटि थिच्नुहोस्"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"यसो गर्नुभयो भने तपाईंको कस्टम सर्टकट सदाका लागि मेटिने छ।"</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"तपाईंले यसो गर्नुभयो भने तपाईंका सबै कस्टम सर्टकटहरू सदाका लागि मेटाइने छन्।"</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"सर्टकटहरू खोज्नुहोस्"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"अँ, रिसेट गर्नुहोस्"</string> <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_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"यो की कम्बिनेसन प्रयोग गरिसकिएको छ। अर्कै की प्रयोग गरी हेर्नुहोस्।"</string> + <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="3325858369539848162">"यो की कम्बिनेसन प्रयोग गरिसकिएको छ। अर्कै कम्बिनेसन असाइन गरी हेर्नुहोस्।"</string> <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"सर्टकट सेट गर्न सकिएन।"</string> <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string> <string name="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"सर्टकट हाल्नुहोस्"</string> @@ -1476,6 +1478,7 @@ <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> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"एपहरू बदल्नुहोस्"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"सम्पन्न भयो"</string> <string name="gesture_error_title" msgid="469064941635578511">"फेरि प्रयास गर्नुहोस्!"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"पछाडि जानुहोस्"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"अद्भुत!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"तपाईंले जेस्चर प्रयोग गरी हालसालै चलाइएका एपहरू हेर्ने तरिका सिक्नुभएको छ।"</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"हालसालैका एपहरू हेर्न तीन औँला प्रयोग गरी टचप्याडमा माथितिर स्वाइप गर्नुहोस् र होल्ड गर्नुहोस्"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"एपहरू बदल्नुहोस्"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"अद्भुत!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"तपाईंले एपहरू बदल्ने जेस्चर प्रयोग गर्ने तरिका सिक्नुभयो।"</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"सबै एपहरू हेर्नुहोस्"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"आफ्नो किबोर्डमा भएको एक्सन की थिच्नुहोस्"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"स्याबास!"</string> diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml index a77f5e4629c1..326be99b8a65 100644 --- a/packages/SystemUI/res/values-night/colors.xml +++ b/packages/SystemUI/res/values-night/colors.xml @@ -108,8 +108,14 @@ <color name="people_tile_background">@color/material_dynamic_secondary20</color> - <!-- Dark Theme colors for notification shade/scrim --> - <color name="shade_panel">@android:color/system_accent1_900</color> + <!-- Dark theme base colors for notification shade/scrim, the alpha component is adjusted + programmatically to match the spec --> + <color name="shade_panel">@android:color/system_accent1_800</color> + <color name="surface_effect_0">@android:color/system_accent1_800</color> + + <!-- todo(b/388891904) Remove updated color references once they are available. --> + <color name="shade_panel_base">@color/shade_panel</color> + <color name="notification_scrim_base">@color/surface_effect_0</color> <!-- Keyboard shortcut helper dialog --> <color name="ksh_key_item_color">@*android:color/system_on_surface_variant_dark</color> diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml index 6e8d328c1896..a3d8856625f4 100644 --- a/packages/SystemUI/res/values-nl/strings.xml +++ b/packages/SystemUI/res/values-nl/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"Updaten"</string> <string name="status_bar_work" msgid="5238641949837091056">"Werkprofiel"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Vliegtuigmodus"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"Je hoort je volgende wekker niet <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template" msgid="2234991538018805736">"om <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"op <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satelliet, goede verbinding"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satelliet, verbinding beschikbaar"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS via satelliet"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"Alleen noodoproepen of SOS"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"geen signaal"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"1 streepje"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"Terug"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"Meldingen"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"Sneltoetsen"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"Toetsenbordindeling wisselen"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"of"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"Zoekopdracht wissen"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"Sneltoetsen"</string> @@ -1432,6 +1432,8 @@ <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_split_screen" msgid="1159669813444812244">"Gesplitst scherm"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <string name="shortcut_helper_category_input" msgid="8674018654124839566">"Invoer"</string> <string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"App-sneltoetsen"</string> <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Huidige app"</string> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"Snelkoppelingen aanpassen"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Sneltoets verwijderen?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"Resetten naar standaard?"</string> - <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_add_shortcut_description" msgid="7636040209946696120">"Als je deze sneltoets wilt maken, druk je tegelijkertijd op de actietoets en een of meer andere toetsen"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Hiermee wordt je aangepaste sneltoets definitief verwijderd."</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"Hiermee worden al je aangepaste snelkoppelingen definitief verwijderd."</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Sneltoetsen zoeken"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"Ja, resetten"</string> <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_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Toetsencombinatie is al in gebruik. Probeer een andere toets."</string> + <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="3325858369539848162">"Toetsencombinatie is al in gebruik. Probeer een andere combinatie."</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="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"Snelkoppeling toevoegen"</string> @@ -1476,6 +1478,7 @@ <string name="touchpad_tutorial_back_gesture_button" msgid="3104716365403620315">"Terug"</string> <string name="touchpad_tutorial_home_gesture_button" msgid="8023973153559885624">"Naar startscherm"</string> <string name="touchpad_tutorial_recent_apps_gesture_button" msgid="8919227647650347359">"Recente apps bekijken"</string> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"Wisselen tussen apps"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"Klaar"</string> <string name="gesture_error_title" msgid="469064941635578511">"Probeer het nog eens."</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"Terug"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"Goed gedaan!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"Je weet nu hoe je het gebaar Recente apps bekijken maakt."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"Als je recente apps wilt bekijken, swipe je met 3 vingers omhoog op de touchpad en houd je vast"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"Wisselen tussen apps"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"Goed werk!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"Je weet nu hoe je het gebaar om van app te wisselen maakt."</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"Alle apps bekijken"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"Druk op de actietoets op het toetsenbord"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"Goed gedaan!"</string> diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml index 9986c11b49fd..fa89a4858cdc 100644 --- a/packages/SystemUI/res/values-or/strings.xml +++ b/packages/SystemUI/res/values-or/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"ଅପଡେଟ ହେଉଛି"</string> <string name="status_bar_work" msgid="5238641949837091056">"ୱର୍କ ପ୍ରୋଫାଇଲ୍"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"ଏୟାରପ୍ଲେନ ମୋଡ"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"<xliff:g id="WHEN">%1$s</xliff:g>ବେଳେ ଆପଣ ନିଜର ପରବର୍ତ୍ତୀ ଆଲାର୍ମ ଶୁଣିପାରିବେ ନାହିଁ"</string> <string name="alarm_template" msgid="2234991538018805736">"<xliff:g id="WHEN">%1$s</xliff:g> ହେଲେ"</string> <string name="alarm_template_far" msgid="3561752195856839456">"<xliff:g id="WHEN">%1$s</xliff:g> ବେଳେ"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"ସାଟେଲାଇଟ, ଭଲ କନେକ୍ସନ"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"ସାଟେଲାଇଟ, କନେକ୍ସନ ଉପଲବ୍ଧ"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"ସେଟେଲାଇଟ SOS"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"କେବଳ ଜରୁରୀକାଳୀନ କଲ କିମ୍ବା SOS"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>।"</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"କୌଣସି ସିଗନାଲ ନାହିଁ"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"ଗୋଟିଏ ବାର"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"ଫେରନ୍ତୁ"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"ବିଜ୍ଞପ୍ତି"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"କୀବୋର୍ଡ ସର୍ଟକଟ"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"କୀ\'ବୋର୍ଡ୍ର ଲେଆଉଟ୍କୁ ବଦଳାନ୍ତୁ"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"କିମ୍ବା"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"ସର୍ଚ୍ଚ କ୍ୱେରୀକୁ ଖାଲି କରନ୍ତୁ"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"କୀବୋର୍ଡ ସର୍ଟକଟ"</string> @@ -1432,6 +1432,8 @@ <string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"ସିଷ୍ଟମ ଆପ୍ସ"</string> <string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"ମଲ୍ଟିଟାସ୍କିଂ"</string> <string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"ସ୍ପ୍ଲିଟ ସ୍କ୍ରିନ"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <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> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"ସର୍ଟକଟଗୁଡ଼ିକୁ କଷ୍ଟମାଇଜ କରନ୍ତୁ"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"ସର୍ଟକଟକୁ କାଢ଼ି ଦେବେ?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"ଡିଫଲ୍ଟରେ ପୁଣି ରିସେଟ କରିବେ?"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"ସର୍ଟକଟ ଆସାଇନ କରିବା ପାଇଁ କୀ\'କୁ ଦବାନ୍ତୁ"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"ଏହି ସର୍ଟକଟ ତିଆରି କରିବାକୁ ଏକାଠି ଆକ୍ସନ କୀ କିମ୍ବା ଗୋଟିଏ ବା ଅଧିକ ଅନ୍ୟ କୀ ଦବାନ୍ତୁ"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"ଏହା ଆପଣଙ୍କ କଷ୍ଟମ ସର୍ଟକଟକୁ ସ୍ଥାୟୀ ଭାବେ ଡିଲିଟ କରିଦେବ।"</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"ଏହା ଆପଣଙ୍କର ସମସ୍ତ କଷ୍ଟମ ସର୍ଟକଟକୁ ସ୍ଥାୟୀ ଭାବେ ଡିଲିଟ କରିଦେବ।"</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"ସର୍ଚ୍ଚ ସର୍ଟକଟ"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"ହଁ, ରିସେଟ କରନ୍ତୁ"</string> <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_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"କୀ କମ୍ବିନେସନ ପୂର୍ବରୁ ବ୍ୟବହାର କରାଯାଉଛି। ଅନ୍ୟ ଏକ କୀ ବ୍ୟବହାର କରି ଦେଖନ୍ତୁ।"</string> + <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="3325858369539848162">"କୀ କମ୍ବିନେସନ ପୂର୍ବରୁ ବ୍ୟବହାର କରାଯାଉଛି। ଅନ୍ୟ ଏକ କମ୍ବିନେସନ ବ୍ୟବହାର କରି ଦେଖନ୍ତୁ।"</string> <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"ସର୍ଟକଟ ସେଟ କରାଯାଇପାରିବ ନାହିଁ।"</string> <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string> <string name="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"ସର୍ଟକଟ ଯୋଗ କରନ୍ତୁ"</string> @@ -1476,6 +1478,7 @@ <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> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"ଆପ୍ସକୁ ସୁଇଚ କରନ୍ତୁ"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"ହୋଇଗଲା"</string> <string name="gesture_error_title" msgid="469064941635578511">"ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ!"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"ପଛକୁ ଫେରନ୍ତୁ"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"ବଢ଼ିଆ କାମ!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"ଆପଣ ବର୍ତ୍ତମାନର ଆପ୍ସ ଜେଶ୍ଚରକୁ ଭ୍ୟୁ କରିବା ସମ୍ପୂର୍ଣ୍ଣ କରିଛନ୍ତି।"</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"ବର୍ତ୍ତମାନର ଆପ୍ସ ଭ୍ୟୁ କରିବାକୁ, ଆପଣଙ୍କ ଟଚପେଡରେ ତିନୋଟି ଆଙ୍ଗୁଠି ବ୍ୟବହାର କରି ଉପରକୁ ସ୍ୱାଇପ କରି ଧରି ରଖନ୍ତୁ"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"ଆପ୍ସକୁ ସୁଇଚ କରନ୍ତୁ"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"ବଢ଼ିଆ କାମ!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"ଆପଣ ସୁଇଚ ଆପ୍ସ ଜେଶ୍ଚର ସମ୍ପୂର୍ଣ୍ଣ କରିଛନ୍ତି।"</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"ସବୁ ଆପ ଭ୍ୟୁ କରନ୍ତୁ"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"ଆପଣଙ୍କର କୀବୋର୍ଡରେ ଆକ୍ସନ କୀ\'କୁ ଦବାନ୍ତୁ"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"ବହୁତ ବଢ଼ିଆ!"</string> diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml index 45888847bc2e..a0c06122b7a4 100644 --- a/packages/SystemUI/res/values-pa/strings.xml +++ b/packages/SystemUI/res/values-pa/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"ਅੱਪਡੇਟ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ"</string> <string name="status_bar_work" msgid="5238641949837091056">"ਕਾਰਜ ਪ੍ਰੋਫਾਈਲ"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"ਹਵਾਈ-ਜਹਾਜ਼ ਮੋਡ"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"ਤੁਸੀਂ <xliff:g id="WHEN">%1$s</xliff:g> ਵਜੇ ਆਪਣਾ ਅਗਲਾ ਅਲਾਰਮ ਨਹੀਂ ਸੁਣੋਗੇ"</string> <string name="alarm_template" msgid="2234991538018805736">"<xliff:g id="WHEN">%1$s</xliff:g> ਵਜੇ"</string> <string name="alarm_template_far" msgid="3561752195856839456">"<xliff:g id="WHEN">%1$s</xliff:g> ਵਜੇ"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"ਸੈਟੇਲਾਈਟ, ਕਨੈਕਸ਼ਨ ਵਧੀਆ ਹੈ"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"ਸੈਟੇਲਾਈਟ, ਕਨੈਕਸ਼ਨ ਉਪਲਬਧ ਹੈ"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"ਸੈਟੇਲਾਈਟ SOS"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"ਸਿਰਫ਼ ਐਮਰਜੈਂਸੀ ਕਾਲਾਂ ਜਾਂ ਸਹਾਇਤਾ"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"ਕੋਈ ਸਿਗਨਲ ਨਹੀਂ"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"ਇੱਕ ਸਿਗਨਲ ਪੱਟੀ"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"ਪਿੱਛੇ"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"ਸੂਚਨਾਵਾਂ"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"ਕੀ-ਬੋਰਡ ਸ਼ਾਰਟਕੱਟ"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"ਕੀ-ਬੋਰਡ ਖਾਕਾ ਬਦਲੋ"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"ਜਾਂ"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"ਖੋਜ ਪੁੱਛਗਿੱਛ ਕਲੀਅਰ ਕਰੋ"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"ਕੀ-ਬੋਰਡ ਸ਼ਾਰਟਕੱਟ"</string> @@ -1432,6 +1432,8 @@ <string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"ਸਿਸਟਮ ਐਪਾਂ"</string> <string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"ਮਲਟੀਟਾਸਕਿੰਗ"</string> <string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"ਸਪਲਿਟ ਸਕ੍ਰੀਨ"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <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> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"ਸ਼ਾਰਟਕੱਟਾਂ ਨੂੰ ਵਿਉਂਤਬੱਧ ਕਰੋ"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"ਕੀ ਸ਼ਾਰਟਕੱਟ ਨੂੰ ਹਟਾਉਣਾ ਹੈ?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"ਕੀ ਵਾਪਸ ਪੂਰਵ-ਨਿਰਧਾਰਿਤ \'ਤੇ ਰੀਸੈੱਟ ਕਰਨਾ ਹੈ?"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"ਸ਼ਾਰਟਕੱਟ ਨਿਰਧਾਰਿਤ ਕਰਨ ਲਈ ਕੁੰਜੀ ਦਬਾਓ"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"ਇਸ ਸ਼ਾਰਟਕੱਟ ਨੂੰ ਬਣਾਉਣ ਲਈ, ਕਾਰਵਾਈ ਕੁੰਜੀ ਅਤੇ ਇੱਕ ਜਾਂ ਇੱਕ ਤੋਂ ਵੱਧ ਕੁੰਜੀਆਂ ਨੂੰ ਇਕੱਠੇ ਦਬਾਓ"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"ਇਸ ਨਾਲ ਤੁਹਾਡੇ ਵਿਉਂਤੇ ਸ਼ਾਰਟਕੱਟ ਨੂੰ ਪੱਕੇ ਤੌਰ \'ਤੇ ਮਿਟਾ ਦਿੱਤਾ ਜਾਵੇਗਾ।"</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"ਇਸ ਨਾਲ ਤੁਹਾਡੇ ਸਾਰੇ ਵਿਉਂਤਬੱਧ ਸ਼ਾਰਟਕੱਟ ਪੱਕੇ ਤੌਰ \'ਤੇ ਮਿਟ ਜਾਣਗੇ।"</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"ਸ਼ਾਰਟਕੱਟ ਖੋਜੋ"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"ਹਾਂ, ਰੀਸੈੱਟ ਕਰੋ"</string> <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_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"ਕੁੰਜੀ ਸੁਮੇਲ ਪਹਿਲਾਂ ਹੀ ਵਰਤੋਂ ਵਿੱਚ ਹੈ। ਕੋਈ ਹੋਰ ਕੁੰਜੀ ਵਰਤ ਕੇ ਦੇਖੋ।"</string> + <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="3325858369539848162">"ਕੁੰਜੀ ਸੁਮੇਲ ਪਹਿਲਾਂ ਹੀ ਵਰਤੋਂ ਵਿੱਚ ਹੈ। ਕੋਈ ਹੋਰ ਸੁਮੇਲ ਵਰਤ ਕੇ ਦੇਖੋ।"</string> <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"ਸ਼ਾਰਟਕੱਟ ਨੂੰ ਸੈੱਟ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ।"</string> <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string> <string name="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"ਸ਼ਾਰਟਕੱਟ ਸ਼ਾਮਲ ਕਰੋ"</string> @@ -1476,6 +1478,7 @@ <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> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"ਐਪਾਂ ਵਿਚਕਾਰ ਸਵਿੱਚ ਕਰੋ"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"ਹੋ ਗਿਆ"</string> <string name="gesture_error_title" msgid="469064941635578511">"ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ!"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"ਵਾਪਸ ਜਾਓ"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"ਬਹੁਤ ਵਧੀਆ!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"ਤੁਸੀਂ \'ਹਾਲੀਆ ਐਪਾਂ ਦੇਖੋ\' ਦਾ ਇਸ਼ਾਰਾ ਪੂਰਾ ਕੀਤਾ ਹੈ।"</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"ਹਾਲੀਆ ਐਪਾਂ ਦੇਖਣ ਲਈ, ਆਪਣੇ ਟੱਚਪੈਡ \'ਤੇ ਤਿੰਨ ਉਂਗਲਾਂ ਵਰਤ ਕੇ ਉੱਪਰ ਵੱਲ ਸਵਾਈਪ ਕਰਕੇ ਰੋਕ ਕੇ ਰੱਖੋ"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"ਐਪਾਂ ਵਿਚਕਾਰ ਸਵਿੱਚ ਕਰੋ"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"ਬਹੁਤ ਵਧੀਆ!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"ਤੁਸੀਂ ਐਪ ਸਵਿੱਚ ਦਾ ਇਸ਼ਾਰਾ ਪੂਰਾ ਕੀਤਾ।"</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"ਸਾਰੀਆਂ ਐਪਾਂ ਦੇਖੋ"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"ਆਪਣੇ ਕੀ-ਬੋਰਡ \'ਤੇ ਕਾਰਵਾਈ ਕੁੰਜੀ ਨੂੰ ਦਬਾਓ"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"ਬਹੁਤ ਵਧੀਆ!"</string> diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml index 67ca4393ee64..031311741a55 100644 --- a/packages/SystemUI/res/values-pl/strings.xml +++ b/packages/SystemUI/res/values-pl/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"Aktualizuję"</string> <string name="status_bar_work" msgid="5238641949837091056">"Profil służbowy"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Tryb samolotowy"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"Nie usłyszysz swojego następnego alarmu <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template" msgid="2234991538018805736">"o <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"w: <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satelita – połączenie dobre"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satelita – połączenie dostępne"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Satelitarne połączenie alarmowe"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"Tylko połączenia alarmowe lub SOS"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"brak sygnału"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"1 pasek"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"Wstecz"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"Powiadomienia"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"Skróty klawiszowe"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"Przełącz układ klawiatury"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"lub"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"Wyczyść wyszukiwanie hasło"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"Skróty klawiszowe"</string> @@ -1432,6 +1432,8 @@ <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_split_screen" msgid="1159669813444812244">"Podzielony ekran"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <string name="shortcut_helper_category_input" msgid="8674018654124839566">"Wprowadzanie"</string> <string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Skróty do aplikacji"</string> <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Bieżąca aplikacja"</string> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"Dostosuj skróty"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Usunąć skrót?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"Zresetować do ustawień domyślnych?"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Naciśnij klawisz, aby przypisać skrót"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"Aby utworzyć ten skrót, naciśnij jednocześnie klawisz działania i co najmniej 1 inny klawisz"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Spowoduje to trwałe usunięcie skrótu niestandardowego."</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"Spowoduje to trwałe usunięcie wszystkich skrótów niestandardowych."</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Szukaj skrótów"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"Tak, zresetuj"</string> <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_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Ta kombinacja klawiszy jest już używana. Użyj innego klawisza."</string> + <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="3325858369539848162">"Ta kombinacja klawiszy jest już używana. Użyj innej."</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="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"Dodaj skrót"</string> @@ -1476,6 +1478,7 @@ <string name="touchpad_tutorial_back_gesture_button" msgid="3104716365403620315">"Wróć"</string> <string name="touchpad_tutorial_home_gesture_button" msgid="8023973153559885624">"Otwórz stronę główną"</string> <string name="touchpad_tutorial_recent_apps_gesture_button" msgid="8919227647650347359">"Wyświetlanie ostatnich aplikacji"</string> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"Przełączanie aplikacji"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"Gotowe"</string> <string name="gesture_error_title" msgid="469064941635578511">"Spróbuj jeszcze raz"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"Wróć"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"Brawo!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"Znasz już gest wyświetlania ostatnio używanych aplikacji."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"Aby wyświetlić ostatnie aplikacje, przesuń 3 palcami w górę na touchpadzie i przytrzymaj"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"Przełączanie aplikacji"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"Brawo!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"Gest do przełączania aplikacji został opanowany."</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"Wyświetl wszystkie aplikacje"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"Naciśnij klawisz działania na klawiaturze"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"Brawo!"</string> diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml index ead72a21e432..e21554cb9e6f 100644 --- a/packages/SystemUI/res/values-pt-rBR/strings.xml +++ b/packages/SystemUI/res/values-pt-rBR/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"Atualizando"</string> <string name="status_bar_work" msgid="5238641949837091056">"Perfil de trabalho"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Modo avião"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"Você não ouvirá o próximo alarme às <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template" msgid="2234991538018805736">"às <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"<xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satélite, conexão boa"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satélite, conexão disponível"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS via satélite"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"Apenas chamadas de emergência ou SOS"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"sem sinal"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"uma barra"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"Voltar"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"Notificações"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"Atalhos do teclado"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"Alterar layout do teclado"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"ou"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"Limpar a consulta de pesquisa"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"Atalhos do teclado"</string> @@ -1432,6 +1432,8 @@ <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_split_screen" msgid="1159669813444812244">"Tela dividida"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <string name="shortcut_helper_category_input" msgid="8674018654124839566">"Entrada"</string> <string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Atalhos de apps"</string> <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"App atual"</string> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"Personalizar atalhos"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Remover atalho?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"Redefinir para o padrão?"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Pressione a tecla para atribuir o atalho"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"Para criar esse atalho, pressione a tecla de ação e uma ou mais outras teclas"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Essa ação vai excluir permanentemente seu atalho personalizado."</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"Essa ação vai excluir permanentemente todos os seus atalhos personalizados."</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Pesquisar atalhos"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"Sim, redefinir"</string> <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_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_key_combination_in_use_error_message" msgid="3325858369539848162">"Essa combinação de teclas já está em uso. Tente outra."</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="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"Adicionar atalho"</string> @@ -1476,6 +1478,7 @@ <string name="touchpad_tutorial_back_gesture_button" msgid="3104716365403620315">"Voltar"</string> <string name="touchpad_tutorial_home_gesture_button" msgid="8023973153559885624">"Ir para a página inicial"</string> <string name="touchpad_tutorial_recent_apps_gesture_button" msgid="8919227647650347359">"Ver os apps recentes"</string> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"Mudar de app"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"Concluído"</string> <string name="gesture_error_title" msgid="469064941635578511">"Tente de novo"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"Voltar"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"Muito bem!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"Você concluiu o gesto para ver os apps recentes."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"Se quiser ver os apps recentes, deslize para cima e pressione o touchpad com três dedos"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"Mudar de app"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"Muito bem!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"Você concluiu o gesto para mudar de app."</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"Ver todos os apps"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"Pressione a tecla de ação no teclado"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"Muito bem!"</string> diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml index 303a2808d552..f5c29d485130 100644 --- a/packages/SystemUI/res/values-pt-rPT/strings.xml +++ b/packages/SystemUI/res/values-pt-rPT/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"A atualizar"</string> <string name="status_bar_work" msgid="5238641949837091056">"Perfil de trabalho"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Modo de avião"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"Não vai ouvir o próximo alarme às <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template" msgid="2234991538018805736">"às <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"em <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satélite, boa ligação"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satélite, ligação disponível"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Satélite SOS"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"Apenas chamadas de emergência ou SOS"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"sem sinal"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"1 barra"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"Anterior"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"Notificações"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"Atalhos de teclado"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"Alterar esquema de teclado"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"ou"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"Limpar consulta de pesquisa"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"Atalhos de teclado"</string> @@ -1432,6 +1432,8 @@ <string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"Apps do sistema"</string> <string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Multitasking"</string> <string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Ecrã dividido"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <string name="shortcut_helper_category_input" msgid="8674018654124839566">"Entrada"</string> <string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Atalhos de apps"</string> <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"App atual"</string> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"Personalize os atalhos"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Remover atalho?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"Repor para a predefinição?"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Prima a tecla para atribuir o atalho"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"Para criar este atalho, prima a tecla de ação e uma ou mais teclas adicionais em simultâneo"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Esta ação elimina o atalho personalizado permanentemente."</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"Esta ação elimina todos os seus atalhos personalizados permanentemente."</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Pesquisar atalhos"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"Sim, repor"</string> <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_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_key_combination_in_use_error_message" msgid="3325858369539848162">"A combinação de teclas já está a ser usada. Experimente outra combinação."</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="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"Adicionar atalho"</string> @@ -1476,6 +1478,7 @@ <string name="touchpad_tutorial_back_gesture_button" msgid="3104716365403620315">"Voltar"</string> <string name="touchpad_tutorial_home_gesture_button" msgid="8023973153559885624">"Aceder ao ecrã principal"</string> <string name="touchpad_tutorial_recent_apps_gesture_button" msgid="8919227647650347359">"Ver apps recentes"</string> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"Mudar de app"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"Concluir"</string> <string name="gesture_error_title" msgid="469064941635578511">"Tente novamente!"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"Voltar"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"Muito bem!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"Concluiu o gesto para ver as apps recentes."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"Para ver as apps recentes, deslize rapidamente para cima sem soltar com 3 dedos no touchpad"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"Mudar de app"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"Muito bem!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"Concluiu o gesto para alternar entre apps."</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"Ver todas as apps"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"Prima a tecla de ação no teclado"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"Muito bem!"</string> diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml index ead72a21e432..e21554cb9e6f 100644 --- a/packages/SystemUI/res/values-pt/strings.xml +++ b/packages/SystemUI/res/values-pt/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"Atualizando"</string> <string name="status_bar_work" msgid="5238641949837091056">"Perfil de trabalho"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Modo avião"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"Você não ouvirá o próximo alarme às <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template" msgid="2234991538018805736">"às <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"<xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satélite, conexão boa"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satélite, conexão disponível"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS via satélite"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"Apenas chamadas de emergência ou SOS"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"sem sinal"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"uma barra"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"Voltar"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"Notificações"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"Atalhos do teclado"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"Alterar layout do teclado"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"ou"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"Limpar a consulta de pesquisa"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"Atalhos do teclado"</string> @@ -1432,6 +1432,8 @@ <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_split_screen" msgid="1159669813444812244">"Tela dividida"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <string name="shortcut_helper_category_input" msgid="8674018654124839566">"Entrada"</string> <string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Atalhos de apps"</string> <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"App atual"</string> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"Personalizar atalhos"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Remover atalho?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"Redefinir para o padrão?"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Pressione a tecla para atribuir o atalho"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"Para criar esse atalho, pressione a tecla de ação e uma ou mais outras teclas"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Essa ação vai excluir permanentemente seu atalho personalizado."</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"Essa ação vai excluir permanentemente todos os seus atalhos personalizados."</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Pesquisar atalhos"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"Sim, redefinir"</string> <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_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_key_combination_in_use_error_message" msgid="3325858369539848162">"Essa combinação de teclas já está em uso. Tente outra."</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="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"Adicionar atalho"</string> @@ -1476,6 +1478,7 @@ <string name="touchpad_tutorial_back_gesture_button" msgid="3104716365403620315">"Voltar"</string> <string name="touchpad_tutorial_home_gesture_button" msgid="8023973153559885624">"Ir para a página inicial"</string> <string name="touchpad_tutorial_recent_apps_gesture_button" msgid="8919227647650347359">"Ver os apps recentes"</string> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"Mudar de app"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"Concluído"</string> <string name="gesture_error_title" msgid="469064941635578511">"Tente de novo"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"Voltar"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"Muito bem!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"Você concluiu o gesto para ver os apps recentes."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"Se quiser ver os apps recentes, deslize para cima e pressione o touchpad com três dedos"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"Mudar de app"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"Muito bem!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"Você concluiu o gesto para mudar de app."</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"Ver todos os apps"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"Pressione a tecla de ação no teclado"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"Muito bem!"</string> diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml index afd18f12cbf5..7a9ea1fa10fd 100644 --- a/packages/SystemUI/res/values-ro/strings.xml +++ b/packages/SystemUI/res/values-ro/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"Se actualizează"</string> <string name="status_bar_work" msgid="5238641949837091056">"Profil de serviciu"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Mod Avion"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"Nu vei auzi următoarea alarmă <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template" msgid="2234991538018805736">"la <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"<xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satelit, conexiune bună"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satelit, conexiune disponibilă"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS prin satelit"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"Numai apeluri de urgență sau SOS"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"fără semnal"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"o bară"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"Înapoi"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"Notificări"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"Comenzi rapide de la tastatură"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"Schimbă aspectul tastaturii"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"sau"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"Șterge termenul de căutare"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"Comenzi rapide de la tastatură"</string> @@ -1432,6 +1432,8 @@ <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_split_screen" msgid="1159669813444812244">"Ecran împărțit"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <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> <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Aplicația actuală"</string> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"Personalizează comenzile rapide"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Elimini comanda rapidă?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"Resetezi la valorile prestabilite?"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Apasă tasta pentru a atribui comanda rapidă"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"Pentru a crea această comandă rapidă, apasă tasta de acțiuni și una sau mai multe taste simultan"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Astfel, se va șterge definitiv comanda rapidă personalizată."</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"Astfel, se vor șterge definitiv toate comenzile rapide personalizate."</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Comenzi directe de căutare"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"Resetează"</string> <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_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Combinația de taste este deja folosită. Încearcă altă tastă."</string> + <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="3325858369539848162">"Combinația de taste este deja folosită. Încearcă altă combinație."</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="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"Adaugă o comandă rapidă"</string> @@ -1476,6 +1478,7 @@ <string name="touchpad_tutorial_back_gesture_button" msgid="3104716365403620315">"Înapoi"</string> <string name="touchpad_tutorial_home_gesture_button" msgid="8023973153559885624">"Înapoi la pagina de pornire"</string> <string name="touchpad_tutorial_recent_apps_gesture_button" msgid="8919227647650347359">"Vezi aplicațiile recente"</string> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"Comută între aplicații"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"Gata"</string> <string name="gesture_error_title" msgid="469064941635578511">"Încearcă din nou!"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"Înapoi"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"Excelent!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"Ai finalizat gestul pentru afișarea aplicațiilor recente."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"Ca să vezi aplicațiile recente, glisează în sus și ține apăsat cu trei degete pe touchpad"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"Comută între aplicații"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"Excelent!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"Ai finalizat gestul de trecere la altă aplicație."</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"Vezi toate aplicațiile"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"Apasă tasta de acțiuni de pe tastatură"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"Felicitări!"</string> diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml index 10dc51d71683..f5b33742c8cb 100644 --- a/packages/SystemUI/res/values-ru/strings.xml +++ b/packages/SystemUI/res/values-ru/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"Обновление"</string> <string name="status_bar_work" msgid="5238641949837091056">"Рабочий профиль"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Режим полета"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"Следующий будильник: <xliff:g id="WHEN">%1$s</xliff:g>. Звук отключен."</string> <string name="alarm_template" msgid="2234991538018805736">"<xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"<xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Спутниковая связь, хорошее качество соединения"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Доступно соединение по спутниковой связи"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Спутниковый SOS"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"Только экстренные вызовы или спутниковый SOS"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"нет сигнала"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"одно деление"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"Назад"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"Уведомления"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"Сочетания клавиш"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"Переключение раскладки"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"или"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"Удалить поисковый запрос"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"Сочетания клавиш"</string> @@ -1432,6 +1432,8 @@ <string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"Системные приложения"</string> <string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Многозадачность"</string> <string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Разделение экрана"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <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> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"Настройки сочетаний клавиш"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Удалить сочетание клавиш?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"Сбросить настройки?"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Нажмите клавишу, чтобы назначить сочетание клавиш."</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"Чтобы сохранить сочетание клавиш, нажмите одновременно клавишу действия и одну или несколько дополнительных клавиш"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Настроенное сочетание будет безвозвратно удалено."</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"Все настроенные сочетания клавиш будут безвозвратно удалены."</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Найти"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"Сбросить"</string> <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_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Это сочетание клавиш уже используется. Попробуйте другое."</string> + <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="3325858369539848162">"Это сочетание клавиш уже используется. Попробуйте другое."</string> <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Это сочетание клавиш выбрать нельзя."</string> <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string> <string name="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"Добавить сочетание клавиш"</string> @@ -1476,6 +1478,7 @@ <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> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"Переход в другое приложение"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"Готово"</string> <string name="gesture_error_title" msgid="469064941635578511">"Попробуйте ещё раз"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"Назад"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"Отлично!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"Вы выполнили жест для просмотра недавних приложений."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"Чтобы увидеть недавние приложения, проведите по сенсорной панели тремя пальцами вверх и удерживайте."</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"Переход в другое приложение"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"Отлично!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"Вы выполнили жест перехода в другое приложение."</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"Все приложения"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"Нажмите клавишу действия на клавиатуре."</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"Блестяще!"</string> diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml index 5a7a6a377527..a9ea2ae68a89 100644 --- a/packages/SystemUI/res/values-si/strings.xml +++ b/packages/SystemUI/res/values-si/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"යාවත්කාලීන කිරීම"</string> <string name="status_bar_work" msgid="5238641949837091056">"කාර්යාල පැතිකඩ"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"ගුවන්යානා ප්රකාරය"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"ඔබට ඔබේ ඊළඟ එලාමය <xliff:g id="WHEN">%1$s</xliff:g> නොඇසෙනු ඇත"</string> <string name="alarm_template" msgid="2234991538018805736">"<xliff:g id="WHEN">%1$s</xliff:g> ට"</string> <string name="alarm_template_far" msgid="3561752195856839456">"<xliff:g id="WHEN">%1$s</xliff:g> දී"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"චන්ද්රිකාව, හොඳ සම්බන්ධතාවයක්"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"චන්ද්රිකාව, සම්බන්ධතාවය තිබේ"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"චන්ද්රිකා SOS"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"හදිසි ඇමතුම් හෝ SOS පමණි"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"සංඥාව නැත"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"තීරු එකක්"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"ආපසු"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"දැනුම්දීම්"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"යතුරු පුවරු කෙටිමං"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"යතුරුපුවරු පිරිසැලසුම මාරු කරන්න"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"හෝ"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"සෙවීම් විමසුම හිස් කරන්න"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"යතුරු පුවරු කෙටිමං"</string> @@ -1432,16 +1432,17 @@ <string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"පද්ධති යෙදුම්"</string> <string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"බහුකාර්ය"</string> <string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"බෙදුම් තිරය"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <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> - <!-- no translation found for shortcut_helper_customize_mode_title (8327297960035006036) --> - <skip /> + <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"කෙටිමං අභිරුචිකරණය කරන්න"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"කෙටිමඟ ඉවත් කරන්න ද?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"පෙරනිමියට යළි සකසන්න ද?"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"කෙටිමඟ පැවරීමට යතුර ඔබන්න"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"මෙම කෙටිමඟ නිර්මාණය කිරීමට, ක්රියාකාරී යතුර සහ තවත් යතුරු එකක් හෝ කිහිපයක් එකවර ඔබන්න"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"මෙය ඔබේ අභිරුචි කෙටිමඟ ස්ථිරවම මකනු ඇත."</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"මෙය ඔබේ සියලු අභිරුචි කෙටිමං ස්ථිරවම මකනු ඇත."</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"කෙටි මං සොයන්න"</string> @@ -1463,13 +1464,11 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"ඔව්, යළි සකසන්න"</string> <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_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"යතුරු සංයෝජනය දැනටමත් භාවිත වේ. වෙනත් යතුරක් උත්සාහ කරන්න."</string> + <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="3325858369539848162">"යතුරු සංයෝජනය දැනටමත් භාවිත වේ. තවත් සංයෝජනයක් උත්සාහ කරන්න."</string> <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"කෙටිමඟ සැකසිය නොහැක."</string> <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string> - <!-- no translation found for shortcut_helper_add_shortcut_button_label (7655779534665954910) --> - <skip /> - <!-- no translation found for shortcut_helper_delete_shortcut_button_label (3148773472696137052) --> - <skip /> + <string name="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"කෙටිමඟ එක් කරන්න"</string> + <string name="shortcut_helper_delete_shortcut_button_label" msgid="3148773472696137052">"කෙටිමඟ මකන්න"</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> @@ -1479,6 +1478,7 @@ <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> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"යෙදුම් මාරු කරන්න"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"නිමයි"</string> <string name="gesture_error_title" msgid="469064941635578511">"නැවත උත්සාහ කරන්න!"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"ආපස්සට යන්න"</string> @@ -1496,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"අනර්ඝ වැඩක්!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"ඔබ මෑත යෙදුම් ඉංගිත බැලීම සම්පූර්ණ කර ඇත."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"මෑත කාලීන යෙදුම් බැලීමට, ඔබේ ස්පර්ශක පෑඩයේ ඇඟිලි තුනක් භාවිතයෙන් ඉහළට ස්වයිප් කරගෙන සිටින්න"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"යෙදුම් මාරු කරන්න"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"අනර්ඝ වැඩක්!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"ඔබ යෙදුම් මාරු කිරීමේ ඉංගිතය සම්පූර්ණ කර ඇත."</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"සියලු යෙදුම් බලන්න"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"ඔබේ යතුරු පුවරුවේ ක්රියාකාරී යතුර ඔබන්න"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"හොඳින් කළා!"</string> diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml index d2d9cdeed413..406f423a18d0 100644 --- a/packages/SystemUI/res/values-sk/strings.xml +++ b/packages/SystemUI/res/values-sk/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"Aktualizuje sa"</string> <string name="status_bar_work" msgid="5238641949837091056">"Pracovný profil"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Režim v lietadle"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"Váš budík o <xliff:g id="WHEN">%1$s</xliff:g> sa nespustí"</string> <string name="alarm_template" msgid="2234991538018805736">"o <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"<xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satelit, dobrá kvalita pripojenia"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satelit, pripojenie je k dispozícii"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Pomoc cez satelit"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"Len tiesňové volania alebo pomoc v tiesni"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"žiadny signál"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"jedna čiarka"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"Späť"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"Upozornenia"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"Klávesové skratky"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"Prepnúť rozloženie klávesnice"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"alebo"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"Vymazať vyhľadávací dopyt"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"Klávesové skratky"</string> @@ -1432,6 +1432,8 @@ <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_split_screen" msgid="1159669813444812244">"Rozdelená obrazovka"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <string name="shortcut_helper_category_input" msgid="8674018654124839566">"Vstup"</string> <string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Skratky aplikácií"</string> <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Aktuálna aplikácia"</string> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"Prispôsobenie skratiek"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Chcete skratku odstrániť?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"Chcete resetovať na predvolené nastavenie?"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Stlačením klávesa priraďte skratku"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"Ak chcete vytvoriť túto skratku, stlačte súčasne akčný kláves a minimálne jeden ďalší kláves"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Týmto natrvalo odstránite vlastnú skratku."</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"Týmto natrvalo odstránite všetky vlastné odkazy."</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Prehľadávať skratky"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"Áno, resetovať"</string> <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_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_key_combination_in_use_error_message" msgid="3325858369539848162">"Kombinácia klávesov sa už používa. Skúste inú kombináciu."</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="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"Pridať skratku"</string> @@ -1476,6 +1478,7 @@ <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> <string name="touchpad_tutorial_recent_apps_gesture_button" msgid="8919227647650347359">"Zobrazenie nedávnych aplikácií"</string> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"Prepínanie aplikácií"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"Hotovo"</string> <string name="gesture_error_title" msgid="469064941635578511">"Skúste to znova."</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"Prejdenie späť"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"Skvelé!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"Použili ste gesto na zobrazenie nedávnych aplikácií."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"Ak si chcete zobraziť nedávne aplikácie, potiahnite troma prstami na touchpade nahor a pridržte ich"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"Prepínanie aplikácií"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"Skvelé!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"Dokončili ste gesto na prepnutie aplikácií."</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"Zobrazenie všetkých aplikácií"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"Stlačte na klávesnici akčný kláves"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"Dobre!"</string> diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml index c473962ca1ba..7127bed39f6b 100644 --- a/packages/SystemUI/res/values-sl/strings.xml +++ b/packages/SystemUI/res/values-sl/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"Posodabljanje"</string> <string name="status_bar_work" msgid="5238641949837091056">"Profil za Android Work"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Način za letalo"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"Naslednjega alarma ob <xliff:g id="WHEN">%1$s</xliff:g> ne boste slišali"</string> <string name="alarm_template" msgid="2234991538018805736">"ob <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"ob tem času: <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satelit, dobra povezava"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satelit, povezava je na voljo"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS prek satelita"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"Samo klici v sili ali SOS"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"ni signala"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"ena črtica"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"Nazaj"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"Obvestila"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"Bližnjične tipke"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"Preklop postavitve tipkovnice"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"ali"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"Čiščenje iskalne poizvedbe"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"Bližnjične tipke"</string> @@ -1432,6 +1432,8 @@ <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_split_screen" msgid="1159669813444812244">"Razdeljen zaslon"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <string name="shortcut_helper_category_input" msgid="8674018654124839566">"Vnos"</string> <string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Bližnjice do aplikacij"</string> <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Trenutna aplikacija"</string> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"Prilagajanje bližnjic"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Želite odstraniti bližnjico?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"Želite ponastaviti na privzete bližnjice?"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Pritisnite tipko za dodelitev bližnjice"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"Če želite ustvariti to bližnjico, hkrati pritisnite tipko za dejanja in eno ali več drugih tipk"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"S tem boste trajno izbrisali bližnjico po meri."</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"S tem boste trajno izbrisali vse bližnjice po meri."</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Iskanje po bližnjicah"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"Da"</string> <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_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Kombinacija tipk je že v uporabi. Poskusite z drugo tipko."</string> + <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="3325858369539848162">"Kombinacija tipk je že v uporabi. Poskusite drugo kombinacijo."</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="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"Dodajanje bližnjice"</string> @@ -1476,6 +1478,7 @@ <string name="touchpad_tutorial_back_gesture_button" msgid="3104716365403620315">"Pomik nazaj"</string> <string name="touchpad_tutorial_home_gesture_button" msgid="8023973153559885624">"Pomik na začetni zaslon"</string> <string name="touchpad_tutorial_recent_apps_gesture_button" msgid="8919227647650347359">"Ogled nedavnih aplikacij"</string> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"Preklop aplikacij"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"Končano"</string> <string name="gesture_error_title" msgid="469064941635578511">"Poskusite znova"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"Nazaj"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"Odlično!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"Izvedli ste potezo za ogled nedavnih aplikacij."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"Za ogled nedavnih aplikacij povlecite s tremi prsti navzgor po sledilni ploščici in pridržite"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"Preklop aplikacij"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"Odlično!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"Izvedli ste potezo za preklop med aplikacijami."</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"Ogled vseh aplikacij"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"Pritisnite tipko za dejanja na tipkovnici"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"Odlično!"</string> diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml index 3c7d05b2e318..b02434a810a9 100644 --- a/packages/SystemUI/res/values-sq/strings.xml +++ b/packages/SystemUI/res/values-sq/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"Po përditësohet"</string> <string name="status_bar_work" msgid="5238641949837091056">"Profili i punës"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Modaliteti i aeroplanit"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"Nuk do ta dëgjosh alarmin e radhës në <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template" msgid="2234991538018805736">"në <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"në <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Sateliti. Lidhje e mirë"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Sateliti. Ofrohet lidhje"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS satelitor"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"Vetëm telefonata urgjence ose SOS"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"nuk ka sinjal"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"një vijë"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"Prapa"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"Njoftimet"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"Shkurtoret e tastierës"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"Ndërro strukturën e tastierës"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"ose"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"Pastro pyetjen e kërkimit"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"Shkurtoret e tastierës"</string> @@ -1432,16 +1432,17 @@ <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_split_screen" msgid="1159669813444812244">"Ekrani i ndarë"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <string name="shortcut_helper_category_input" msgid="8674018654124839566">"Hyrja"</string> <string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Shkurtoret e aplikacionit"</string> <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Aplikacioni aktual"</string> <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Qasshmëria"</string> <string name="shortcut_helper_title" msgid="8567500639300970049">"Shkurtoret e tastierës"</string> - <!-- no translation found for shortcut_helper_customize_mode_title (8327297960035006036) --> - <skip /> + <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"Personalizo shkurtoret"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Të hiqet shkurtorja?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"Të rivendosen përsëri te parazgjedhjet?"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Shtyp tastin për të caktuar shkurtoren"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"Për të krijuar këtë shkurtore, shtyp tastin e veprimit dhe një ose disa nga tastet e tjera së bashku"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Kjo do ta fshijë përgjithmonë shkurtoren tënde të personalizuar."</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"Kjo do të fshijë përgjithmonë të gjitha shkurtoret e tua të personalizuara."</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Kërko për shkurtoret"</string> @@ -1463,13 +1464,11 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"Po"</string> <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_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_key_combination_in_use_error_message" msgid="3325858369539848162">"Kombinimi i tasteve është tashmë në përdorim. Provo një kombinim 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> - <!-- no translation found for shortcut_helper_add_shortcut_button_label (7655779534665954910) --> - <skip /> - <!-- no translation found for shortcut_helper_delete_shortcut_button_label (3148773472696137052) --> - <skip /> + <string name="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"Shto shkurtore"</string> + <string name="shortcut_helper_delete_shortcut_button_label" msgid="3148773472696137052">"Fshi shkurtoren"</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> @@ -1479,6 +1478,7 @@ <string name="touchpad_tutorial_back_gesture_button" msgid="3104716365403620315">"Kthehu prapa"</string> <string name="touchpad_tutorial_home_gesture_button" msgid="8023973153559885624">"Shko tek ekrani bazë"</string> <string name="touchpad_tutorial_recent_apps_gesture_button" msgid="8919227647650347359">"Shiko aplikacionet e fundit"</string> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"Ndërro aplikacionet"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"U krye"</string> <string name="gesture_error_title" msgid="469064941635578511">"Provo përsëri!"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"Kthehu prapa"</string> @@ -1496,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"Punë e shkëlqyer!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"Përfundove gjestin për shikimin e aplikacioneve të fundit."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"Për të shikuar aplikacionet e fundit, rrëshqit shpejt lart dhe mbaj shtypur me tre gishta në bllokun me prekje"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"Ndërro aplikacionet"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"Punë e shkëlqyer!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"E ke përfunduar gjestin e ndërrimit të aplikacioneve."</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"Shiko të gjitha aplikacionet"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"Shtyp tastin e veprimit në tastierë"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"Shumë mirë!"</string> diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml index b7ddd4d7c6d2..70967419276b 100644 --- a/packages/SystemUI/res/values-sr/strings.xml +++ b/packages/SystemUI/res/values-sr/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"Ажурира се"</string> <string name="status_bar_work" msgid="5238641949837091056">"Пословни профил"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Режим рада у авиону"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"Нећете чути следећи аларм у <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template" msgid="2234991538018805736">"у <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"у <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Сателит, веза је добра"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Сателит, веза је доступна"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Хитна помоћ преко сателита"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"Само хитни позиви или хитна помоћ"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"нема сигнала"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"једна црта"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"Назад"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"Обавештења"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"Тастерске пречице"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"Промени распоред тастатуре"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"или"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"Обриши упит за претрагу"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"Тастерске пречице"</string> @@ -1432,6 +1432,8 @@ <string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"Системске апликације"</string> <string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Обављање више задатака истовремено"</string> <string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Подељени екран"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <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> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"Прилагодите пречице"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Желите да уклоните пречицу?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"Желите да ресетујете на подразумевано?"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Притисните тастер да бисте доделили пречицу"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"Да бисте направили ову пречицу, притисните заједно тастер радњи и један или више других тастера"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Овим ћете трајно избрисати прилагођену пречицу."</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"Тиме ћете трајно избрисати све прилагођене пречице."</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Претражите пречице"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"Да, ресетуј"</string> <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_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Комбинација тастера се већ користи. Пробајте са другим тастером."</string> + <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="3325858369539848162">"Комбинација тастера се већ користи. Пробајте са другом комбинацијом."</string> <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Подешавање пречице није успело."</string> <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string> <string name="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"Додајте пречицу"</string> @@ -1476,6 +1478,7 @@ <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> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"Пређи на другу апликацију"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"Готово"</string> <string name="gesture_error_title" msgid="469064941635578511">"Пробајте поново."</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"Назад"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"Одлично!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"Довршили сте покрет за приказивање недавно коришћених апликација."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"Да бисте прегледали недавне апликације, превуците нагоре и задржите са три прста на тачпеду"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"Пређи на другу апликацију"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"Одлично!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"Довршили сте покрет за промену апликација."</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"Прикажи све апликације"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"Притисните тастер радњи на тастатури"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"Одлично!"</string> diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml index 2d2a94d4e7b2..4e64e5a80d31 100644 --- a/packages/SystemUI/res/values-sv/strings.xml +++ b/packages/SystemUI/res/values-sv/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"Uppdaterar"</string> <string name="status_bar_work" msgid="5238641949837091056">"Jobbprofil"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Flygplansläge"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"Nästa alarm, kl. <xliff:g id="WHEN">%1$s</xliff:g>, kommer inte att höras"</string> <string name="alarm_template" msgid="2234991538018805736">"kl. <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"kl. <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satellit, bra anslutning"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satellit, anslutning tillgänglig"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS-larm via satellit"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"Endast nödsamtal eller SOS-larm"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"ingen signal"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"en stapel"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"Tillbaka"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"Aviseringar"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"Kortkommandon"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"Byt tangentbordslayout"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"eller"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"Rensa sökfråga"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"Kortkommandon"</string> @@ -1432,6 +1432,8 @@ <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_split_screen" msgid="1159669813444812244">"Delad skärm"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <string name="shortcut_helper_category_input" msgid="8674018654124839566">"Inmatning"</string> <string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Genvägar till appar"</string> <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Aktuell app"</string> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"Anpassa kortkommandon"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Vill du ta bort kortkommandot?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"Vill du återställa till standardinställningarna?"</string> - <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_add_shortcut_description" msgid="7636040209946696120">"Skapa det här kortkommandot genom att trycka på åtgärdstangenten och en eller flera andra tangenter samtidigt"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Det anpassade kortkommandot raderas permanent."</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"Alla anpassade genvägar raderas permanent."</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Sökgenvägar"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"Ja, återställ"</string> <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_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Tangentkombinationen används redan. Testa en annan tangent."</string> + <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="3325858369539848162">"Tangentkombinationen används redan. Testa en annan kombination."</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="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"Lägg till genväg"</string> @@ -1476,6 +1478,7 @@ <string name="touchpad_tutorial_back_gesture_button" msgid="3104716365403620315">"Tillbaka"</string> <string name="touchpad_tutorial_home_gesture_button" msgid="8023973153559885624">"Återvänd till startsidan"</string> <string name="touchpad_tutorial_recent_apps_gesture_button" msgid="8919227647650347359">"Se de senaste apparna"</string> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"Byta app"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"Klar"</string> <string name="gesture_error_title" msgid="469064941635578511">"Försök igen!"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"Tillbaka"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"Bra jobbat!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"Du är klar med rörelsen för att se de senaste apparna."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"Svep uppåt på styrplattan med tre fingrar och håll kvar för att se nyligen använda appar"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"Byta app"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"Bra jobbat!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"Du har slutfört rörelsen för att byta app"</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"Visa alla appar"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"Tryck på åtgärdstangenten på tangentbordet"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"Bra gjort!"</string> diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml index 424434d182a7..ecba5597e588 100644 --- a/packages/SystemUI/res/values-sw/strings.xml +++ b/packages/SystemUI/res/values-sw/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"Inasasisha"</string> <string name="status_bar_work" msgid="5238641949837091056">"Wasifu wa kazini"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Hali ya ndegeni"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"Hutasikia kengele yako inayofuata ya saa <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template" msgid="2234991538018805736">"saa <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"siku ya <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Setilaiti, muunganisho thabiti"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Setilaiti, muunganisho unapatikana"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Msaada kupitia Setilaiti"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"Simu ya dharura au kipengele cha Msaada kupitia Setilaiti pekee"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"hakuna mtandao"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"upau mmoja"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"Nyuma"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"Arifa"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"Mikato ya Kibodi"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"Badili mkao wa kibodi"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"au"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"Futa hoja ya utafutaji"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"Mikato ya Kibodi"</string> @@ -1432,6 +1432,8 @@ <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_split_screen" msgid="1159669813444812244">"Gawa skrini"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <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> <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Programu Inayotumika Sasa"</string> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"Kuweka mapendeleo ya njia za mkato"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Ungependa kuondoa njia ya mkato?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"Ungependa kurejesha njia za mkato chaguomsingi?"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Bonyeza kitufe ukabidhi njia ya mkato"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"Ili kuanzisha njia hii ya mkato, bonyeza kitufe cha Vitendo na ufunguo mmoja au zaidi kwa pamoja"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Hatua hii itaondoa kabisa njia yako maalum ya mkato."</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"Hatua hii itafuta kabisa njia zako zote maalum za mkato."</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Njia mkato za kutafutia"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"Ndiyo"</string> <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_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Tayari unatumia mchanganyiko wa vitufe. Jaribu kitufe kingine."</string> + <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="3325858369539848162">"Tayari unatumia mchanganyiko huu wa vitufe. Jaribu mchanganyiko mwingine."</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="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"Weka njia ya mkato"</string> @@ -1476,6 +1478,7 @@ <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> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"Badilisha programu"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"Nimemaliza"</string> <string name="gesture_error_title" msgid="469064941635578511">"Jaribu tena!"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"Rudi nyuma"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"Kazi nzuri!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"Umekamilisha mafunzo ya mguso wa kuangalia programu za hivi majuzi."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"Telezesha vidole vitatu juu na ushikilie kwenye padi yako ya kugusa ili uangalie programu za hivi majuzi"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"Badilisha programu"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"Kazi nzuri!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"Umekamilisha mafunzo kuhusu mguso wa kubadili programu."</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"Angalia programu zote"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"Bonyeza kitufe cha vitendo kwenye kibodi yako"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"Vizuri sana!"</string> diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml index cb340789345e..d6e5be4c1bd5 100644 --- a/packages/SystemUI/res/values-ta/strings.xml +++ b/packages/SystemUI/res/values-ta/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"புதுப்பிக்கிறது"</string> <string name="status_bar_work" msgid="5238641949837091056">"பணிக் கணக்கு"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"விமானப் பயன்முறை"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"அடுத்த அலாரத்தை <xliff:g id="WHEN">%1$s</xliff:g> மணிக்கு கேட்க மாட்டீர்கள்"</string> <string name="alarm_template" msgid="2234991538018805736">"<xliff:g id="WHEN">%1$s</xliff:g> மணிக்கு"</string> <string name="alarm_template_far" msgid="3561752195856839456">"<xliff:g id="WHEN">%1$s</xliff:g> மணிக்கு"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"சாட்டிலைட், நிலையான இணைப்பு"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"சாட்டிலைட், இணைப்பு கிடைக்கிறது"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"சாட்டிலைட் SOS"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"அவசர அழைப்புகள் அல்லது SOS மட்டும்"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"சிக்னல் இல்லை"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"ஒரு கோடு"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"முந்தையது"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"அறிவிப்புகள்"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"கீபோர்டு ஷார்ட்கட்கள்"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"கீபோர்டு லே அவுட்டை மாற்று"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"அல்லது"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"தேடல் வினவலை அழிக்கும்"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"கீபோர்டு ஷார்ட்கட்கள்"</string> @@ -1432,6 +1432,8 @@ <string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"சிஸ்டம் ஆப்ஸ்"</string> <string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"பல வேலைகளைச் செய்தல்"</string> <string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"திரைப் பிரிப்பு"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <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> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"ஷார்ட்கட்களைப் பிரத்தியேகமாக்குதல்"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"ஷார்ட்கட்டை அகற்றவா?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"மீண்டும் இயல்புநிலைக்கு மீட்டமைக்கவா?"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"ஷார்ட்கட்டை அமைக்க பட்டனை அழுத்துங்கள்"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"இந்த ஷார்ட்கட்டை உருவாக்க, ஆக்ஷன் பட்டனுடன் ஒன்று அல்லது அதற்கும் மேற்பட்ட பிற பட்டன்களை ஒன்றாக அழுத்துங்கள்"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"இது உங்கள் பிரத்தியேக ஷார்ட்கட்டை நிரந்தரமாக நீக்கும்."</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"இது உங்கள் பிரத்தியேக ஷார்ட்கட்கள் அனைத்தையும் நிரந்தரமாக நீக்கும்."</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"ஷார்ட்கட்களைத் தேடுக"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"ஆம். மீட்டமை"</string> <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_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"பட்டன் சேர்க்கை ஏற்கெனவே பயன்பாட்டில் உள்ளது. வேறொரு பட்டனைப் பயன்படுத்திப் பார்க்கவும்."</string> + <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="3325858369539848162">"பட்டன் சேர்க்கை ஏற்கெனவே பயன்பாட்டில் உள்ளது. வேறு பட்டன் சேர்க்கையை முயலவும்."</string> <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"ஷார்ட்கட்டை அமைக்க முடியாது."</string> <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string> <string name="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"ஷார்ட்கட்டைச் சேர்க்கும்"</string> @@ -1476,6 +1478,7 @@ <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> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"ஆப்ஸுக்கிடையில் மாறுங்கள்"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"முடிந்தது"</string> <string name="gesture_error_title" msgid="469064941635578511">"மீண்டும் முயலவும்!"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"பின்செல்"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"அருமை!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"சமீபத்தில் பயன்படுத்திய ஆப்ஸுக்கான சைகை பயிற்சியை நிறைவுசெய்துவிட்டீர்கள்."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"சமீபத்திய ஆப்ஸைப் பார்க்க, உங்கள் டச்பேடில் மூன்று விரல்களால் மேல்நோக்கி ஸ்வைப் செய்து பிடிக்கவும்"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"ஆப்ஸுக்கிடையில் மாறுங்கள்"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"அருமை!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"ஆப்ஸுக்கிடையில் மாறும் சைகைப் பயிற்சியை முடித்துவிட்டீர்கள்."</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"அனைத்து ஆப்ஸையும் காட்டு"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"உங்கள் கீபோர்டில் ஆக்ஷன் பட்டனை அழுத்தவும்"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"அருமை!"</string> diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml index 8dac7651ae49..404e5a1308f4 100644 --- a/packages/SystemUI/res/values-te/strings.xml +++ b/packages/SystemUI/res/values-te/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"అప్డేట్ చేస్తోంది"</string> <string name="status_bar_work" msgid="5238641949837091056">"ఆఫీస్ ప్రొఫైల్"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"విమానం మోడ్"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"మీరు <xliff:g id="WHEN">%1$s</xliff:g> సెట్ చేసిన మీ తర్వాత అలారం మీకు వినిపించదు"</string> <string name="alarm_template" msgid="2234991538018805736">"<xliff:g id="WHEN">%1$s</xliff:g>కి"</string> <string name="alarm_template_far" msgid="3561752195856839456">"<xliff:g id="WHEN">%1$s</xliff:g>కి"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"శాటిలైట్, కనెక్షన్ బాగుంది"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"శాటిలైట్, కనెక్షన్ అందుబాటులో ఉంది"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"ఎమర్జెన్సీ శాటిలైట్ సహాయం"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"ఎమర్జెన్సీ కాల్స్ లేదా SOS మాత్రమే"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"సిగ్నల్ లేదు"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"సిగ్నల్ ఒక బార్ ఉంది"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"వెనుకకు"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"నోటిఫికేషన్లు"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"కీబోర్డ్ షార్ట్కట్లు"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"కీబోర్డ్ లేఅవుట్ను మార్చండి"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"లేదా"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"సెర్చ్ క్వెరీని క్లియర్ చేయండి"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"కీబోర్డ్ షార్ట్కట్లు"</string> @@ -1432,6 +1432,8 @@ <string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"సిస్టమ్ యాప్లు"</string> <string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"మల్టీ-టాస్కింగ్"</string> <string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"స్ప్లిట్ స్క్రీన్"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <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> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"షార్ట్కట్లను అనుకూలంగా మార్చండి"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"షార్ట్కట్ను తీసివేయాలా?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"తిరిగి ఆటోమేటిక్ సెట్టింగ్కు రీసెట్ చేయాలా?"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"షార్ట్కట్ను కేటాయించడానికి కీని నొక్కండి"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"ఈ షార్ట్కట్ను క్రియేట్ చేయడానికి, యాక్షన్ కీని, ఒకటి లేదా అంత కంటే ఎక్కువ ఇతర కీలను కలిపి నొక్కండి"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"ఇది మీ అనుకూల షార్ట్కట్ను శాశ్వతంగా తొలగిస్తుంది."</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"ఇది మీ అనుకూల షార్ట్కట్లన్నింటిని శాశ్వతంగా తొలగిస్తుంది."</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"షార్ట్కట్లను వెతకండి"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"అవును, రీసెట్ చేయాలి"</string> <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_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"కీ కాంబినేషన్ ఇప్పటికే వినియోగంలో ఉంది. వేరొక కీని ట్రై చేయండి."</string> + <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="3325858369539848162">"కీ కాంబినేషన్ ఇప్పటికే వినియోగంలో ఉంది. మరొక కాంబినేషన్ను ట్రై చేయండి."</string> <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"షార్ట్కట్ను సెట్ చేయడం సాధ్యం కాదు."</string> <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string> <string name="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"షార్ట్కట్ను జోడించండి"</string> @@ -1476,6 +1478,7 @@ <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> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"యాప్ల మధ్య మారండి"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"పూర్తయింది"</string> <string name="gesture_error_title" msgid="469064941635578511">"మళ్లీ ట్రై చేయండి!"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"వెనుకకు"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"చక్కగా పూర్తి చేశారు!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"ఇటీవలి యాప్లను చూడడానికి ఉపయోగించే సంజ్ఞకు సంబంధించిన ట్యుటోరియల్ను మీరు పూర్తి చేశారు."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"ఇటీవలి యాప్లను చూడటానికి, మీ టచ్ప్యాడ్లో మూడు వేళ్లను ఉపయోగించి పైకి స్వైప్ చేసి, హోల్డ్ చేయండి"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"యాప్ల మధ్య మారండి"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"చక్కగా పూర్తి చేశారు!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"మీరు యాప్ల మధ్య మారేందుకు సంజ్ఞను పూర్తి చేశారు."</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"అన్ని యాప్లను చూడండి"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"మీ కీబోర్డ్లో యాక్షన్ కీని నొక్కండి"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"చక్కగా చేశారు!"</string> diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml index cd111fd54d02..c67852bd826a 100644 --- a/packages/SystemUI/res/values-th/strings.xml +++ b/packages/SystemUI/res/values-th/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"กำลังอัปเดต"</string> <string name="status_bar_work" msgid="5238641949837091056">"โปรไฟล์งาน"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"โหมดบนเครื่องบิน"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"คุณจะไม่ได้ยินเสียงปลุกครั้งถัดไปในเวลา <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template" msgid="2234991538018805736">"เวลา <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"ในวันที่ <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"ดาวเทียม, การเชื่อมต่อดี"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"ดาวเทียม, การเชื่อมต่อที่พร้อมใช้งาน"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS ดาวเทียม"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"โทรฉุกเฉินหรือ SOS เท่านั้น"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>"</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"ไม่มีสัญญาณ"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"1 ขีด"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"กลับ"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"การแจ้งเตือน"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"แป้นพิมพ์ลัด"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"สลับรูปแบบแป้นพิมพ์"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"หรือ"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"ล้างคำค้นหา"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"แป้นพิมพ์ลัด"</string> @@ -1432,6 +1432,8 @@ <string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"แอประบบ"</string> <string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"การทํางานหลายอย่างพร้อมกัน"</string> <string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"แยกหน้าจอ"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <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> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"ปรับแต่งแป้นพิมพ์ลัด"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"นำแป้นพิมพ์ลัดออกใช่ไหม"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"รีเซ็ตกลับเป็นค่าเริ่มต้นไหม"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"กดแป้นเพื่อกำหนดแป้นพิมพ์ลัด"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"หากต้องการสร้างแป้นพิมพ์ลัดนี้ ให้กดปุ่มดำเนินการและปุ่มอื่นๆ อย่างน้อย 1 ปุ่มพร้อมกัน"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"การดำเนินการนี้จะลบแป้นพิมพ์ลัดที่กำหนดเองอย่างถาวร"</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"การดำเนินการนี้จะลบแป้นพิมพ์ลัดที่กำหนดเองทั้งหมดอย่างถาวร"</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"ค้นหาแป้นพิมพ์ลัด"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"ใช่ รีเซ็ต"</string> <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_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"มีการใช้แป้นที่กดร่วมกันนี้แล้ว โปรดลองใช้แป้นอื่น"</string> + <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="3325858369539848162">"มีการใช้แป้นที่กดร่วมกันนี้แล้ว โปรดลองใช้ชุดค่าผสมอื่น"</string> <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"ตั้งค่าแป้นพิมพ์ลัดไม่ได้"</string> <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string> <string name="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"เพิ่มทางลัด"</string> @@ -1476,6 +1478,7 @@ <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> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"เปลี่ยนแอป"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"เสร็จสิ้น"</string> <string name="gesture_error_title" msgid="469064941635578511">"ลองอีกครั้งนะ"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"ย้อนกลับ"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"เยี่ยมมาก"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"คุณทำท่าทางสัมผัสเพื่อดูแอปล่าสุดสำเร็จแล้ว"</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"หากต้องการดูแอปล่าสุด ให้ใช้ 3 นิ้วปัดขึ้นแล้วค้างไว้บนทัชแพด"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"เปลี่ยนแอป"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"เก่งมาก"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"คุณทำท่าทางสัมผัสเพื่อสลับแอปเสร็จแล้ว"</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"ดูแอปทั้งหมด"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"กดปุ่มดำเนินการบนแป้นพิมพ์"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"ยอดเยี่ยม"</string> diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml index 3c5448e20664..cf2b2f043963 100644 --- a/packages/SystemUI/res/values-tl/strings.xml +++ b/packages/SystemUI/res/values-tl/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"Ina-update"</string> <string name="status_bar_work" msgid="5238641949837091056">"Profile sa trabaho"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Airplane mode"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"Hindi mo maririnig ang iyong susunod na alarm ng <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template" msgid="2234991538018805736">"ng <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"sa <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satellite, malakas ang koneksyon"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satellite, may koneksyon"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Satellite SOS"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"Mga emergency na tawag o SOS lang"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"walang signal"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"isang bar"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"Bumalik"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"Mga Notification"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"Mga Keyboard Shortcut"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"Magpalit ng layout ng keyboard"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"o"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"I-clear ang query sa paghahanap"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"Mga Keyboard Shortcut"</string> @@ -1432,6 +1432,8 @@ <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_split_screen" msgid="1159669813444812244">"Split screen"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <string name="shortcut_helper_category_input" msgid="8674018654124839566">"Input"</string> <string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Mga shortcut ng app"</string> <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Kasalukuyang App"</string> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"I-customize ang mga shortcut"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Alisin ang shortcut?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"I-reset pabalik sa default?"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Pindutin ang key para magtalaga ng shortcut"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"Para gawin ang shortcut na ito, pindutin nang magkasabay ang Action key at isa o higit pang key"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Permanente nitong ide-delete ang iyong custom na shortcut."</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"Permanente nitong ide-delete ang lahat ng iyong custom na shortcut."</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Mga shortcut ng paghahanap"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"Oo, i-reset"</string> <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_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Ginagamit na ang kumbinasyon ng key. Sumubok ng ibang key."</string> + <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="3325858369539848162">"Ginagamit na ang kumbinasyon ng key. Sumubok ng ibang kumbinasyon."</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="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"Maglagay ng shortcut"</string> @@ -1476,6 +1478,7 @@ <string name="touchpad_tutorial_back_gesture_button" msgid="3104716365403620315">"Bumalik"</string> <string name="touchpad_tutorial_home_gesture_button" msgid="8023973153559885624">"Pumunta sa home"</string> <string name="touchpad_tutorial_recent_apps_gesture_button" msgid="8919227647650347359">"Tingnan ang mga kamakailang app"</string> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"Lumipat ng app"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"Tapos na"</string> <string name="gesture_error_title" msgid="469064941635578511">"Subukan ulit!"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"Bumalik"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"Magaling!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"Nakumpleto mo ang galaw sa pag-view ng mga kamakailang app."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"Para tingnan ang mga kamakailang app, mag-swipe pataas at i-hold gamit ang tatlong daliri sa iyong touchpad"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"Lumipat ng app"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"Magaling!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"Nakumpleto mo na ang galaw para magpalipat-lipat sa mga app."</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"Tingnan ang lahat ng app"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"Pindutin ang action key sa iyong keyboard"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"Magaling!"</string> diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml index 61ed4e54209b..8cd9eb36d180 100644 --- a/packages/SystemUI/res/values-tr/strings.xml +++ b/packages/SystemUI/res/values-tr/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"Güncelleniyor"</string> <string name="status_bar_work" msgid="5238641949837091056">"İş profili"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Uçak modu"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"<xliff:g id="WHEN">%1$s</xliff:g> olarak ayarlanmış bir sonraki alarmınızı duymayacaksınız"</string> <string name="alarm_template" msgid="2234991538018805736">"saat: <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"gün ve saat: <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Uydu, bağlantı güçlü"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Uydu, bağlantı mevcut"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Acil Uydu Bağlantısı"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"Yalnızca acil durum aramaları veya acil yardım"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"sinyal yok"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"tek çubuk"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"Geri"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"Bildirimler"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"Klavye Kısayolları"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"Klavye düzenini değiştir"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"veya"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"Arama sorgusunu temizle"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"Klavye Kısayolları"</string> @@ -1432,6 +1432,8 @@ <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_split_screen" msgid="1159669813444812244">"Bölünmüş ekran"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <string name="shortcut_helper_category_input" msgid="8674018654124839566">"Giriş"</string> <string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Uygulama kısayolları"</string> <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Mevcut Uygulama"</string> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"Kısayolları özelleştirin"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Kısayol kaldırılsın mı?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"Varsayılan kısayollara sıfırlansın mı?"</string> - <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_add_shortcut_description" msgid="7636040209946696120">"Bu kısayolu oluşturmak için Eylem tuşuna ve bir veya daha fazla başka tuşa aynı anda basın"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Bu işlem, özel kısayolunuzu kalıcı olarak siler."</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"Bu işlem, tüm özel kısayollarınızı kalıcı olarak siler."</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Kısayollarda ara"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"Sıfırla"</string> <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_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_key_combination_in_use_error_message" msgid="3325858369539848162">"Tuş kombinasyonu zaten kullanılıyor. Başka bir tuş kombinasyonu 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="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"Kısayol ekle"</string> @@ -1476,6 +1478,7 @@ <string name="touchpad_tutorial_back_gesture_button" msgid="3104716365403620315">"Geri dön"</string> <string name="touchpad_tutorial_home_gesture_button" msgid="8023973153559885624">"Ana sayfaya git"</string> <string name="touchpad_tutorial_recent_apps_gesture_button" msgid="8919227647650347359">"Son uygulamaları görüntüle"</string> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"Uygulamalar arasında geçiş yapma"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"Bitti"</string> <string name="gesture_error_title" msgid="469064941635578511">"Tekrar deneyin."</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"Geri dön"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"Tebrikler!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"Son uygulamaları görüntüleme hareketini tamamladınız."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"Son kullanılan uygulamaları görüntülemek için dokunmatik alanda üç parmağınızla yukarı kaydırıp basılı tutun"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"Uygulamalar arasında geçiş yapma"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"Tebrikler!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"Uygulamalar arasında geçiş yapma hareketini tamamladınız."</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"Tüm uygulamaları göster"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"Klavyenizde eylem tuşuna basın"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"Tebrikler!"</string> diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml index 778b25bf4651..bd877f3a4c7c 100644 --- a/packages/SystemUI/res/values-uk/strings.xml +++ b/packages/SystemUI/res/values-uk/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"Оновлення"</string> <string name="status_bar_work" msgid="5238641949837091056">"Робочий профіль"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Режим польоту"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"Наступний сигнал о <xliff:g id="WHEN">%1$s</xliff:g> не пролунає"</string> <string name="alarm_template" msgid="2234991538018805736">"<xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"<xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Хороше з’єднання із супутником"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Доступне з’єднання із супутником"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Супутниковий сигнал SOS"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"Лише екстрені виклики або сигнал SOS"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"немає сигналу"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"одна смужка сигналу"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"Назад"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"Сповіщення"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"Комбінації клавіш"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"Змінити розкладку клавіатури"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"або"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"Очистити пошуковий запит"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"Комбінації клавіш"</string> @@ -1290,7 +1290,7 @@ <string name="accessibility_enter_hint" msgid="2617864063504824834">"відкрити пристрій"</string> <string name="keyguard_try_fingerprint" msgid="2825130772993061165">"Щоб відкрити, використайте відбиток пальця"</string> <string name="accessibility_fingerprint_bouncer" msgid="7189102492498735519">"Пройдіть автентифікацію. Для цього торкніться сканера відбитків пальців."</string> - <string name="ongoing_call_content_description" msgid="6394763878322348560">"Поточний виклик"</string> + <string name="ongoing_call_content_description" msgid="6394763878322348560">"Поточний дзвінок"</string> <string name="mobile_data_settings_title" msgid="3955246641380064901">"Мобільний трафік"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"Підключено"</string> <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Тимчасово з’єднано"</string> @@ -1432,16 +1432,17 @@ <string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"Системні додатки"</string> <string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"Багатозадачність"</string> <string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"Розділити екран"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <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> - <!-- no translation found for shortcut_helper_customize_mode_title (8327297960035006036) --> - <skip /> + <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"Налаштуйте комбінації клавіш"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Видалити комбінацію клавіш?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"Відновити комбінації клавіш за умовчанням?"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Натисніть клавішу, щоб призначити комбінацію клавіш"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"Щоб створити цю комбінацію, одночасно натисніть клавішу дії і одну чи кілька інших клавіш"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Вашу власну комбінацію клавіш буде видалено назавжди."</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"Усі ваші власні комбінації клавіш буде видалено назавжди."</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Пошук комбінацій клавіш"</string> @@ -1463,13 +1464,11 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"Так, відновити"</string> <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_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Комбінація клавіш уже використовується. Спробуйте іншу клавішу."</string> + <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="3325858369539848162">"Комбінація клавіш уже використовується. Спробуйте іншу."</string> <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Не вдалося встановити комбінацію клавіш."</string> <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string> - <!-- no translation found for shortcut_helper_add_shortcut_button_label (7655779534665954910) --> - <skip /> - <!-- no translation found for shortcut_helper_delete_shortcut_button_label (3148773472696137052) --> - <skip /> + <string name="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"Додати комбінацію клавіш"</string> + <string name="shortcut_helper_delete_shortcut_button_label" msgid="3148773472696137052">"Видалити комбінацію клавіш"</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> @@ -1479,6 +1478,7 @@ <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> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"Перемикання між додатками"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"Готово"</string> <string name="gesture_error_title" msgid="469064941635578511">"Спробуйте ще"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"Назад"</string> @@ -1496,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"Чудово!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"Ви виконали жест для перегляду нещодавно відкритих додатків."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"Щоб переглянути останні додатки, проведіть трьома пальцями вгору й утримуйте їх на сенсорній панелі"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"Перемикання між додатками"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"Чудово!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"Ви виконали жест перемикання додатків."</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"Переглянути всі додатки"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"Натисніть клавішу дії на клавіатурі"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"Чудово!"</string> diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml index 2f3e6f4bcfe8..d3900f2be29f 100644 --- a/packages/SystemUI/res/values-ur/strings.xml +++ b/packages/SystemUI/res/values-ur/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"اپ ڈیٹ ہو رہا ہے"</string> <string name="status_bar_work" msgid="5238641949837091056">"دفتری پروفائل"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"ہوائی جہاز وضع"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"آپ کو <xliff:g id="WHEN">%1$s</xliff:g> بجے اپنا اگلا الارم سنائی نہیں دے گا"</string> <string name="alarm_template" msgid="2234991538018805736">"<xliff:g id="WHEN">%1$s</xliff:g> بجے"</string> <string name="alarm_template_far" msgid="3561752195856839456">"<xliff:g id="WHEN">%1$s</xliff:g> بجے"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"سیٹلائٹ، کنکشن اچھا ہے"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"سیٹلائٹ، کنکشن دستیاب ہے"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"سیٹلائٹ SOS"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"صرف ایمرجنسی کالز یا SOS"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>، <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>۔"</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"کوئی سگنل نہیں"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"ایک بار"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"پیچھے"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"اطلاعات"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"کی بورڈ شارٹ کٹس"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"کی بورڈ لے آؤٹ سوئچ کریں"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"یا"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"تلاش کا استفسار صاف کریں"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"کی بورڈ شارٹ کٹس"</string> @@ -1432,6 +1432,8 @@ <string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"سسٹم ایپس"</string> <string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"ملٹی ٹاسکنگ"</string> <string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"اسپلٹ اسکرین"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <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> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"شارٹ کٹس کو حسب ضرورت بنائیں"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"شارٹ کٹ ہٹائیں؟"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"ڈیفالٹ پر واپس ری سیٹ کریں؟"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"شارٹ کٹ تفویض کرنے کے لیے کلید کو دبائیں"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"یہ شارٹ کٹ تخلیق کرنے کے لیے، ایکشن کلید اور ایک یا زیادہ دوسری کلیدوں کو ایک ساتھ دبائیں"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"اس سے آپ کا حسب ضرورت شارٹ کٹ مستقل طور پر حذف ہو جائے گا۔"</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"اس سے آپ کے تمام حسب ضرورت شارٹ کٹس مستقل طور پر حذف ہو جائیں گے۔"</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"تلاش کے شارٹ کٹس"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"ہاں، ری سیٹ کریں"</string> <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_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"کلیدی مجموعہ پہلے سے استعمال میں ہے۔ دوسری کلید آزمائیں۔"</string> + <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="3325858369539848162">"کلیدی مجموعہ پہلے سے استعمال میں ہے۔ دوسرا مجموعہ آزمائیں۔"</string> <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"شارٹ کٹ سیٹ نہیں کیا جا سکتا۔"</string> <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string> <string name="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"شارٹ کٹ شامل کریں"</string> @@ -1476,6 +1478,7 @@ <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> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"ایپس سوئچ کریں"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"ہو گیا"</string> <string name="gesture_error_title" msgid="469064941635578511">"دوبارہ کوشش کریں!"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"واپس جائیں"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"بہترین!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"آپ نے حالیہ ایپس دیکھیں کا اشارہ مکمل کر لیا ہے۔"</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"حالیہ ایپس دیکھنے کے لیے، اپنے ٹچ پیڈ پر تین انگلیوں کی مدد سے اوپر کی طرف سوائپ کریں اور دبائے رکھیں"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"ایپس سوئچ کریں"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"بہترین!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"آپ نے ایپس کے مابین سوئچ کرنے کا اشارہ مکمل کر لیا۔"</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"سبھی ایپس دیکھیں"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"اپنے کی بورڈ پر ایکشن کلید دبائیں"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"بہت خوب!"</string> diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml index 23137bd9898c..7006abb34b99 100644 --- a/packages/SystemUI/res/values-uz/strings.xml +++ b/packages/SystemUI/res/values-uz/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"Yangilanmoqda"</string> <string name="status_bar_work" msgid="5238641949837091056">"Ish profili"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Parvoz rejimi"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"Keyingi signal (<xliff:g id="WHEN">%1$s</xliff:g>) chalinmaydi"</string> <string name="alarm_template" msgid="2234991538018805736">"<xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"<xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Sputnik, aloqa sifati yaxshi"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Sputnik, aloqa mavjud"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Sputnik SOS"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"Faqat favqulodda chaqiruvlar yoki SOS"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"signal yoʻq"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"bitta ustun"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"Orqaga"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"Bildirishnomalar"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"Tezkor tugmalar"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"Klaviatura terilmasini almashtirish"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"yoki"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"Qidiruv soʻrovini tozalash"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"Tezkor tugmalar"</string> @@ -1432,16 +1432,17 @@ <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_split_screen" msgid="1159669813444812244">"Ekranni ikkiga ajratish"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <string name="shortcut_helper_category_input" msgid="8674018654124839566">"Kiritish"</string> <string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Ilova yorliqlari"</string> <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Joriy ilova"</string> <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Qulayliklar"</string> <string name="shortcut_helper_title" msgid="8567500639300970049">"Tezkor tugmalar"</string> - <!-- no translation found for shortcut_helper_customize_mode_title (8327297960035006036) --> - <skip /> + <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"Yorliqlarni moslash"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Tezkor tugma olib tashlansinmi?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"Asliga qaytarilsinmi?"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Tezkor tugma sozlash uchun tugmani bosing"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"Bu tezkor tugmani yaratish uchun Amal tugmasi va bir yoki bir nechta tugmalarni birgalikda bosing"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Bunda maxsus tezkor tugma butunlay oʻchirib tashlanadi."</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"Bunda barcha maxsus yorliqlaringiz butunlay oʻchirib tashlanadi."</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Tezkor tugmalar qidiruvi"</string> @@ -1463,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"Ha, asliga qaytarilsin"</string> <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_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Bu tugmalar birikmasi band. Boshqasini ishlating."</string> + <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="3325858369539848162">"Bu tugmalar birikmasi band. Boshqa birikmani sinang."</string> <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"Buyruq sozlanmadi."</string> <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string> <string name="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"Yorliq yaratish"</string> @@ -1477,6 +1478,7 @@ <string name="touchpad_tutorial_back_gesture_button" msgid="3104716365403620315">"Orqaga"</string> <string name="touchpad_tutorial_home_gesture_button" msgid="8023973153559885624">"Boshiga qaytish"</string> <string name="touchpad_tutorial_recent_apps_gesture_button" msgid="8919227647650347359">"Oxirgi ilovalarni koʻrish"</string> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"Ilovalarni almashtirish"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"Tayyor"</string> <string name="gesture_error_title" msgid="469064941635578511">"Qayta urining!"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"Orqaga qaytish"</string> @@ -1494,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"Barakalla!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"Oxirgi ilovalarni koʻrish ishorasini tugalladingiz."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"Oxirgi ochilgan ilovalarni koʻrish uchun sensorli panelda uchta barmoq bilan tepega surib, ushlab turing"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"Ilovalarni almashtirish"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"Barakalla!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"Ilovalarni almashtirish darsini tamomladingiz."</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"Barcha ilovalarni koʻrish"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"Klaviaturadagi amal tugmasini bosing"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"Barakalla!"</string> diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml index 3b4b132698f3..18857426db84 100644 --- a/packages/SystemUI/res/values-vi/strings.xml +++ b/packages/SystemUI/res/values-vi/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"Đang cập nhật"</string> <string name="status_bar_work" msgid="5238641949837091056">"Hồ sơ công việc"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Chế độ trên máy bay"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"Bạn sẽ không nghe thấy chuông báo tiếp theo lúc <xliff:g id="WHEN">%1$s</xliff:g> của mình"</string> <string name="alarm_template" msgid="2234991538018805736">"lúc <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"vào <xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Kết nối vệ tinh tốt"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Hiện có kết nối vệ tinh"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Liên lạc khẩn cấp qua vệ tinh"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"Chỉ cuộc gọi khẩn cấp hoặc SOS"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"không có tín hiệu"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"1 vạch"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"Quay lại"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"Thông báo"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"Phím tắt"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"Chuyển đổi bố cục bàn phím"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"hoặc"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"Xoá cụm từ tìm kiếm"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"Phím tắt"</string> @@ -1432,6 +1432,8 @@ <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_split_screen" msgid="1159669813444812244">"Chia đôi màn hình"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <string name="shortcut_helper_category_input" msgid="8674018654124839566">"Phương thức nhập"</string> <string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Phím tắt cho ứng dụng"</string> <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Ứng dụng hiện tại"</string> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"Tuỳ chỉnh phím tắt"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Xoá phím tắt?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"Đặt lại về phím tắt mặc định?"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Nhấn phím để chỉ định phím tắt"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"Để tạo phím tắt này, hãy nhấn tổ hợp phím Hành động và một hoặc nhiều phím khác"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Thao tác này sẽ xoá vĩnh viễn phím tắt tuỳ chỉnh của bạn."</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"Thao tác này sẽ xoá vĩnh viễn mọi phím tắt tuỳ chỉnh của bạn."</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Tìm phím tắt"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"Có, đặt lại"</string> <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_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_key_combination_in_use_error_message" msgid="3325858369539848162">"Tổ hợp phím đã được sử dụng. Hãy thử một tổ hợp 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="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"Thêm phím tắt"</string> @@ -1476,6 +1478,7 @@ <string name="touchpad_tutorial_back_gesture_button" msgid="3104716365403620315">"Quay lại"</string> <string name="touchpad_tutorial_home_gesture_button" msgid="8023973153559885624">"Chuyển đến màn hình chính"</string> <string name="touchpad_tutorial_recent_apps_gesture_button" msgid="8919227647650347359">"Xem các ứng dụng gần đây"</string> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"Chuyển đổi ứng dụng"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"Xong"</string> <string name="gesture_error_title" msgid="469064941635578511">"Hãy thử lại!"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"Quay lại"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"Tuyệt vời!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"Bạn đã hoàn tất cử chỉ xem ứng dụng gần đây."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"Để xem các ứng dụng gần đây, hãy dùng 3 ngón tay vuốt lên và giữ trên bàn di chuột"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"Chuyển đổi ứng dụng"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"Tuyệt vời!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"Bạn đã thực hiện xong cử chỉ chuyển đổi ứng dụng."</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"Xem tất cả các ứng dụng"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"Nhấn phím hành động trên bàn phím"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"Rất tốt!"</string> diff --git a/packages/SystemUI/res/values-xlarge-land/config.xml b/packages/SystemUI/res/values-xlarge-land/config.xml index 5e4304e1c13a..6d8b64ade259 100644 --- a/packages/SystemUI/res/values-xlarge-land/config.xml +++ b/packages/SystemUI/res/values-xlarge-land/config.xml @@ -16,4 +16,5 @@ <resources> <item name="shortcut_helper_screen_width_fraction" format="float" type="dimen">0.8</item> + <bool name="center_align_magic_portrait_shape">true</bool> </resources> diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml index e66048e8022a..53c825baab02 100644 --- a/packages/SystemUI/res/values-zh-rCN/strings.xml +++ b/packages/SystemUI/res/values-zh-rCN/strings.xml @@ -83,7 +83,7 @@ <string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"必须先解锁设备,然后才能保存屏幕截图"</string> <string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"请再次尝试截屏"</string> <string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"无法保存屏幕截图"</string> - <string name="screenshot_failed_to_capture_text" msgid="7818288545874407451">"此应用或您所在的单位不允许进行屏幕截图"</string> + <string name="screenshot_failed_to_capture_text" msgid="7818288545874407451">"此应用或您所在的组织不允许截屏"</string> <string name="screenshot_blocked_by_admin" msgid="5486757604822795797">"您的 IT 管理员已禁止截取屏幕截图"</string> <string name="screenshot_edit_label" msgid="8754981973544133050">"编辑"</string> <string name="screenshot_edit_description" msgid="3333092254706788906">"编辑屏幕截图"</string> @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"正在更新"</string> <string name="status_bar_work" msgid="5238641949837091056">"工作资料"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"飞行模式"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"您在<xliff:g id="WHEN">%1$s</xliff:g>将不会听到下次闹钟响铃"</string> <string name="alarm_template" msgid="2234991538018805736">"<xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"<xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"卫星,连接质量良好"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"卫星,可连接"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"卫星紧急呼救"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"仅限紧急呼叫或紧急求救"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>,<xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>。"</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"无信号"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"信号强度为一格"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"返回"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"通知"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"键盘快捷键"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"切换键盘布局"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"或"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"清除搜索查询"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"键盘快捷键"</string> @@ -1432,6 +1432,8 @@ <string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"系统应用"</string> <string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"多任务处理"</string> <string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"分屏"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <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> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"自定义快捷方式"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"要移除快捷键吗?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"要重置为默认快捷键吗?"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"按下按键即可指定快捷键"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"若要创建此快捷方式,请同时按下快捷操作按键以及一个或多个其他键"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"此操作会永久删除您的自定义快捷键。"</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"此操作会永久删除您的所有自定义快捷键。"</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"搜索快捷键"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"是,重置"</string> <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_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"按键组合已被使用,请尝试使用其他按键。"</string> + <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="3325858369539848162">"按键组合已被使用,请尝试其他组合。"</string> <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"无法设置快捷方式。"</string> <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string> <string name="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"添加快捷方式"</string> @@ -1476,6 +1478,7 @@ <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> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"切换应用"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"完成"</string> <string name="gesture_error_title" msgid="469064941635578511">"再试一次!"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"返回"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"太棒了!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"您已完成“查看最近用过的应用”的手势教程。"</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"如需查看最近用过的应用,请用三根手指在触控板上向上滑动并按住"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"切换应用"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"太棒了!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"您完成了应用切换手势教程。"</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"查看所有应用"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"按键盘上的快捷操作按键"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"非常棒!"</string> diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml index f9631f267948..aa539d699378 100644 --- a/packages/SystemUI/res/values-zh-rHK/strings.xml +++ b/packages/SystemUI/res/values-zh-rHK/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"正在更新"</string> <string name="status_bar_work" msgid="5238641949837091056">"工作設定檔"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"飛行模式"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"你不會<xliff:g id="WHEN">%1$s</xliff:g>聽到鬧鐘"</string> <string name="alarm_template" msgid="2234991538018805736">"在 <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"在<xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"衛星,連線質素好"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"衛星,可以連線"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"緊急衛星連接"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"只限緊急電話或緊急求救"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>,<xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>。"</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"無訊號"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"一格"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"返回"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"通知"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"鍵盤快速鍵"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"切換鍵盤配置"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"或"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"清除搜尋查詢"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"鍵盤快速鍵"</string> @@ -1432,6 +1432,8 @@ <string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"系統應用程式"</string> <string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"多工處理"</string> <string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"分割螢幕"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <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> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"自訂快速鍵"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"要移除快速鍵嗎?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"要重設至預設捷徑嗎?"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"按鍵即可指派快速鍵"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"如要建立此快速鍵,請同時按下 Action 鍵及另外一個或多個鍵"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"這將永久刪除你的自訂快速鍵。"</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"這將永久刪除你的所有自訂捷徑。"</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"搜尋快速鍵"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"是,請重設"</string> <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_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"此按鍵組合已在使用,請改用其他按鍵。"</string> + <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="3325858369539848162">"此按鍵組合已在使用,請改用其他組合。"</string> <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"無法設定快速鍵。"</string> <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string> <string name="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"新增捷徑"</string> @@ -1476,6 +1478,7 @@ <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> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"切換應用程式"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"完成"</string> <string name="gesture_error_title" msgid="469064941635578511">"請再試一次!"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"返回"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"做得好!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"你已完成「查看最近使用的應用程式」手勢的教學課程。"</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"如要查看最近使用的應用程式,請用三隻手指在觸控板上向上滑動並按住"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"切換應用程式"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"做得好!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"你已完成「應用程式切換手勢」的教學課程。"</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"查看所有應用程式"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"按下鍵盤上的快捷操作鍵"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"做得好!"</string> diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml index 4394dd9a0bd7..2d6ac8d43442 100644 --- a/packages/SystemUI/res/values-zh-rTW/strings.xml +++ b/packages/SystemUI/res/values-zh-rTW/strings.xml @@ -750,6 +750,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"更新中"</string> <string name="status_bar_work" msgid="5238641949837091056">"工作資料夾"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"飛航模式"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"你不會聽到下一個<xliff:g id="WHEN">%1$s</xliff:g> 的鬧鐘"</string> <string name="alarm_template" msgid="2234991538018805736">"於<xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"於<xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -759,8 +761,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"衛星,連線品質良好"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"衛星,可連線"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"緊急衛星連線"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"僅限緊急電話或需要求救的情況"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>,<xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>。"</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"沒有訊號"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"訊號強度一格"</string> @@ -857,7 +858,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"返回"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"通知"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"鍵盤快速鍵"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"切換鍵盤配置"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"或"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"清除搜尋查詢"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"鍵盤快速鍵"</string> @@ -1432,6 +1432,8 @@ <string name="shortcut_helper_category_system_apps" msgid="6001757545472556810">"系統應用程式"</string> <string name="shortcut_helper_category_multitasking" msgid="7413381961404090136">"多工處理"</string> <string name="shortcutHelper_category_split_screen" msgid="1159669813444812244">"分割畫面"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <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> @@ -1440,7 +1442,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"自訂快速鍵"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"要移除快速鍵嗎?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"要重設為預設值嗎?"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"按下按鍵即可指派快速鍵"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"如要建立這個快速鍵,請同時按下快捷操作鍵和一或多個其他鍵"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"這項操作會永久刪除自訂快速鍵。"</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"這麼做會永久刪除所有快速鍵。"</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"搜尋快速鍵"</string> @@ -1462,7 +1464,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"是,請重設"</string> <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_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"按鍵組合重複,請改用其他按鍵。"</string> + <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="3325858369539848162">"這組按鍵已在使用中,請試試其他組合。"</string> <string name="shortcut_customizer_generic_error_message" msgid="3128454624049722741">"無法設定捷徑。"</string> <string name="shortcut_helper_plus_symbol" msgid="4534843157353732011">"+"</string> <string name="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"新增快速鍵"</string> @@ -1476,6 +1478,7 @@ <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> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"切換應用程式"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"完成"</string> <string name="gesture_error_title" msgid="469064941635578511">"請再試一次!"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"返回"</string> @@ -1493,6 +1496,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"太棒了!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"你已完成「查看最近使用的應用程式」手勢教學課程。"</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"如要查看最近使用的應用程式,請在觸控板上用三指向上滑動並按住"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"切換應用程式"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"太棒了!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"你已完成應用程式切換手勢的教學課程。"</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"查看所有應用程式"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"按下鍵盤上的快捷操作鍵"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"非常好!"</string> diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml index f7f4bcfcdc83..a210ca37214f 100644 --- a/packages/SystemUI/res/values-zu/strings.xml +++ b/packages/SystemUI/res/values-zu/strings.xml @@ -752,6 +752,8 @@ <string name="qr_code_scanner_updating_secondary_label" msgid="8344598017007876352">"Iyabuyekeza"</string> <string name="status_bar_work" msgid="5238641949837091056">"Iphrofayela yomsebenzi"</string> <string name="status_bar_airplane" msgid="4848702508684541009">"Imodi yendiza"</string> + <!-- no translation found for status_bar_supervision (6735015942701134125) --> + <skip /> <string name="zen_alarm_warning" msgid="7844303238486849503">"Ngeke uzwe i-alamu yakho elandelayo ngo-<xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template" msgid="2234991538018805736">"ngo-<xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="alarm_template_far" msgid="3561752195856839456">"nge-<xliff:g id="WHEN">%1$s</xliff:g>"</string> @@ -761,8 +763,7 @@ <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Isethelayithi, uxhumano oluhle"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Isethelayithi, uxhumano luyatholakala"</string> <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Isethelayithi yokuxhumana ngezimo eziphuthumayo"</string> - <!-- no translation found for satellite_emergency_only_carrier_text (9103913890116841786) --> - <skip /> + <string name="satellite_emergency_only_carrier_text" msgid="9103913890116841786">"Izingcingo eziphuthumayo noma i-SOS kuphela"</string> <string name="accessibility_phone_string_format" msgid="7798841417881811812">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="SIGNAL_STRENGTH_DESCRIPTION">%2$s</xliff:g>."</string> <string name="accessibility_no_signal" msgid="7052827511409250167">"ayikho isignali"</string> <string name="accessibility_one_bar" msgid="5342012847647834506">"ibha eyodwa"</string> @@ -859,7 +860,6 @@ <string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"Emuva"</string> <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"Izaziso"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"Izinqamulelo Zekhibhodi"</string> - <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"Shintsha isakhiwo sekhibhodi"</string> <string name="keyboard_shortcut_join" msgid="3578314570034512676">"noma"</string> <string name="keyboard_shortcut_clear_text" msgid="6631051796030377857">"Sula umbuzo wosesho"</string> <string name="keyboard_shortcut_search_list_title" msgid="4271769465397671138">"Izinqamuleli Zekhibhodi"</string> @@ -1434,6 +1434,8 @@ <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_split_screen" msgid="1159669813444812244">"Hlukanisa isikrini"</string> + <!-- no translation found for shortcutHelper_category_accessibility (8068337792277570938) --> + <skip /> <string name="shortcut_helper_category_input" msgid="8674018654124839566">"Okokufaka"</string> <string name="shortcut_helper_category_app_shortcuts" msgid="8010249408308587117">"Izinqamuleli Zohlelo lokusebenza"</string> <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"I-App yamanje"</string> @@ -1442,7 +1444,7 @@ <string name="shortcut_helper_customize_mode_title" msgid="8327297960035006036">"Qamba ngokwabahlinzekelwayo izinqamuleli"</string> <string name="shortcut_customize_mode_remove_shortcut_dialog_title" msgid="7106420484940737208">"Susa isinqamuleli?"</string> <string name="shortcut_customize_mode_reset_shortcut_dialog_title" msgid="8131184731313717780">"Setha kabusha ubuyele kokuzenzakalelayo?"</string> - <string name="shortcut_customize_mode_add_shortcut_description" msgid="6866025005347407696">"Cindezela ukhiye ukuze unikeze isinqamuleli"</string> + <string name="shortcut_customize_mode_add_shortcut_description" msgid="7636040209946696120">"Ukuze usungule lesi sinqamuleli, cindezela ukhiye Wesenzo kanye nokhiye owodwa noma ngaphezulu ndawonye"</string> <string name="shortcut_customize_mode_remove_shortcut_description" msgid="6851287900585057128">"Lokhu kuzosula isinqamuleli sakho somuntu ngamunye unomphela."</string> <string name="shortcut_customize_mode_reset_shortcut_description" msgid="2081849715634358684">"Lokhu kuzosula unomphela zonke izinqamuleli zakho zangokwezifiso."</string> <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Sesha izinqamuleli"</string> @@ -1464,7 +1466,7 @@ <string name="shortcut_helper_customize_dialog_reset_button_label" msgid="7645535254306312685">"Yebo, setha kabusha"</string> <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_customizer_key_combination_in_use_error_message" msgid="7693234470526626327">"Inhlanganisela yokhiye isiyasetshenziswa kakade. Zama omunye ukhiye."</string> + <string name="shortcut_customizer_key_combination_in_use_error_message" msgid="3325858369539848162">"Inhlanganisela yokhiye isiyasetshenziswa kakade. Zama enye inhlanganisela."</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="shortcut_helper_add_shortcut_button_label" msgid="7655779534665954910">"Faka isinqamuleli"</string> @@ -1478,6 +1480,7 @@ <string name="touchpad_tutorial_back_gesture_button" msgid="3104716365403620315">"Iya emuva"</string> <string name="touchpad_tutorial_home_gesture_button" msgid="8023973153559885624">"Iya ekhasini lokuqala"</string> <string name="touchpad_tutorial_recent_apps_gesture_button" msgid="8919227647650347359">"Buka ama-app akamuva"</string> + <string name="touchpad_tutorial_switch_apps_gesture_button" msgid="7768255095423767779">"Shintsha ama-app"</string> <string name="touchpad_tutorial_done_button" msgid="176168488821755503">"Kwenziwe"</string> <string name="gesture_error_title" msgid="469064941635578511">"Zama futhi!"</string> <string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"Buyela emuva"</string> @@ -1495,6 +1498,13 @@ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"Umsebenzi omuhle!"</string> <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"Uqedele ukubuka ukuthinta kwama-app akamuva."</string> <string name="touchpad_recent_gesture_error_body" msgid="8695535720378462022">"Ukuze ubuke ama-app akamuva, swayiphela phezulu futhi ubambe usebenzisa iminwe emithathu kuphedi yakho yokuthinta"</string> + <string name="touchpad_switch_apps_gesture_action_title" msgid="6835222344612924512">"Shintsha ama-app"</string> + <!-- no translation found for touchpad_switch_apps_gesture_guidance (2751565200937541667) --> + <skip /> + <string name="touchpad_switch_apps_gesture_success_title" msgid="4894947244328032458">"Umsebenzi omuhle!"</string> + <string name="touchpad_switch_apps_gesture_success_body" msgid="8151089866035126312">"Ukuqedile ukuthinta kokushintsha ama-app."</string> + <!-- no translation found for touchpad_switch_gesture_error_body (5508381152326379652) --> + <skip /> <string name="tutorial_action_key_title" msgid="8172535792469008169">"Buka wonke ama-app"</string> <string name="tutorial_action_key_guidance" msgid="5040613427202799294">"Cindezela inkinobho yokufinyelela kukhibhodi yakho"</string> <string name="tutorial_action_key_success_title" msgid="2371827347071979571">"Wenze kahle!"</string> diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index d2b7d0b90c43..36ede64f91d9 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -31,8 +31,14 @@ <!-- The dark background color behind the shade --> <color name="shade_scrim_background_dark">@androidprv:color/system_under_surface_light</color> - <!-- Colors for notification shade/scrim --> - <color name="shade_panel">@android:color/system_accent1_800</color> + <!-- Base colors for notification shade/scrim, the alpha component is adjusted programmatically + to match the spec --> + <color name="shade_panel">@android:color/system_accent1_900</color> + <color name="surface_effect_0">@android:color/system_accent1_100</color> + + <!-- todo(b/388891904) Remove updated color references once they are available. --> + <color name="shade_panel_base">@color/shade_panel</color> + <color name="notification_scrim_base">@color/surface_effect_0</color> <!-- The color of the background in the separated list of the Global Actions menu --> <color name="global_actions_separated_background">#F5F5F5</color> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 940e87d3d163..68e33f27aefa 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -1104,7 +1104,10 @@ --> <bool name="config_userSwitchingMustGoThroughLoginScreen">false</bool> - <!-- The dream component used when the device is low light environment. --> <string translatable="false" name="config_lowLightDreamComponent"/> + + <!--Whether we should position magic portrait shape effects in the center of lockscreen + it's false by default, and only be true in tablet landscape --> + <bool name="center_align_magic_portrait_shape">false</bool> </resources> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index f9904e336f24..724a12c12490 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -266,6 +266,9 @@ <!-- Height of a large notification in the status bar --> <dimen name="notification_max_height">358dp</dimen> + <!-- Height of a large promoted ongoing notification in the status bar --> + <dimen name="notification_max_height_for_promoted_ongoing">272dp</dimen> + <!-- Height of a heads up notification in the status bar for legacy custom views --> <dimen name="notification_max_heads_up_height_legacy">128dp</dimen> @@ -2057,6 +2060,8 @@ <dimen name="dream_overlay_status_bar_ambient_text_shadow_dy">0.5dp</dimen> <dimen name="dream_overlay_status_bar_ambient_text_shadow_radius">3dp</dimen> <dimen name="dream_overlay_icon_inset_dimen">0dp</dimen> + <dimen name="dream_overlay_icon_shadow_radius">1dp</dimen> + <dimen name="dream_overlay_icon_ambient_shadow_radius">2dp</dimen> <!-- Default device corner radius, used for assist UI --> <dimen name="config_rounded_mask_size">0px</dimen> diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml index c06b0784092c..dc089e707ca3 100644 --- a/packages/SystemUI/res/values/ids.xml +++ b/packages/SystemUI/res/values/ids.xml @@ -218,6 +218,7 @@ <item type="id" name="accessibility_actions_view" /> <item type="id" name="ambient_indication_container" /> <item type="id" name="aod_notification_icon_container" /> + <item type="id" name="aod_promoted_notification_frame" /> <item type="id" name="burn_in_layer" /> <item type="id" name="burn_in_layer_empty_view" /> <item type="id" name="communal_tutorial_indicator" /> @@ -291,4 +292,5 @@ <item type="id" name="brightness_dialog_slider" /> + <item type="id" name="aod_promoted_notification_view_updater_tag" /> </resources> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index c800b0374155..64367ef79856 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1351,14 +1351,14 @@ <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> - <!-- 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 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> <!-- Content description for a "show screensaver" button on glanceable hub. [CHAR LIMIT=NONE] --> <string name="accessibility_glanceable_hub_to_dream_button">Show screensaver button</string> + <!-- Title shown in hub onboarding bottom sheet. [CHAR LIMIT=50] --> + <string name="hub_onboarding_bottom_sheet_title">Explore hub mode</string> + <!-- Information about communal hub shown in the onboarding bottom sheet. [CHAR LIMIT=NONE] --> + <string name="hub_onboarding_bottom_sheet_text">Access your favorite widgets and screen savers while charging.</string> + <!-- Hub onboarding bottom sheet action button title. [CHAR LIMIT=NONE] --> + <string name="hub_onboarding_bottom_sheet_action_button">Let\u2019s go</string> <!-- Related to user switcher --><skip/> @@ -2083,6 +2083,12 @@ <!-- [CHAR LIMIT=80] Text shown in feedback button in notification guts for a bundled notification --> <string name="notification_guts_bundle_feedback" translatable="false">Provide Bundle Feedback</string> + <!-- [CHAR LIMIT=30] Text shown in button used to dismiss this single notification. --> + <string name="notification_inline_dismiss">Dismiss</string> + + <!-- [CHAR LIMIT=30] Text shown in button used to prevent app from showing Live Updates, for this notification and all future ones --> + <string name="notification_inline_disable_promotion">Don\'t show again</string> + <!-- Notification: Control panel: Label that displays when the app's notifications cannot be blocked. --> <string name="notification_unblockable_desc">These notifications can\'t be modified.</string> @@ -2310,9 +2316,6 @@ <string name="group_system_lock_screen">Lock screen</string> <!-- User visible title for the keyboard shortcut that pulls up Notes app for quick memo. [CHAR LIMIT=70] --> <string name="group_system_quick_memo">Take a note</string> - <!-- TODO(b/383734125): make it translatable once string is finalized by UXW.--> - <!-- User visible title for the keyboard shortcut that toggles Voice Access. [CHAR LIMIT=70] --> - <string name="group_system_toggle_voice_access" translatable="false">Toggle Voice Access</string> <!-- User visible title for the multitasking keyboard shortcuts list. [CHAR LIMIT=70] --> <string name="keyboard_shortcut_group_system_multitasking">Multitasking</string> @@ -2371,6 +2374,17 @@ <!-- User visible title for the keyboard shortcut that takes the user to the maps app. [CHAR LIMIT=70] --> <string name="keyboard_shortcut_group_applications_maps">Maps</string> + <!-- User visible title for the keyboard shortcut that toggles bounce keys. [CHAR LIMIT=70]--> + <string name="group_accessibility_toggle_bounce_keys">Toggle bounce keys</string> + <!-- User visible title for the keyboard shortcut that toggles mouse keys. [CHAR LIMIT=70]--> + <string name="group_accessibility_toggle_mouse_keys">Toggle mouse keys</string> + <!-- User visible title for the keyboard shortcut that toggles sticky keys. [CHAR LIMIT=70]--> + <string name="group_accessibility_toggle_sticky_keys">Toggle sticky keys</string> + <!-- User visible title for the keyboard shortcut that toggles slow keys. [CHAR LIMIT=70]--> + <string name="group_accessibility_toggle_slow_keys">Toggle slow keys</string> + <!-- User visible title for the keyboard shortcut that toggles Voice Access. [CHAR LIMIT=70] --> + <string name="group_accessibility_toggle_voice_access">Toggle Voice Access</string> + <!-- SysUI Tuner: Label for screen about do not disturb settings [CHAR LIMIT=60] --> <string name="volume_and_do_not_disturb">Do Not Disturb</string> @@ -3779,6 +3793,11 @@ 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] --> <string name="shortcutHelper_category_split_screen">Split screen</string> + <!-- Title of the keyboard shortcut helper category "Accessibility". This category contains shortcuts + for android accessibility and disability inclusion features such as screen readers, voice control, + switch access, talkback, etc. The helper is a component that shows the user which keyboard + shortcuts they can use. [CHAR LIMIT=NONE] --> + <string name="shortcutHelper_category_accessibility">Accessibility</string> <!-- Title of the keyboard shortcut helper category "Input". The helper is a component that shows the user which keyboard shortcuts they can use. The "Input" shortcuts are the ones provided by the keyboard. Examples are "Access emoji" or "Switch to next language" @@ -4000,13 +4019,13 @@ <!-- Touchpad switch apps gesture action name in tutorial [CHAR LIMIT=NONE] --> <string name="touchpad_switch_apps_gesture_action_title">Switch apps</string> <!-- Touchpad switch apps gesture guidance in gestures tutorial [CHAR LIMIT=NONE] --> - <string name="touchpad_switch_apps_gesture_guidance">Swipe left using four fingers on your touchpad</string> + <string name="touchpad_switch_apps_gesture_guidance">Swipe right using four fingers on your touchpad</string> <!-- Screen title after switch apps gesture was done successfully [CHAR LIMIT=NONE] --> <string name="touchpad_switch_apps_gesture_success_title">Great job!</string> <!-- Text shown to the user after they complete switch apps gesture tutorial [CHAR LIMIT=NONE] --> <string name="touchpad_switch_apps_gesture_success_body">You completed the switch apps gesture.</string> <!-- Text shown to the user after switch gesture was not done correctly [CHAR LIMIT=NONE] --> - <string name="touchpad_switch_gesture_error_body">Swipe left using four fingers on your touchpad to switch apps</string> + <string name="touchpad_switch_gesture_error_body">Swipe right using four fingers on your touchpad to switch apps</string> <!-- KEYBOARD TUTORIAL--> <!-- Action key tutorial title [CHAR LIMIT=NONE] --> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 08891aa65417..c95c6dd89353 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -430,6 +430,7 @@ <style name="Theme.SystemUI.MediaProjectionAppSelector" parent="@*android:style/Theme.DeviceDefault.Chooser"> + <item name="android:colorBackground">@*android:color/materialColorSurfaceContainer</item> </style> <!-- Standard animations for hiding and showing the status bar. --> @@ -564,16 +565,6 @@ <item name="android:windowNoTitle">true</item> </style> - <style name="SystemUI.Material3.Slider.Volume"> - <item name="trackHeight">40dp</item> - <item name="thumbHeight">52dp</item> - <item name="trackCornerSize">12dp</item> - <item name="trackInsideCornerSize">2dp</item> - <item name="trackStopIndicatorSize">6dp</item> - <item name="trackIconSize">20dp</item> - <item name="labelBehavior">gone</item> - </style> - <style name="SystemUI.Material3.Slider" parent="@style/Widget.Material3.Slider"> <item name="labelStyle">@style/Widget.Material3.Slider.Label</item> <item name="thumbColor">@color/thumb_color</item> diff --git a/packages/SystemUI/res/xml/gradient_color_wallpaper.xml b/packages/SystemUI/res/xml/gradient_color_wallpaper.xml new file mode 100644 index 000000000000..f453a4450750 --- /dev/null +++ b/packages/SystemUI/res/xml/gradient_color_wallpaper.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<wallpaper + xmlns:android="http://schemas.android.com/apk/res/android" + android:showMetadataInPreview="false" + android:supportsMultipleDisplays="true" />
\ No newline at end of file diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl index d363e524a9f2..011458859db4 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl @@ -23,7 +23,7 @@ import android.os.IRemoteCallback; import android.view.MotionEvent; import com.android.systemui.shared.recents.ISystemUiProxy; -// Next ID: 38 +// Next ID: 39 oneway interface IOverviewProxy { void onActiveNavBarRegionChanges(in Region activeRegion) = 11; @@ -154,4 +154,9 @@ oneway interface IOverviewProxy { * Sent when {@link TaskbarDelegate#onDisplayRemoved} is called. */ void onDisplayRemoved(int displayId) = 37; + + /** + * Sent when {@link TaskbarDelegate#onDisplayRemoveSystemDecorations} is called. + */ + void onDisplayRemoveSystemDecorations(int displayId) = 38; } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java index 8576a6ebac42..a518c57bdd16 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java @@ -72,6 +72,25 @@ public class Task { @ViewDebug.ExportedProperty(category = "recents") public final int displayId; + /** + * The component of the first activity in the task, can be considered the "application" of + * this task. + */ + @Nullable + public ComponentName baseActivity; + /** + * The number of activities in this task (including running). + */ + public int numActivities; + /** + * Whether the top activity is to be displayed. See {@link android.R.attr#windowNoDisplay}. + */ + public boolean isTopActivityNoDisplay; + /** + * Whether fillsParent() is false for every activity in the tasks stack. + */ + public boolean isActivityStackTransparent; + // The source component name which started this task public final ComponentName sourceComponent; @@ -90,6 +109,10 @@ public class Task { this.userId = t.userId; this.lastActiveTime = t.lastActiveTime; this.displayId = t.displayId; + this.baseActivity = t.baseActivity; + this.numActivities = t.numActivities; + this.isTopActivityNoDisplay = t.isTopActivityNoDisplay; + this.isActivityStackTransparent = t.isActivityStackTransparent; updateHashCode(); } @@ -106,7 +129,9 @@ public class Task { } public TaskKey(int id, int windowingMode, @NonNull Intent intent, - ComponentName sourceComponent, int userId, long lastActiveTime, int displayId) { + ComponentName sourceComponent, int userId, long lastActiveTime, int displayId, + @Nullable ComponentName baseActivity, int numActivities, + boolean isTopActivityNoDisplay, boolean isActivityStackTransparent) { this.id = id; this.windowingMode = windowingMode; this.baseIntent = intent; @@ -114,6 +139,10 @@ public class Task { this.userId = userId; this.lastActiveTime = lastActiveTime; this.displayId = displayId; + this.baseActivity = baseActivity; + this.numActivities = numActivities; + this.isTopActivityNoDisplay = isTopActivityNoDisplay; + this.isActivityStackTransparent = isActivityStackTransparent; updateHashCode(); } @@ -185,6 +214,10 @@ public class Task { parcel.writeLong(lastActiveTime); parcel.writeInt(displayId); parcel.writeTypedObject(sourceComponent, flags); + parcel.writeTypedObject(baseActivity, flags); + parcel.writeInt(numActivities); + parcel.writeBoolean(isTopActivityNoDisplay); + parcel.writeBoolean(isActivityStackTransparent); } private static TaskKey readFromParcel(Parcel parcel) { @@ -195,9 +228,14 @@ public class Task { long lastActiveTime = parcel.readLong(); int displayId = parcel.readInt(); ComponentName sourceComponent = parcel.readTypedObject(ComponentName.CREATOR); + ComponentName baseActivity = parcel.readTypedObject(ComponentName.CREATOR); + int numActivities = parcel.readInt(); + boolean isTopActivityNoDisplay = parcel.readBoolean(); + boolean isActivityStackTransparent = parcel.readBoolean(); return new TaskKey(id, windowingMode, baseIntent, sourceComponent, userId, - lastActiveTime, displayId); + lastActiveTime, displayId, baseActivity, numActivities, isTopActivityNoDisplay, + isActivityStackTransparent); } @Override diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java index 41ad4373455e..32a76fb8439a 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java @@ -17,10 +17,11 @@ package com.android.systemui.shared.recents.utilities; import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT; -import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN; -import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_SHOWN; +import static android.app.StatusBarManager.NAVIGATION_HINT_IME_VISIBLE; +import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_BUTTON_VISIBLE; import android.annotation.TargetApi; +import android.app.StatusBarManager.NavigationHint; import android.content.Context; import android.content.res.Resources; import android.graphics.Color; @@ -103,17 +104,25 @@ public class Utilities { } /** - * @return updated set of flags from InputMethodService based off {@param oldHints} - * Leaves original hints unmodified + * Gets the updated navigation icon hints, based on the current ones and the given IME state. + * + * @param oldHints current navigation icon hints. + * @param backDisposition the IME back disposition mode. Only takes effect if + * {@code isImeVisible} is {@code true}. + * @param isImeVisible whether the IME is currently visible. + * @param showImeSwitcher whether the IME Switcher button should be shown. Only takes effect if + * {@code isImeVisible} is {@code true}. */ - public static int calculateBackDispositionHints(int oldHints, - @BackDispositionMode int backDisposition, boolean imeShown, boolean showImeSwitcher) { + @NavigationHint + public static int calculateNavigationIconHints(@NavigationHint int oldHints, + @BackDispositionMode int backDisposition, boolean isImeVisible, + boolean showImeSwitcher) { int hints = oldHints; switch (backDisposition) { case InputMethodService.BACK_DISPOSITION_DEFAULT: case InputMethodService.BACK_DISPOSITION_WILL_NOT_DISMISS: case InputMethodService.BACK_DISPOSITION_WILL_DISMISS: - if (imeShown) { + if (isImeVisible) { hints |= NAVIGATION_HINT_BACK_ALT; } else { hints &= ~NAVIGATION_HINT_BACK_ALT; @@ -123,15 +132,15 @@ public class Utilities { hints &= ~NAVIGATION_HINT_BACK_ALT; break; } - if (imeShown) { - hints |= NAVIGATION_HINT_IME_SHOWN; + if (isImeVisible) { + hints |= NAVIGATION_HINT_IME_VISIBLE; } else { - hints &= ~NAVIGATION_HINT_IME_SHOWN; + hints &= ~NAVIGATION_HINT_IME_VISIBLE; } - if (showImeSwitcher) { - hints |= NAVIGATION_HINT_IME_SWITCHER_SHOWN; + if (showImeSwitcher && isImeVisible) { + hints |= NAVIGATION_HINT_IME_SWITCHER_BUTTON_VISIBLE; } else { - hints &= ~NAVIGATION_HINT_IME_SWITCHER_SHOWN; + hints &= ~NAVIGATION_HINT_IME_SWITCHER_BUTTON_VISIBLE; } return hints; diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextView.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextView.kt index bd20777c7102..e928525afc14 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextView.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextView.kt @@ -16,6 +16,7 @@ package com.android.systemui.shared.shadow import android.content.Context +import android.content.res.TypedArray import android.graphics.Canvas import android.graphics.drawable.Drawable import android.util.AttributeSet @@ -31,19 +32,23 @@ constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0, - defStyleRes: Int = 0 + defStyleRes: Int = 0, ) : TextView(context, attrs, defStyleAttr, defStyleRes) { - private val mKeyShadowInfo: ShadowInfo - private val mAmbientShadowInfo: ShadowInfo + private lateinit var mKeyShadowInfo: ShadowInfo + private lateinit var mAmbientShadowInfo: ShadowInfo init { - val attributes = + updateShadowDrawables( context.obtainStyledAttributes( attrs, R.styleable.DoubleShadowTextView, defStyleAttr, - defStyleRes + defStyleRes, ) + ) + } + + private fun updateShadowDrawables(attributes: TypedArray) { val drawableSize: Int val drawableInsetSize: Int try { @@ -70,17 +75,17 @@ constructor( ambientShadowBlur, ambientShadowOffsetX, ambientShadowOffsetY, - ambientShadowAlpha + ambientShadowAlpha, ) drawableSize = attributes.getDimensionPixelSize( R.styleable.DoubleShadowTextView_drawableIconSize, - 0 + 0, ) drawableInsetSize = attributes.getDimensionPixelSize( R.styleable.DoubleShadowTextView_drawableIconInsetSize, - 0 + 0, ) } finally { attributes.recycle() @@ -95,12 +100,19 @@ constructor( mAmbientShadowInfo, drawable, drawableSize, - drawableInsetSize + drawableInsetSize, ) } setCompoundDrawablesRelative(drawables[0], drawables[1], drawables[2], drawables[3]) } + override fun setTextAppearance(resId: Int) { + super.setTextAppearance(resId) + updateShadowDrawables( + context.obtainStyledAttributes(resId, R.styleable.DoubleShadowTextView) + ) + } + public override fun onDraw(canvas: Canvas) { applyShadows(mKeyShadowInfo, mAmbientShadowInfo, this, canvas) { super.onDraw(canvas) } } 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 6f13d637d5c5..0048fd4d33d3 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 @@ -98,12 +98,12 @@ public class QuickStepContract { public static final long SYSUI_STATE_ONE_HANDED_ACTIVE = 1L << 16; // Allow system gesture no matter the system bar(s) is visible or not public static final long SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY = 1L << 17; - // The IME is showing - public static final long SYSUI_STATE_IME_SHOWING = 1L << 18; + // The IME is visible. + public static final long SYSUI_STATE_IME_VISIBLE = 1L << 18; // The window magnification is overlapped with system gesture insets at the bottom. public static final long SYSUI_STATE_MAGNIFICATION_OVERLAP = 1L << 19; - // ImeSwitcher is showing - public static final long SYSUI_STATE_IME_SWITCHER_SHOWING = 1L << 20; + // The IME Switcher button is visible. + public static final long SYSUI_STATE_IME_SWITCHER_BUTTON_VISIBLE = 1L << 20; // Device dozing/AOD state public static final long SYSUI_STATE_DEVICE_DOZING = 1L << 21; // The home feature is disabled (either by SUW/SysUI/device policy) @@ -134,6 +134,8 @@ public class QuickStepContract { public static final long SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING = 1L << 34; // Communal hub is showing public static final long SYSUI_STATE_COMMUNAL_HUB_SHOWING = 1L << 35; + // The back button is adjusted for the IME. + public static final long SYSUI_STATE_IME_ALT_BACK = 1L << 36; // Mask for SystemUiStateFlags to isolate SYSUI_STATE_AWAKE and // SYSUI_STATE_WAKEFULNESS_TRANSITION, to match WAKEFULNESS_* constants @@ -168,9 +170,9 @@ public class QuickStepContract { SYSUI_STATE_DIALOG_SHOWING, SYSUI_STATE_ONE_HANDED_ACTIVE, SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY, - SYSUI_STATE_IME_SHOWING, + SYSUI_STATE_IME_VISIBLE, SYSUI_STATE_MAGNIFICATION_OVERLAP, - SYSUI_STATE_IME_SWITCHER_SHOWING, + SYSUI_STATE_IME_SWITCHER_BUTTON_VISIBLE, SYSUI_STATE_DEVICE_DOZING, SYSUI_STATE_BACK_DISABLED, SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED, @@ -185,6 +187,7 @@ public class QuickStepContract { SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED, SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING, SYSUI_STATE_COMMUNAL_HUB_SHOWING, + SYSUI_STATE_IME_ALT_BACK, }) public @interface SystemUiStateFlags {} @@ -244,14 +247,14 @@ public class QuickStepContract { if ((flags & SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY) != 0) { str.add("allow_gesture"); } - if ((flags & SYSUI_STATE_IME_SHOWING) != 0) { + if ((flags & SYSUI_STATE_IME_VISIBLE) != 0) { str.add("ime_visible"); } if ((flags & SYSUI_STATE_MAGNIFICATION_OVERLAP) != 0) { str.add("magnification_overlap"); } - if ((flags & SYSUI_STATE_IME_SWITCHER_SHOWING) != 0) { - str.add("ime_switcher_showing"); + if ((flags & SYSUI_STATE_IME_SWITCHER_BUTTON_VISIBLE) != 0) { + str.add("ime_switcher_button_visible"); } if ((flags & SYSUI_STATE_DEVICE_DOZING) != 0) { str.add("device_dozing"); @@ -295,6 +298,9 @@ public class QuickStepContract { if ((flags & SYSUI_STATE_COMMUNAL_HUB_SHOWING) != 0) { str.add("communal_hub_showing"); } + if ((flags & SYSUI_STATE_IME_ALT_BACK) != 0) { + str.add("ime_alt_back"); + } return str.toString(); } diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierText.java b/packages/SystemUI/src/com/android/keyguard/CarrierText.java index ae282c7e14bd..e2ac6a494020 100644 --- a/packages/SystemUI/src/com/android/keyguard/CarrierText.java +++ b/packages/SystemUI/src/com/android/keyguard/CarrierText.java @@ -22,6 +22,7 @@ import android.text.TextUtils; import android.text.method.SingleLineTransformationMethod; import android.util.AttributeSet; import android.view.View; +import android.view.accessibility.AccessibilityNodeInfo; import android.widget.TextView; import com.android.systemui.res.R; @@ -65,6 +66,14 @@ public class CarrierText extends TextView { } } + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + // Clear selected state set by CarrierTextController so "selected" not announced by + // accessibility but we can still marquee. + info.setSelected(false); + } + public boolean getShowAirplaneMode() { return mShowAirplaneMode; } diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt index 9b852df88604..71b622aa0608 100644 --- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt +++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt @@ -61,8 +61,6 @@ import com.android.systemui.plugins.clocks.ClockTickRate 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.power.domain.interactor.PowerInteractor -import com.android.systemui.power.shared.model.ScreenPowerState import com.android.systemui.res.R as SysuiR import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.settings.UserTracker @@ -108,7 +106,6 @@ constructor( private val zenModeController: ZenModeController, private val zenModeInteractor: ZenModeInteractor, private val userTracker: UserTracker, - private val powerInteractor: PowerInteractor, ) { var loggers = listOf( @@ -380,12 +377,12 @@ constructor( override fun onTimeChanged() { refreshTime() } - } - private fun refreshTime() { - clock?.smallClock?.events?.onTimeTick() - clock?.largeClock?.events?.onTimeTick() - } + private fun refreshTime() { + clock?.smallClock?.events?.onTimeTick() + clock?.largeClock?.events?.onTimeTick() + } + } @VisibleForTesting internal fun listenForDnd(scope: CoroutineScope): Job { @@ -477,7 +474,6 @@ constructor( listenForAnyStateToAodTransition(this) listenForAnyStateToLockscreenTransition(this) listenForAnyStateToDozingTransition(this) - listenForScreenPowerOn(this) } } smallTimeListener?.update(shouldTimeListenerRun) @@ -647,17 +643,6 @@ constructor( } } - @VisibleForTesting - internal fun listenForScreenPowerOn(scope: CoroutineScope): Job { - return scope.launch { - powerInteractor.screenPowerState.collect { powerState -> - if (powerState != ScreenPowerState.SCREEN_OFF) { - refreshTime() - } - } - } - } - class TimeListener(val clockFace: ClockFaceController, val executor: DelayableExecutor) { val predrawListener = ViewTreeObserver.OnPreDrawListener { diff --git a/packages/SystemUI/src/com/android/keyguard/ConnectedDisplayKeyguardPresentation.kt b/packages/SystemUI/src/com/android/keyguard/ConnectedDisplayKeyguardPresentation.kt index 3f332f769c6e..8f6a54c4ae36 100644 --- a/packages/SystemUI/src/com/android/keyguard/ConnectedDisplayKeyguardPresentation.kt +++ b/packages/SystemUI/src/com/android/keyguard/ConnectedDisplayKeyguardPresentation.kt @@ -91,6 +91,10 @@ constructor( super.onCreate(savedInstanceState) onCreateV2() + val window = window ?: return + val layoutParams = window.attributes + layoutParams.flags = layoutParams.flags or WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER + window.attributes = layoutParams } fun onCreateV2() { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardFingerprintListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardFingerprintListenModel.kt index 953cf88feccb..943cbe87c8c2 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardFingerprintListenModel.kt +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardFingerprintListenModel.kt @@ -49,6 +49,7 @@ data class KeyguardFingerprintListenModel( var systemUser: Boolean = false, var udfps: Boolean = false, var userDoesNotHaveTrust: Boolean = false, + var communalShowing: Boolean = false, ) : KeyguardListenModel() { /** List of [String] to be used as a [Row] with [DumpsysTableLogger]. */ @@ -81,6 +82,7 @@ data class KeyguardFingerprintListenModel( systemUser.toString(), udfps.toString(), userDoesNotHaveTrust.toString(), + communalShowing.toString(), ) } @@ -122,6 +124,7 @@ data class KeyguardFingerprintListenModel( systemUser = model.systemUser udfps = model.udfps userDoesNotHaveTrust = model.userDoesNotHaveTrust + communalShowing = model.communalShowing } } @@ -170,6 +173,7 @@ data class KeyguardFingerprintListenModel( "systemUser", "underDisplayFingerprint", "userDoesNotHaveTrust", + "communalShowing", ) } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index 8cfb4c5592aa..ff7b2b025539 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -1242,7 +1242,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard public void reinflateViewFlipper( KeyguardSecurityViewFlipperController.OnViewInflatedCallback onViewInflatedListener) { mSecurityViewFlipperController.clearViews(); - mSecurityViewFlipperController.asynchronouslyInflateView(mCurrentSecurityMode, + mSecurityViewFlipperController.getSecurityView(mCurrentSecurityMode, mKeyguardSecurityCallback, (controller) -> { mView.updateSecurityViewFlipper(); onViewInflatedListener.onViewInflated(controller); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java index 120045fc058b..641cac51785f 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java @@ -23,7 +23,6 @@ import static com.android.systemui.flags.Flags.LOCKSCREEN_ENABLE_LANDSCAPE; import android.util.Log; import android.view.LayoutInflater; -import androidx.annotation.Nullable; import androidx.asynclayoutinflater.view.AsyncLayoutInflater; import com.android.internal.annotations.VisibleForTesting; @@ -35,7 +34,9 @@ import com.android.systemui.res.R; import com.android.systemui.util.ViewController; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; import javax.inject.Inject; @@ -56,6 +57,8 @@ public class KeyguardSecurityViewFlipperController private final EmergencyButtonController.Factory mEmergencyButtonControllerFactory; private final Factory mKeyguardSecurityViewControllerFactory; private final FeatureFlags mFeatureFlags; + private final List<OnViewInflatedCallback> mOnViewInflatedListeners = new ArrayList<>(); + private final Set<SecurityMode> mSecurityModeInProgress = new HashSet<>(); @Inject protected KeyguardSecurityViewFlipperController(KeyguardSecurityViewFlipper view, @@ -106,7 +109,13 @@ public class KeyguardSecurityViewFlipperController } } - asynchronouslyInflateView(securityMode, keyguardSecurityCallback, onViewInflatedCallback); + // Prevent multiple inflations for the same security mode. Instead, add callback to a list + // and then notify each in order when the view is inflated. + mOnViewInflatedListeners.add(onViewInflatedCallback); + if (!mSecurityModeInProgress.contains(securityMode)) { + mSecurityModeInProgress.add(securityMode); + asynchronouslyInflateView(securityMode, keyguardSecurityCallback); + } } /** @@ -117,9 +126,8 @@ public class KeyguardSecurityViewFlipperController * @param securityMode * @param keyguardSecurityCallback */ - public void asynchronouslyInflateView(SecurityMode securityMode, - KeyguardSecurityCallback keyguardSecurityCallback, - @Nullable OnViewInflatedCallback onViewInflatedListener) { + private void asynchronouslyInflateView(SecurityMode securityMode, + KeyguardSecurityCallback keyguardSecurityCallback) { int layoutId = mFeatureFlags.isEnabled(LOCKSCREEN_ENABLE_LANDSCAPE) ? getLayoutIdFor(securityMode) : getLegacyLayoutIdFor(securityMode); if (layoutId != 0) { @@ -129,24 +137,26 @@ public class KeyguardSecurityViewFlipperController mAsyncLayoutInflater.inflate(layoutId, mView, (view, resId, parent) -> { mView.addView(view); + mSecurityModeInProgress.remove(securityMode); KeyguardInputViewController<KeyguardInputView> childController = mKeyguardSecurityViewControllerFactory.create( (KeyguardInputView) view, securityMode, keyguardSecurityCallback); childController.init(); mChildren.add(childController); - if (onViewInflatedListener != null) { - onViewInflatedListener.onViewInflated(childController); - // Single bouncer constrains are default - if (mFeatureFlags.isEnabled(LOCKSCREEN_ENABLE_LANDSCAPE)) { - boolean useSplitBouncer = - getResources().getBoolean(R.bool.update_bouncer_constraints) - && getResources().getConfiguration().orientation - == ORIENTATION_LANDSCAPE; + for (OnViewInflatedCallback callback : mOnViewInflatedListeners) { + callback.onViewInflated(childController); + } + mOnViewInflatedListeners.clear(); - updateConstraints(useSplitBouncer); - } + // Single bouncer constrains are default + if (mFeatureFlags.isEnabled(LOCKSCREEN_ENABLE_LANDSCAPE)) { + boolean useSplitBouncer = + getResources().getBoolean(R.bool.update_bouncer_constraints) + && getResources().getConfiguration().orientation + == ORIENTATION_LANDSCAPE; + updateConstraints(useSplitBouncer); } }); } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 61038ef1a72d..c266a5b47cff 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -43,6 +43,7 @@ import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STR import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN; +import static com.android.systemui.Flags.glanceableHubV2; import static com.android.systemui.Flags.simPinBouncerReset; import static com.android.systemui.Flags.simPinUseSlotId; import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED; @@ -128,6 +129,7 @@ import com.android.systemui.biometrics.AuthController; import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider; import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; @@ -294,6 +296,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private final Provider<JavaAdapter> mJavaAdapter; private final Provider<SceneInteractor> mSceneInteractor; private final Provider<AlternateBouncerInteractor> mAlternateBouncerInteractor; + private final CommunalSceneInteractor mCommunalSceneInteractor; private final AuthController mAuthController; private final UiEventLogger mUiEventLogger; private final Set<String> mAllowFingerprintOnOccludingActivitiesFromPackage; @@ -404,6 +407,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab protected int mFingerprintRunningState = BIOMETRIC_STATE_STOPPED; private boolean mFingerprintDetectRunning; private boolean mIsDreaming; + private boolean mCommunalShowing; private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID; private final FingerprintInteractiveToAuthProvider mFingerprintInteractiveToAuthProvider; @@ -2205,7 +2209,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab IActivityTaskManager activityTaskManagerService, Provider<AlternateBouncerInteractor> alternateBouncerInteractor, Provider<JavaAdapter> javaAdapter, - Provider<SceneInteractor> sceneInteractor) { + Provider<SceneInteractor> sceneInteractor, + CommunalSceneInteractor communalSceneInteractor) { mContext = context; mSubscriptionManager = subscriptionManager; mUserTracker = userTracker; @@ -2254,6 +2259,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mAlternateBouncerInteractor = alternateBouncerInteractor; mJavaAdapter = javaAdapter; mSceneInteractor = sceneInteractor; + mCommunalSceneInteractor = communalSceneInteractor; mHandler = new Handler(mainLooper) { @Override @@ -2535,6 +2541,13 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab ); } + if (glanceableHubV2()) { + mJavaAdapter.get().alwaysCollectFlow( + mCommunalSceneInteractor.isCommunalVisible(), + this::onCommunalShowingChanged + ); + } + // start() can be invoked in the middle of user switching, so check for this state and issue // the call manually as that important event was missed. if (mUserTracker.isUserSwitching()) { @@ -2837,6 +2850,15 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } /** + * Sets whether the communal hub is showing. + */ + @VisibleForTesting + void onCommunalShowingChanged(boolean showing) { + mCommunalShowing = showing; + updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE); + } + + /** * Whether the alternate bouncer is showing. */ public void setAlternateBouncerShowing(boolean showing) { @@ -2998,11 +3020,14 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab final boolean strongerAuthRequired = !isUnlockingWithFingerprintAllowed(); final boolean shouldListenBouncerState = !strongerAuthRequired || !isPrimaryBouncerShowingOrWillBeShowing(); + final boolean isUdfpsAuthRequiredOnCommunal = + !mCommunalShowing || isAlternateBouncerShowing(); final boolean shouldListenUdfpsState = !isUdfps || (!userCanSkipBouncer && !strongerAuthRequired - && userDoesNotHaveTrust); + && userDoesNotHaveTrust + && (!glanceableHubV2() || isUdfpsAuthRequiredOnCommunal)); boolean shouldListen = shouldListenKeyguardState && shouldListenUserState @@ -3033,7 +3058,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mSwitchingUser, mIsSystemUser, isUdfps, - userDoesNotHaveTrust)); + userDoesNotHaveTrust, + mCommunalShowing)); return shouldListen; } @@ -3332,6 +3358,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } + /** Triggers an out of band time update */ + public void triggerTimeUpdate() { + mHandler.sendEmptyMessage(MSG_TIME_UPDATE); + } + /** * Handle {@link #MSG_TIME_UPDATE} */ 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 e7470a34a065..102efcf7badd 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java @@ -17,6 +17,7 @@ package com.android.systemui.accessibility.floatingmenu; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION; import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; import android.content.Context; @@ -90,9 +91,11 @@ class MenuViewLayerController implements IAccessibilityFloatingMenu { WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSLUCENT); + params.setTitle("FloatingMenu"); params.receiveInsetsIgnoringZOrder = true; params.privateFlags |= - PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION | SYSTEM_FLAG_SHOW_FOR_ALL_USERS; + PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION | SYSTEM_FLAG_SHOW_FOR_ALL_USERS + | PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION; params.windowAnimations = android.R.style.Animation_Translucent; // Insets are configured to allow the menu to display over navigation and system bars. params.setFitInsetsTypes(0); 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 081d2a087b07..b7b31566a5e9 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 @@ -75,8 +75,8 @@ public class AmbientStatusBarView extends ConstraintLayout { private ShadowInfo mAmbientShadowInfo; private int mDrawableSize; private int mDrawableInsetSize; - private static final float KEY_SHADOW_ALPHA = 0.8f; - private static final float AMBIENT_SHADOW_ALPHA = 0.6f; + private static final float KEY_SHADOW_ALPHA = 0.9f; + private static final float AMBIENT_SHADOW_ALPHA = 0.7f; public AmbientStatusBarView(Context context) { this(context, null); @@ -102,14 +102,14 @@ public class AmbientStatusBarView extends ConstraintLayout { super.onFinishInflate(); mKeyShadowInfo = createShadowInfo( - R.dimen.dream_overlay_status_bar_key_text_shadow_radius, + R.dimen.dream_overlay_icon_shadow_radius, R.dimen.dream_overlay_status_bar_key_text_shadow_dx, R.dimen.dream_overlay_status_bar_key_text_shadow_dy, KEY_SHADOW_ALPHA ); mAmbientShadowInfo = createShadowInfo( - R.dimen.dream_overlay_status_bar_ambient_text_shadow_radius, + R.dimen.dream_overlay_icon_ambient_shadow_radius, R.dimen.dream_overlay_status_bar_ambient_text_shadow_dx, R.dimen.dream_overlay_status_bar_ambient_text_shadow_dy, AMBIENT_SHADOW_ALPHA diff --git a/packages/SystemUI/src/com/android/systemui/common/shared/colors/ShadeColors.kt b/packages/SystemUI/src/com/android/systemui/common/shared/colors/ShadeColors.kt new file mode 100644 index 000000000000..70ace6a6d038 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/common/shared/colors/ShadeColors.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.common.shared.colors + +import android.content.res.Resources +import android.graphics.Color +import com.android.internal.graphics.ColorUtils +import com.android.systemui.res.R + +object ShadeColors { + @JvmStatic + fun Resources.shadeBasePanel(): Int { + val layerAbove = + ColorUtils.setAlphaComponent(getColor(R.color.shade_panel), (0.4f * 255).toInt()) + val layerBelow = ColorUtils.setAlphaComponent(Color.WHITE, (0.1f * 255).toInt()) + return ColorUtils.compositeColors(layerAbove, layerBelow) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/common/shared/colors/SurfaceEffectColors.kt b/packages/SystemUI/src/com/android/systemui/common/shared/colors/SurfaceEffectColors.kt new file mode 100644 index 000000000000..d4027c029a0a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/common/shared/colors/SurfaceEffectColors.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.common.shared.colors + +import android.content.res.Resources +import android.graphics.Color +import com.android.internal.graphics.ColorUtils +import com.android.systemui.res.R + +object SurfaceEffectColors { + @JvmStatic + fun Resources.surfaceEffect0(): Int { + return getColor(com.android.internal.R.color.surface_effect_0) + } + + @JvmStatic + fun Resources.surfaceEffect1(): Int { + return getColor(com.android.internal.R.color.surface_effect_1) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt b/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt index 7fcdd9596049..5671fa49c716 100644 --- a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt @@ -19,15 +19,16 @@ package com.android.systemui.common.ui.data.repository import android.content.Context import android.content.res.Configuration +import android.view.Display import android.view.DisplayInfo import androidx.annotation.DimenRes import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.wrapper.DisplayUtilsWrapper +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import dagger.Binds import dagger.Module import dagger.Provides @@ -57,7 +58,7 @@ interface ConfigurationRepository { val configurationValues: Flow<Configuration> /** Emits the latest display this configuration controller has been moved to. */ - val onMovedToDisplay: Flow<Int> + val onMovedToDisplay: StateFlow<Int> fun getResolutionScale(): Float @@ -121,20 +122,21 @@ constructor( configurationController.addCallback(callback) awaitClose { configurationController.removeCallback(callback) } } - override val onMovedToDisplay: Flow<Int> - get() = conflatedCallbackFlow { - val callback = - object : ConfigurationController.ConfigurationListener { - override fun onMovedToDisplay( - newDisplayId: Int, - newConfiguration: Configuration?, - ) { - trySend(newDisplayId) + override val onMovedToDisplay: StateFlow<Int> = + conflatedCallbackFlow { + val callback = + object : ConfigurationController.ConfigurationListener { + override fun onMovedToDisplay( + newDisplayId: Int, + newConfiguration: Configuration?, + ) { + trySend(newDisplayId) + } } - } - configurationController.addCallback(callback) - awaitClose { configurationController.removeCallback(callback) } - } + configurationController.addCallback(callback) + awaitClose { configurationController.removeCallback(callback) } + } + .stateIn(scope, SharingStarted.Eagerly, Display.DEFAULT_DISPLAY) override val scaleForResolution: StateFlow<Float> = onConfigurationChange diff --git a/packages/SystemUI/src/com/android/systemui/communal/DevicePosturingCommandListener.kt b/packages/SystemUI/src/com/android/systemui/communal/DevicePosturingCommandListener.kt deleted file mode 100644 index c7b7050340a0..000000000000 --- a/packages/SystemUI/src/com/android/systemui/communal/DevicePosturingCommandListener.kt +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2025 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.communal - -import android.annotation.SuppressLint -import android.app.DreamManager -import com.android.systemui.CoreStartable -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.statusbar.commandline.Command -import com.android.systemui.statusbar.commandline.CommandRegistry -import java.io.PrintWriter -import javax.inject.Inject - -@SysUISingleton -class DevicePosturingCommandListener -@Inject -constructor(private val commandRegistry: CommandRegistry, private val dreamManager: DreamManager) : - CoreStartable { - private val command = DevicePosturingCommand() - - override fun start() { - commandRegistry.registerCommand(COMMAND_ROOT) { command } - } - - internal inner class DevicePosturingCommand : Command { - @SuppressLint("MissingPermission") - override fun execute(pw: PrintWriter, args: List<String>) { - val arg = args.getOrNull(0) - if (arg == null || arg.lowercase() == "help") { - help(pw) - return - } - - when (arg.lowercase()) { - "true" -> dreamManager.setDevicePostured(true) - "false" -> dreamManager.setDevicePostured(false) - else -> { - pw.println("Invalid argument!") - help(pw) - } - } - } - - override fun help(pw: PrintWriter) { - pw.println("Usage: $ adb shell cmd statusbar device-postured <true|false>") - } - } - - private companion object { - const val COMMAND_ROOT = "device-postured" - } -} diff --git a/packages/SystemUI/src/com/android/systemui/communal/DevicePosturingListener.kt b/packages/SystemUI/src/com/android/systemui/communal/DevicePosturingListener.kt new file mode 100644 index 000000000000..47040fa4a572 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/DevicePosturingListener.kt @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.communal + +import android.annotation.SuppressLint +import android.app.DreamManager +import android.service.dreams.Flags.allowDreamWhenPostured +import com.android.app.tracing.coroutines.launchInTraced +import com.android.systemui.CoreStartable +import com.android.systemui.communal.posturing.domain.interactor.PosturingInteractor +import com.android.systemui.communal.posturing.shared.model.PosturedState +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.log.dagger.CommunalTableLog +import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.log.table.logDiffsForTable +import com.android.systemui.statusbar.commandline.Command +import com.android.systemui.statusbar.commandline.CommandRegistry +import java.io.PrintWriter +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.onEach + +@SysUISingleton +class DevicePosturingListener +@Inject +constructor( + private val commandRegistry: CommandRegistry, + private val dreamManager: DreamManager, + private val interactor: PosturingInteractor, + @Background private val bgScope: CoroutineScope, + @CommunalTableLog private val tableLogBuffer: TableLogBuffer, +) : CoreStartable { + private val command = DevicePosturingCommand() + + @SuppressLint("MissingPermission") + override fun start() { + if (!allowDreamWhenPostured()) { + return + } + + interactor.postured + .distinctUntilChanged() + .logDiffsForTable( + tableLogBuffer = tableLogBuffer, + columnPrefix = "", + columnName = "postured", + initialValue = false, + ) + .onEach { postured -> dreamManager.setDevicePostured(postured) } + .launchInTraced("$TAG#collectPostured", bgScope) + + commandRegistry.registerCommand(COMMAND_ROOT) { command } + } + + internal inner class DevicePosturingCommand : Command { + @SuppressLint("MissingPermission") + override fun execute(pw: PrintWriter, args: List<String>) { + val arg = args.getOrNull(0) + if (arg == null || arg.lowercase() == "help") { + help(pw) + return + } + + val state = + when (arg.lowercase()) { + "true" -> PosturedState.Postured(confidence = 1f) + "false" -> PosturedState.NotPostured + "clear" -> PosturedState.Unknown + else -> { + pw.println("Invalid argument!") + help(pw) + null + } + } + state?.let { interactor.setValueForDebug(it) } + } + + override fun help(pw: PrintWriter) { + pw.println("Usage: $ adb shell cmd statusbar device-postured <true|false|clear>") + } + } + + private companion object { + const val COMMAND_ROOT = "device-postured" + const val TAG = "DevicePosturingListener" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalStartableModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalStartableModule.kt index e3443227685f..7358aa7b3fcd 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalStartableModule.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalStartableModule.kt @@ -22,7 +22,7 @@ import com.android.systemui.communal.CommunalDreamStartable import com.android.systemui.communal.CommunalMetricsStartable import com.android.systemui.communal.CommunalOngoingContentStartable import com.android.systemui.communal.CommunalSceneStartable -import com.android.systemui.communal.DevicePosturingCommandListener +import com.android.systemui.communal.DevicePosturingListener import com.android.systemui.communal.log.CommunalLoggerStartable import com.android.systemui.communal.widgets.CommunalAppWidgetHostStartable import com.android.systemui.dagger.qualifiers.PerUser @@ -71,6 +71,6 @@ interface CommunalStartableModule { @Binds @IntoMap - @ClassKey(DevicePosturingCommandListener::class) - fun bindDevicePosturingCommandListener(impl: DevicePosturingCommandListener): CoreStartable + @ClassKey(DevicePosturingListener::class) + fun bindDevicePosturingistener(impl: DevicePosturingListener): CoreStartable } diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt index 4de39c457f3b..a02bc8b89910 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt @@ -51,6 +51,12 @@ interface CommunalPrefsRepository { /** Save the CTA tile dismissed state for the current user. */ suspend fun setCtaDismissed(user: UserInfo) + + /** Whether hub onboarding has been dismissed. */ + fun isHubOnboardingDismissed(user: UserInfo): Flow<Boolean> + + /** Save the hub onboarding dismissed state for the current user. */ + suspend fun setHubOnboardingDismissed(user: UserInfo) } @OptIn(ExperimentalCoroutinesApi::class) @@ -65,9 +71,6 @@ constructor( ) : CommunalPrefsRepository { private val logger by lazy { Logger(logBuffer, TAG) } - override fun isCtaDismissed(user: UserInfo): Flow<Boolean> = - readKeyForUser(user, CTA_DISMISSED_STATE) - /** * Emits an event each time a Backup & Restore restoration job is completed, and once at the * start of collection. @@ -82,18 +85,29 @@ constructor( .onEach { logger.i("Restored state for communal preferences.") } .emitOnStart() + override fun isCtaDismissed(user: UserInfo): Flow<Boolean> = + readKeyForUser(user, CTA_DISMISSED_STATE) + override suspend fun setCtaDismissed(user: UserInfo) = withContext(bgDispatcher) { getSharedPrefsForUser(user).edit().putBoolean(CTA_DISMISSED_STATE, true).apply() logger.i("Dismissed CTA tile") } + override fun isHubOnboardingDismissed(user: UserInfo): Flow<Boolean> = + readKeyForUser(user, HUB_ONBOARDING_DISMISSED_STATE) + + override suspend fun setHubOnboardingDismissed(user: UserInfo) = + withContext(bgDispatcher) { + getSharedPrefsForUser(user) + .edit() + .putBoolean(HUB_ONBOARDING_DISMISSED_STATE, true) + .apply() + logger.i("Dismissed hub onboarding") + } + private fun getSharedPrefsForUser(user: UserInfo): SharedPreferences { - return userFileManager.getSharedPreferences( - FILE_NAME, - Context.MODE_PRIVATE, - user.id, - ) + return userFileManager.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE, user.id) } private fun readKeyForUser(user: UserInfo, key: String): Flow<Boolean> { @@ -109,5 +123,6 @@ constructor( const val TAG = "CommunalPrefsRepository" const val FILE_NAME = "communal_hub_prefs" const val CTA_DISMISSED_STATE = "cta_dismissed" + const val HUB_ONBOARDING_DISMISSED_STATE = "hub_onboarding_dismissed" } } diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalPrefsInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalPrefsInteractor.kt index 0b5f40d8041e..76e6cde4ad81 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalPrefsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalPrefsInteractor.kt @@ -32,6 +32,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch @OptIn(ExperimentalCoroutinesApi::class) @SysUISingleton @@ -63,6 +64,24 @@ constructor( suspend fun setCtaDismissed(user: UserInfo = userTracker.userInfo) = repository.setCtaDismissed(user) + val isHubOnboardingDismissed: Flow<Boolean> = + userInteractor.selectedUserInfo + .flatMapLatest { user -> repository.isHubOnboardingDismissed(user) } + .logDiffsForTable( + tableLogBuffer = tableLogBuffer, + columnPrefix = "", + columnName = "isHubOnboardingDismissed", + initialValue = false, + ) + .stateIn( + scope = bgScope, + started = SharingStarted.WhileSubscribed(), + initialValue = false, + ) + + fun setHubOnboardingDismissed(user: UserInfo = userTracker.userInfo) = + bgScope.launch { repository.setHubOnboardingDismissed(user) } + private companion object { const val TAG = "CommunalPrefsInteractor" } diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/HubOnboardingInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/HubOnboardingInteractor.kt new file mode 100644 index 000000000000..26a0a7930b4a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/HubOnboardingInteractor.kt @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.communal.domain.interactor + +import com.android.systemui.communal.data.repository.CommunalSettingsRepository +import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf +import com.android.systemui.util.kotlin.BooleanFlowOperators.not +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf + +class HubOnboardingInteractor +@Inject +constructor( + communalSceneInteractor: CommunalSceneInteractor, + communalSettingsRepository: CommunalSettingsRepository, + private val communalPrefsInteractor: CommunalPrefsInteractor, +) { + /** Dismiss hub onboarding education. */ + fun setHubOnboardingDismissed() = communalPrefsInteractor.setHubOnboardingDismissed() + + /** Should hub onboarding be shown to the user. */ + val shouldShowHubOnboarding: Flow<Boolean> = + if (communalSettingsRepository.getV2FlagEnabled()) { + allOf( + not(communalPrefsInteractor.isHubOnboardingDismissed), + communalSceneInteractor.isIdleOnCommunal, + ) + } else { + flowOf(false) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/posturing/dagger/NoopPosturingModule.kt b/packages/SystemUI/src/com/android/systemui/communal/posturing/dagger/NoopPosturingModule.kt new file mode 100644 index 000000000000..f576a2000e05 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/posturing/dagger/NoopPosturingModule.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.communal.posturing.dagger + +import com.android.systemui.communal.posturing.data.repository.NoOpPosturingRepository +import com.android.systemui.communal.posturing.data.repository.PosturingRepository +import dagger.Binds +import dagger.Module + +/** Module providing a reference implementation of the posturing signal. */ +@Module +interface NoopPosturingModule { + /** Binds a reference implementation of the posturing repository */ + @Binds fun bindPosturingRepository(impl: NoOpPosturingRepository): PosturingRepository +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/posturing/data/repository/NoOpPosturingRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/posturing/data/repository/NoOpPosturingRepository.kt new file mode 100644 index 000000000000..c5f357f556ca --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/posturing/data/repository/NoOpPosturingRepository.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.posturing.data.repository + +import com.android.systemui.communal.posturing.shared.model.PosturedState +import com.android.systemui.dagger.SysUISingleton +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow + +@SysUISingleton +class NoOpPosturingRepository @Inject constructor() : PosturingRepository { + override val posturedState: Flow<PosturedState> = + MutableStateFlow(PosturedState.Unknown).asStateFlow() +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/posturing/data/repository/PosturingRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/posturing/data/repository/PosturingRepository.kt new file mode 100644 index 000000000000..dae1a47f5be0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/posturing/data/repository/PosturingRepository.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.communal.posturing.data.repository + +import com.android.systemui.communal.posturing.shared.model.PosturedState +import kotlinx.coroutines.flow.Flow + +/** + * Repository which retrieves the postured state of the device. Posturing is defined as the device + * being stationary and upright. + */ +interface PosturingRepository { + /** Whether the device is currently stationary and upright. */ + val posturedState: Flow<PosturedState> +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/posturing/domain/interactor/PosturingInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/posturing/domain/interactor/PosturingInteractor.kt new file mode 100644 index 000000000000..cd81dea9cad1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/posturing/domain/interactor/PosturingInteractor.kt @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.communal.posturing.domain.interactor + +import com.android.systemui.communal.posturing.data.repository.PosturingRepository +import com.android.systemui.communal.posturing.shared.model.PosturedState +import com.android.systemui.dagger.SysUISingleton +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.combine + +@SysUISingleton +class PosturingInteractor @Inject constructor(repository: PosturingRepository) { + private val debugPostured = MutableStateFlow<PosturedState>(PosturedState.Unknown) + + val postured: Flow<Boolean> = + combine(repository.posturedState, debugPostured) { postured, debugValue -> + debugValue.asBoolean() ?: postured.asBoolean() ?: false + } + + fun setValueForDebug(value: PosturedState) { + debugPostured.value = value + } +} + +fun PosturedState.asBoolean(): Boolean? { + return when (this) { + is PosturedState.Postured -> true + PosturedState.NotPostured -> false + PosturedState.Unknown -> null + } +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/posturing/shared/model/PosturedState.kt b/packages/SystemUI/src/com/android/systemui/communal/posturing/shared/model/PosturedState.kt new file mode 100644 index 000000000000..431ca67315eb --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/posturing/shared/model/PosturedState.kt @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.communal.posturing.shared.model + +sealed interface PosturedState { + /** Represents postured state */ + data class Postured(val confidence: Float) : PosturedState + + /** Represents unknown/uninitialized state */ + data object Unknown : PosturedState + + /** Represents state where we are not postured */ + data object NotPostured : PosturedState +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/HubOnboardingViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/HubOnboardingViewModel.kt new file mode 100644 index 000000000000..5245800ba3d1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/HubOnboardingViewModel.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.communal.ui.viewmodel + +import android.annotation.SuppressLint +import com.android.systemui.communal.domain.interactor.HubOnboardingInteractor +import com.android.systemui.lifecycle.ExclusiveActivatable +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlinx.coroutines.awaitCancellation +import kotlinx.coroutines.coroutineScope + +class HubOnboardingViewModel +@AssistedInject +constructor(private val hubOnboardingInteractor: HubOnboardingInteractor) : ExclusiveActivatable() { + + val shouldShowHubOnboarding = hubOnboardingInteractor.shouldShowHubOnboarding + + fun onDismissed() { + hubOnboardingInteractor.setHubOnboardingDismissed() + } + + @SuppressLint("MissingPermission") + override suspend fun onActivated(): Nothing = coroutineScope { awaitCancellation() } + + @AssistedFactory + interface Factory { + fun create(): HubOnboardingViewModel + } +} diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/AllModel.kt b/packages/SystemUI/src/com/android/systemui/controls/management/AllModel.kt index be428a84da2f..9e9e9998a82e 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/AllModel.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/AllModel.kt @@ -25,14 +25,14 @@ import com.android.systemui.controls.controller.ControlInfo * This model is used to show controls separated by zones. * * The model will sort the controls and zones in the following manner: - * * The zones will be sorted in a first seen basis - * * The controls in each zone will be sorted in a first seen basis. + * * The zones will be sorted in a first seen basis + * * The controls in each zone will be sorted in a first seen basis. * - * The controls passed should belong to the same structure, as an instance of this model will be - * created for each structure. + * The controls passed should belong to the same structure, as an instance of this model will be + * created for each structure. * - * The list of favorite ids can contain ids for controls not passed to this model. Those will be - * filtered out. + * The list of favorite ids can contain ids for controls not passed to this model. Those will be + * filtered out. * * @property controls List of controls as returned by loading * @property initialFavoriteIds sorted ids of favorite controls. @@ -43,7 +43,7 @@ class AllModel( private val controls: List<ControlStatus>, initialFavoriteIds: List<String>, private val emptyZoneString: CharSequence, - private val controlsModelCallback: ControlsModel.ControlsModelCallback + private val controlsModelCallback: ControlsModel.ControlsModelCallback, ) : ControlsModel { private var modified = false @@ -51,12 +51,11 @@ class AllModel( override val moveHelper = null override val favorites: List<ControlInfo> - get() = favoriteIds.mapNotNull { id -> - val control = controls.firstOrNull { it.control.controlId == id }?.control - control?.let { - ControlInfo.fromControl(it) + get() = + favoriteIds.mapNotNull { id -> + val control = controls.firstOrNull { it.control.controlId == id }?.control + control?.let { ControlInfo.fromControl(it) } } - } private val favoriteIds = run { val ids = controls.mapTo(HashSet()) { it.control.controlId } @@ -66,15 +65,17 @@ class AllModel( override val elements: List<ElementWrapper> = createWrappers(controls) override fun changeFavoriteStatus(controlId: String, favorite: Boolean) { - val toChange = elements.firstOrNull { - it is ControlStatusWrapper && it.controlStatus.control.controlId == controlId - } as ControlStatusWrapper? + val toChange = + elements.firstOrNull { + it is ControlStatusWrapper && it.controlStatus.control.controlId == controlId + } as ControlStatusWrapper? if (favorite == toChange?.controlStatus?.favorite) return - val changed: Boolean = if (favorite) { - favoriteIds.add(controlId) - } else { - favoriteIds.remove(controlId) - } + val changed: Boolean = + if (favorite) { + favoriteIds.add(controlId) + } else { + favoriteIds.remove(controlId) + } if (changed) { if (!modified) { modified = true @@ -82,15 +83,14 @@ class AllModel( } controlsModelCallback.onChange() } - toChange?.let { - it.controlStatus.favorite = favorite - } + toChange?.let { it.controlStatus.favorite = favorite } } private fun createWrappers(list: List<ControlStatus>): List<ElementWrapper> { - val map = list.groupByTo(OrderedMap(ArrayMap<CharSequence, MutableList<ControlStatus>>())) { - it.control.zone ?: "" - } + val map = + list.groupByTo(OrderedMap(ArrayMap<CharSequence, MutableList<ControlStatus>>())) { + it.control.zone ?: "" + } val output = mutableListOf<ElementWrapper>() var emptyZoneValues: Sequence<ControlStatusWrapper>? = null for (zoneName in map.orderedKeys) { diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt index f034851e5d80..3ea415675bdf 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt @@ -36,10 +36,11 @@ import androidx.core.view.AccessibilityDelegateCompat import androidx.core.view.ViewCompat import androidx.core.view.accessibility.AccessibilityNodeInfoCompat import androidx.recyclerview.widget.RecyclerView -import com.android.systemui.res.R import com.android.systemui.controls.ControlInterface import com.android.systemui.controls.ui.CanUseIconPredicate import com.android.systemui.controls.ui.RenderInfo +import com.android.systemui.res.R +import com.android.systemui.utils.SafeIconLoader private typealias ModelFavoriteChanger = (String, Boolean) -> Unit @@ -54,6 +55,7 @@ private typealias ModelFavoriteChanger = (String, Boolean) -> Unit class ControlAdapter( private val elevation: Float, private val currentUserId: Int, + private val safeIconLoader: SafeIconLoader, ) : RecyclerView.Adapter<Holder>() { companion object { @@ -62,15 +64,14 @@ class ControlAdapter( const val TYPE_DIVIDER = 2 /** - * For low-dp width screens that also employ an increased font scale, adjust the - * number of columns. This helps prevent text truncation on these devices. - * + * For low-dp width screens that also employ an increased font scale, adjust the number of + * columns. This helps prevent text truncation on these devices. */ @JvmStatic fun findMaxColumns(res: Resources): Int { var maxColumns = res.getInteger(R.integer.controls_max_columns) val maxColumnsAdjustWidth = - res.getInteger(R.integer.controls_max_columns_adjust_below_width_dp) + res.getInteger(R.integer.controls_max_columns_adjust_below_width_dp) val outValue = TypedValue() res.getValue(R.dimen.controls_max_columns_adjust_above_font_scale, outValue, true) @@ -78,10 +79,12 @@ class ControlAdapter( val config = res.configuration val isPortrait = config.orientation == Configuration.ORIENTATION_PORTRAIT - if (isPortrait && + if ( + isPortrait && config.screenWidthDp != Configuration.SCREEN_WIDTH_DP_UNDEFINED && config.screenWidthDp <= maxColumnsAdjustWidth && - config.fontScale >= maxColumnsAdjustFontScale) { + config.fontScale >= maxColumnsAdjustFontScale + ) { maxColumns-- } @@ -106,11 +109,12 @@ class ControlAdapter( rightMargin = 0 } elevation = this@ControlAdapter.elevation - background = parent.context.getDrawable( - R.drawable.control_background_ripple) + background = + parent.context.getDrawable(R.drawable.control_background_ripple) }, currentUserId, model?.moveHelper, // Indicates that position information is needed + safeIconLoader, ) { id, favorite -> model?.changeFavoriteStatus(id, favorite) } @@ -119,8 +123,13 @@ class ControlAdapter( ZoneHolder(layoutInflater.inflate(R.layout.controls_zone_header, parent, false)) } TYPE_DIVIDER -> { - DividerHolder(layoutInflater.inflate( - R.layout.controls_horizontal_divider_with_empty, parent, false)) + DividerHolder( + layoutInflater.inflate( + R.layout.controls_horizontal_divider_with_empty, + parent, + false, + ) + ) } else -> throw IllegalStateException("Wrong viewType: $viewType") } @@ -134,9 +143,7 @@ class ControlAdapter( override fun getItemCount() = model?.elements?.size ?: 0 override fun onBindViewHolder(holder: Holder, index: Int) { - model?.let { - holder.bindData(it.elements[index]) - } + model?.let { holder.bindData(it.elements[index]) } } override fun onBindViewHolder(holder: Holder, position: Int, payloads: MutableList<Any>) { @@ -166,13 +173,12 @@ class ControlAdapter( /** * Holder for binding views in the [RecyclerView]- + * * @param view the [View] for this [Holder] */ sealed class Holder(view: View) : RecyclerView.ViewHolder(view) { - /** - * Bind the data from the model into the view - */ + /** Bind the data from the model into the view */ abstract fun bindData(wrapper: ElementWrapper) open fun updateFavorite(favorite: Boolean) {} @@ -181,12 +187,13 @@ sealed class Holder(view: View) : RecyclerView.ViewHolder(view) { /** * Holder for using with [DividerWrapper] to display a divider between zones. * - * The divider can be shown or hidden. It also has a view the height of a control, that can - * be toggled visible or gone. + * The divider can be shown or hidden. It also has a view the height of a control, that can be + * toggled visible or gone. */ private class DividerHolder(view: View) : Holder(view) { private val frame: View = itemView.requireViewById(R.id.frame) private val divider: View = itemView.requireViewById(R.id.divider) + override fun bindData(wrapper: ElementWrapper) { wrapper as DividerWrapper frame.visibility = if (wrapper.showNone) View.VISIBLE else View.GONE @@ -194,9 +201,7 @@ private class DividerHolder(view: View) : Holder(view) { } } -/** - * Holder for using with [ZoneNameWrapper] to display names of zones. - */ +/** Holder for using with [ZoneNameWrapper] to display names of zones. */ private class ZoneHolder(view: View) : Holder(view) { private val zone: TextView = itemView as TextView @@ -208,15 +213,17 @@ private class ZoneHolder(view: View) : Holder(view) { /** * Holder for using with [ControlStatusWrapper] to display names of zones. + * * @param moveHelper a helper interface to facilitate a11y rearranging. Null indicates no - * rearranging - * @param favoriteCallback this callback will be called whenever the favorite state of the - * [Control] this view represents changes. + * rearranging + * @param favoriteCallback this callback will be called whenever the favorite state of the [Control] + * this view represents changes. */ internal class ControlHolder( view: View, currentUserId: Int, val moveHelper: ControlsModel.MoveHelper?, + val safeIconLoader: SafeIconLoader, val favoriteCallback: ModelFavoriteChanger, ) : Holder(view) { private val favoriteStateDescription = @@ -228,16 +235,16 @@ internal class ControlHolder( private val title: TextView = itemView.requireViewById(R.id.title) private val subtitle: TextView = itemView.requireViewById(R.id.subtitle) private val removed: TextView = itemView.requireViewById(R.id.status) - private val favorite: CheckBox = itemView.requireViewById<CheckBox>(R.id.favorite).apply { - visibility = View.VISIBLE - } + private val favorite: CheckBox = + itemView.requireViewById<CheckBox>(R.id.favorite).apply { visibility = View.VISIBLE } private val canUseIconPredicate = CanUseIconPredicate(currentUserId) - private val accessibilityDelegate = ControlHolderAccessibilityDelegate( - this::stateDescription, - this::getLayoutPosition, - moveHelper - ) + private val accessibilityDelegate = + ControlHolderAccessibilityDelegate( + this::stateDescription, + this::getLayoutPosition, + moveHelper, + ) init { ViewCompat.setAccessibilityDelegate(itemView, accessibilityDelegate) @@ -252,7 +259,9 @@ internal class ControlHolder( } else { val position = layoutPosition + 1 return itemView.context.getString( - R.string.accessibility_control_favorite_position, position) + R.string.accessibility_control_favorite_position, + position, + ) } } @@ -262,11 +271,12 @@ internal class ControlHolder( title.text = wrapper.title subtitle.text = wrapper.subtitle updateFavorite(wrapper.favorite) - removed.text = if (wrapper.removed) { - itemView.context.getText(R.string.controls_removed) - } else { - "" - } + removed.text = + if (wrapper.removed) { + itemView.context.getText(R.string.controls_removed) + } else { + "" + } itemView.setOnClickListener { updateFavorite(!favorite.isChecked) favoriteCallback(wrapper.controlId, favorite.isChecked) @@ -282,7 +292,7 @@ internal class ControlHolder( private fun getRenderInfo( component: ComponentName, - @DeviceTypes.DeviceType deviceType: Int + @DeviceTypes.DeviceType deviceType: Int, ): RenderInfo { return RenderInfo.lookup(itemView.context, component, deviceType) } @@ -292,18 +302,19 @@ internal class ControlHolder( val fg = context.getResources().getColorStateList(ri.foreground, context.getTheme()) icon.imageTintList = null - ci.customIcon - ?.takeIf(canUseIconPredicate) - ?.let { - icon.setImageIcon(it) - } ?: run { - icon.setImageDrawable(ri.icon) - - // Do not color app icons - if (ci.deviceType != DeviceTypes.TYPE_ROUTINE) { - icon.setImageTintList(fg) - } + ci.customIcon?.takeIf(canUseIconPredicate)?.let { + val drawable = safeIconLoader.load(it) + icon.setImageDrawable(drawable) + drawable } + ?: run { + icon.setImageDrawable(ri.icon) + + // Do not color app icons + if (ci.deviceType != DeviceTypes.TYPE_ROUTINE) { + icon.setImageTintList(fg) + } + } } } @@ -317,14 +328,13 @@ internal class ControlHolder( * * @param stateRetriever function to determine the state description based on the favorite state * @param positionRetriever function to obtain the position of this control. It only has to be - * correct in controls that are currently favorites (and therefore can - * be moved). + * correct in controls that are currently favorites (and therefore can be moved). * @param moveHelper helper interface to determine if a control can be moved and actually move it. */ private class ControlHolderAccessibilityDelegate( val stateRetriever: (Boolean) -> CharSequence?, val positionRetriever: () -> Int, - val moveHelper: ControlsModel.MoveHelper? + val moveHelper: ControlsModel.MoveHelper?, ) : AccessibilityDelegateCompat() { var isFavorite = false @@ -369,25 +379,29 @@ private class ControlHolderAccessibilityDelegate( private fun addClickAction(host: View, info: AccessibilityNodeInfoCompat) { // Change the text for the double-tap action - val clickActionString = if (isFavorite) { - host.context.getString(R.string.accessibility_control_change_unfavorite) - } else { - host.context.getString(R.string.accessibility_control_change_favorite) - } - val click = AccessibilityNodeInfoCompat.AccessibilityActionCompat( - AccessibilityNodeInfo.ACTION_CLICK, - // “favorite/unfavorite” - clickActionString) + val clickActionString = + if (isFavorite) { + host.context.getString(R.string.accessibility_control_change_unfavorite) + } else { + host.context.getString(R.string.accessibility_control_change_favorite) + } + val click = + AccessibilityNodeInfoCompat.AccessibilityActionCompat( + AccessibilityNodeInfo.ACTION_CLICK, + // “favorite/unfavorite” + clickActionString, + ) info.addAction(click) } private fun maybeAddMoveBeforeAction(host: View, info: AccessibilityNodeInfoCompat) { if (moveHelper?.canMoveBefore(positionRetriever()) ?: false) { val newPosition = positionRetriever() + 1 - 1 - val moveBefore = AccessibilityNodeInfoCompat.AccessibilityActionCompat( - MOVE_BEFORE_ID, - host.context.getString(R.string.accessibility_control_move, newPosition) - ) + val moveBefore = + AccessibilityNodeInfoCompat.AccessibilityActionCompat( + MOVE_BEFORE_ID, + host.context.getString(R.string.accessibility_control_move, newPosition), + ) info.addAction(moveBefore) info.isContextClickable = true } @@ -396,26 +410,25 @@ private class ControlHolderAccessibilityDelegate( private fun maybeAddMoveAfterAction(host: View, info: AccessibilityNodeInfoCompat) { if (moveHelper?.canMoveAfter(positionRetriever()) ?: false) { val newPosition = positionRetriever() + 1 + 1 - val moveAfter = AccessibilityNodeInfoCompat.AccessibilityActionCompat( - MOVE_AFTER_ID, - host.context.getString(R.string.accessibility_control_move, newPosition) - ) + val moveAfter = + AccessibilityNodeInfoCompat.AccessibilityActionCompat( + MOVE_AFTER_ID, + host.context.getString(R.string.accessibility_control_move, newPosition), + ) info.addAction(moveAfter) info.isContextClickable = true } } } -class MarginItemDecorator( - private val topMargin: Int, - private val sideMargins: Int -) : RecyclerView.ItemDecoration() { +class MarginItemDecorator(private val topMargin: Int, private val sideMargins: Int) : + RecyclerView.ItemDecoration() { override fun getItemOffsets( outRect: Rect, view: View, parent: RecyclerView, - state: RecyclerView.State + state: RecyclerView.State, ) { val position = parent.getChildAdapterPosition(view) if (position == RecyclerView.NO_POSITION) return 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 740e011b3d20..e4913cb59768 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt @@ -22,6 +22,7 @@ import android.content.ComponentName import android.content.Context import android.content.Intent import android.os.Bundle +import android.os.Process import android.util.Log import android.view.View import android.view.ViewGroup @@ -42,6 +43,7 @@ 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 com.android.systemui.utils.SafeIconLoader import java.util.concurrent.Executor import javax.inject.Inject @@ -53,6 +55,8 @@ constructor( private val controller: ControlsControllerImpl, private val userTracker: UserTracker, private val customIconCache: CustomIconCache, + private val controlsListingController: ControlsListingController, + private val safeIconLoaderFactory: SafeIconLoader.Factory, ) : ComponentActivity(), ControlsManagementActivity { companion object { @@ -258,8 +262,18 @@ constructor( val elevation = resources.getFloat(R.dimen.control_card_elevation) val recyclerView = requireViewById<RecyclerView>(R.id.list) recyclerView.alpha = 0.0f + val uid = + controlsListingController + .getCurrentServices() + .firstOrNull { it.componentName == component } + ?.serviceInfo + ?.applicationInfo + ?.uid ?: Process.INVALID_UID + val packageName = component.packageName + val safeIconLoader = safeIconLoaderFactory.create(uid, packageName, userTracker.userId) + val adapter = - ControlAdapter(elevation, userTracker.userId).apply { + ControlAdapter(elevation, userTracker.userId, safeIconLoader).apply { registerAdapterDataObserver( object : RecyclerView.AdapterDataObserver() { var hasAnimated = false 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 ab55c5326b55..80c9d0bb8858 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt @@ -25,6 +25,7 @@ import android.content.Context import android.content.Intent import android.content.res.Configuration import android.os.Bundle +import android.os.Process.INVALID_UID import android.text.TextUtils import android.util.Log import android.view.Gravity @@ -47,6 +48,7 @@ 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 com.android.systemui.utils.SafeIconLoader import java.text.Collator import java.util.concurrent.Executor import javax.inject.Inject @@ -57,6 +59,8 @@ constructor( @Main private val executor: Executor, private val controller: ControlsControllerImpl, private val userTracker: UserTracker, + private val safeIconLoaderFactory: SafeIconLoader.Factory, + private val controlsListingController: ControlsListingController, ) : ComponentActivity(), ControlsManagementActivity { companion object { @@ -196,9 +200,20 @@ constructor( listOfStructures = listOf(listOfStructures[structureIndex]) } + val uid = + controlsListingController + .getCurrentServices() + .firstOrNull { it.componentName == componentName } + ?.serviceInfo + ?.applicationInfo + ?.uid ?: INVALID_UID + val packageName = componentName.packageName + val safeIconLoader = + safeIconLoaderFactory.create(uid, packageName, userTracker.userId) + executor.execute { structurePager.adapter = - StructureAdapter(listOfStructures, userTracker.userId) + StructureAdapter(listOfStructures, userTracker.userId, safeIconLoader) structurePager.setCurrentItem(structureIndex) if (error) { statusText.text = @@ -260,8 +275,18 @@ constructor( private fun setUpPager() { structurePager.alpha = 0.0f pageIndicator.alpha = 0.0f + val uid = + controlsListingController + .getCurrentServices() + .firstOrNull { it.componentName == component } + ?.serviceInfo + ?.applicationInfo + ?.uid ?: INVALID_UID + val packageName = componentName?.packageName ?: "" + val safeIconLoader = safeIconLoaderFactory.create(uid, packageName, userTracker.userId) + structurePager.apply { - adapter = StructureAdapter(emptyList(), userTracker.userId) + adapter = StructureAdapter(emptyList(), userTracker.userId, safeIconLoader) registerOnPageChangeCallback( object : ViewPager2.OnPageChangeCallback() { override fun onPageSelected(position: Int) { diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/StructureAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/StructureAdapter.kt index 7e56077dec29..dc6f3c7a514b 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/StructureAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/StructureAdapter.kt @@ -22,10 +22,12 @@ import android.view.ViewGroup import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView import com.android.systemui.res.R +import com.android.systemui.utils.SafeIconLoader class StructureAdapter( private val models: List<StructureContainer>, private val currentUserId: Int, + private val safeIconLoader: SafeIconLoader, ) : RecyclerView.Adapter<StructureAdapter.StructureHolder>() { override fun onCreateViewHolder(parent: ViewGroup, p1: Int): StructureHolder { @@ -33,6 +35,7 @@ class StructureAdapter( return StructureHolder( layoutInflater.inflate(R.layout.controls_structure_page, parent, false), currentUserId, + safeIconLoader, ) } @@ -42,8 +45,8 @@ class StructureAdapter( holder.bind(models[index].model) } - class StructureHolder(view: View, currentUserId: Int) : - RecyclerView.ViewHolder(view) { + class StructureHolder(view: View, currentUserId: Int, safeIconLoader: SafeIconLoader) : + RecyclerView.ViewHolder(view) { private val recyclerView: RecyclerView private val controlAdapter: ControlAdapter @@ -51,7 +54,7 @@ class StructureAdapter( init { recyclerView = itemView.requireViewById<RecyclerView>(R.id.listAll) val elevation = itemView.context.resources.getFloat(R.dimen.control_card_elevation) - controlAdapter = ControlAdapter(elevation, currentUserId) + controlAdapter = ControlAdapter(elevation, currentUserId, safeIconLoader) setUpRecyclerView() } @@ -60,23 +63,29 @@ class StructureAdapter( } private fun setUpRecyclerView() { - val margin = itemView.context.resources - .getDimensionPixelSize(R.dimen.controls_card_margin) + val margin = + itemView.context.resources.getDimensionPixelSize(R.dimen.controls_card_margin) val itemDecorator = MarginItemDecorator(margin, margin) val spanCount = ControlAdapter.findMaxColumns(itemView.resources) recyclerView.apply { this.adapter = controlAdapter - layoutManager = GridLayoutManager(recyclerView.context, spanCount).apply { - spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() { - override fun getSpanSize(position: Int): Int { - return if (adapter?.getItemViewType(position) - != ControlAdapter.TYPE_CONTROL) spanCount else 1 - } + layoutManager = + GridLayoutManager(recyclerView.context, spanCount).apply { + spanSizeLookup = + object : GridLayoutManager.SpanSizeLookup() { + override fun getSpanSize(position: Int): Int { + return if ( + adapter?.getItemViewType(position) != + ControlAdapter.TYPE_CONTROL + ) + spanCount + else 1 + } + } } - } addItemDecoration(itemDecorator) } } } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt index fdb9971a7d63..5e09b7f03822 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt @@ -48,12 +48,13 @@ import android.widget.ImageView import android.widget.TextView import androidx.annotation.ColorInt import androidx.annotation.VisibleForTesting -import com.android.internal.graphics.ColorUtils -import com.android.systemui.res.R import com.android.app.animation.Interpolators +import com.android.internal.graphics.ColorUtils import com.android.systemui.controls.ControlsMetricsLogger import com.android.systemui.controls.controller.ControlsController +import com.android.systemui.res.R import com.android.systemui.util.concurrency.DelayableExecutor +import com.android.systemui.utils.SafeIconLoader import java.util.function.Supplier /** @@ -70,6 +71,7 @@ class ControlViewHolder( val controlsMetricsLogger: ControlsMetricsLogger, val uid: Int, val currentUserId: Int, + val safeIconLoader: SafeIconLoader, ) { companion object { @@ -78,10 +80,8 @@ class ControlViewHolder( private const val ALPHA_DISABLED = 0 private const val STATUS_ALPHA_ENABLED = 1f private const val STATUS_ALPHA_DIMMED = 0.45f - private val FORCE_PANEL_DEVICES = setOf( - DeviceTypes.TYPE_THERMOSTAT, - DeviceTypes.TYPE_CAMERA - ) + private val FORCE_PANEL_DEVICES = + setOf(DeviceTypes.TYPE_THERMOSTAT, DeviceTypes.TYPE_CAMERA) private val ATTR_ENABLED = intArrayOf(android.R.attr.state_enabled) private val ATTR_DISABLED = intArrayOf(-android.R.attr.state_enabled) const val MIN_LEVEL = 0 @@ -89,8 +89,8 @@ class ControlViewHolder( } private val canUseIconPredicate = CanUseIconPredicate(currentUserId) - private val toggleBackgroundIntensity: Float = layout.context.resources - .getFraction(R.fraction.controls_toggle_bg_intensity, 1, 1) + private val toggleBackgroundIntensity: Float = + layout.context.resources.getFraction(R.fraction.controls_toggle_bg_intensity, 1, 1) private var stateAnimator: ValueAnimator? = null private var statusAnimator: Animator? = null private val baseLayer: GradientDrawable @@ -112,8 +112,10 @@ class ControlViewHolder( val deviceType: Int get() = cws.control?.let { it.deviceType } ?: cws.ci.deviceType + val controlStatus: Int get() = cws.control?.let { it.status } ?: Control.STATUS_UNKNOWN + val controlTemplate: ControlTemplate get() = cws.control?.let { it.controlTemplate } ?: ControlTemplate.NO_TEMPLATE @@ -129,14 +131,15 @@ class ControlViewHolder( } fun findBehaviorClass( - status: Int, - template: ControlTemplate, - deviceType: Int + status: Int, + template: ControlTemplate, + deviceType: Int, ): Supplier<out Behavior> { return when { status != Control.STATUS_OK -> Supplier { StatusBehavior() } template == ControlTemplate.NO_TEMPLATE -> Supplier { TouchBehavior() } - template is ThumbnailTemplate -> Supplier { ThumbnailBehavior(currentUserId) } + template is ThumbnailTemplate -> + Supplier { ThumbnailBehavior(currentUserId, safeIconLoader) } // Required for legacy support, or where cameras do not use the new template deviceType == DeviceTypes.TYPE_CAMERA -> Supplier { TouchBehavior() } @@ -172,18 +175,20 @@ class ControlViewHolder( cws.control?.let { layout.setClickable(true) - layout.setOnLongClickListener(View.OnLongClickListener() { - controlActionCoordinator.longPress(this@ControlViewHolder) - true - }) + layout.setOnLongClickListener( + View.OnLongClickListener() { + controlActionCoordinator.longPress(this@ControlViewHolder) + true + } + ) controlActionCoordinator.runPendingAction(cws.ci.controlId) } val wasLoading = isLoading isLoading = false - behavior = bindBehavior(behavior, - findBehaviorClass(controlStatus, controlTemplate, deviceType)) + behavior = + bindBehavior(behavior, findBehaviorClass(controlStatus, controlTemplate, deviceType)) updateContentDescription() // Only log one event per control, at the moment we have determined that the control @@ -198,8 +203,7 @@ class ControlViewHolder( // OK responses signal normal behavior, and the app will provide control updates val failedAttempt = lastChallengeDialog != null when (response) { - ControlAction.RESPONSE_OK -> - lastChallengeDialog = null + ControlAction.RESPONSE_OK -> lastChallengeDialog = null ControlAction.RESPONSE_UNKNOWN -> { lastChallengeDialog = null setErrorStatus() @@ -209,18 +213,28 @@ class ControlViewHolder( setErrorStatus() } ControlAction.RESPONSE_CHALLENGE_PIN -> { - lastChallengeDialog = ChallengeDialogs.createPinDialog( - this, false /* useAlphanumeric */, failedAttempt, onDialogCancel) + lastChallengeDialog = + ChallengeDialogs.createPinDialog( + this, + false /* useAlphanumeric */, + failedAttempt, + onDialogCancel, + ) lastChallengeDialog?.show() } ControlAction.RESPONSE_CHALLENGE_PASSPHRASE -> { - lastChallengeDialog = ChallengeDialogs.createPinDialog( - this, true /* useAlphanumeric */, failedAttempt, onDialogCancel) + lastChallengeDialog = + ChallengeDialogs.createPinDialog( + this, + true /* useAlphanumeric */, + failedAttempt, + onDialogCancel, + ) lastChallengeDialog?.show() } ControlAction.RESPONSE_CHALLENGE_ACK -> { - lastChallengeDialog = ChallengeDialogs.createConfirmationDialog( - this, onDialogCancel) + lastChallengeDialog = + ChallengeDialogs.createConfirmationDialog(this, onDialogCancel) lastChallengeDialog?.show() } } @@ -235,9 +249,7 @@ class ControlViewHolder( fun setErrorStatus() { val text = context.resources.getString(R.string.controls_error_failed) - animateStatusChange(/* animated */ true, { - setStatusText(text, /* immediately */ true) - }) + animateStatusChange(/* animated */ true, { setStatusText(text, /* immediately */ true) }) } private fun updateContentDescription() = @@ -256,34 +268,32 @@ class ControlViewHolder( fun bindBehavior( existingBehavior: Behavior?, supplier: Supplier<out Behavior>, - offset: Int = 0 + offset: Int = 0, ): Behavior { val newBehavior = supplier.get() - val behavior = if (existingBehavior == null || - existingBehavior::class != newBehavior::class) { - // Behavior changes can signal a change in template from the app or - // first time setup - newBehavior.initialize(this) - - // let behaviors define their own, if necessary, and clear any existing ones - layout.setAccessibilityDelegate(null) - newBehavior - } else { - existingBehavior - } + val behavior = + if (existingBehavior == null || existingBehavior::class != newBehavior::class) { + // Behavior changes can signal a change in template from the app or + // first time setup + newBehavior.initialize(this) + + // let behaviors define their own, if necessary, and clear any existing ones + layout.setAccessibilityDelegate(null) + newBehavior + } else { + existingBehavior + } - return behavior.also { - it.bind(cws, offset) - } + return behavior.also { it.bind(cws, offset) } } internal fun applyRenderInfo(enabled: Boolean, offset: Int, animated: Boolean = true) { - val deviceTypeOrError = if (controlStatus == Control.STATUS_OK || - controlStatus == Control.STATUS_UNKNOWN) { - deviceType - } else { - RenderInfo.ERROR_ICON - } + val deviceTypeOrError = + if (controlStatus == Control.STATUS_OK || controlStatus == Control.STATUS_UNKNOWN) { + deviceType + } else { + RenderInfo.ERROR_ICON + } val ri = RenderInfo.lookup(context, cws.componentName, deviceTypeOrError, offset) val fg = context.resources.getColorStateList(ri.foreground, context.theme) val newText = nextStatusText @@ -317,28 +327,31 @@ class ControlViewHolder( private fun animateBackgroundChange( animated: Boolean, enabled: Boolean, - @ColorRes bgColor: Int + @ColorRes bgColor: Int, ) { val bg = context.resources.getColor(R.color.control_default_background, context.theme) - val (newClipColor, newAlpha) = if (enabled) { - // allow color overrides for the enabled state only - val color = cws.control?.getCustomColor()?.let { - val state = intArrayOf(android.R.attr.state_enabled) - it.getColorForState(state, it.getDefaultColor()) - } ?: context.resources.getColor(bgColor, context.theme) - listOf(color, ALPHA_ENABLED) - } else { - listOf( - context.resources.getColor(R.color.control_default_background, context.theme), - ALPHA_DISABLED - ) - } - val newBaseColor = if (behavior is ToggleRangeBehavior) { - ColorUtils.blendARGB(bg, newClipColor, toggleBackgroundIntensity) - } else { - bg - } + val (newClipColor, newAlpha) = + if (enabled) { + // allow color overrides for the enabled state only + val color = + cws.control?.getCustomColor()?.let { + val state = intArrayOf(android.R.attr.state_enabled) + it.getColorForState(state, it.getDefaultColor()) + } ?: context.resources.getColor(bgColor, context.theme) + listOf(color, ALPHA_ENABLED) + } else { + listOf( + context.resources.getColor(R.color.control_default_background, context.theme), + ALPHA_DISABLED, + ) + } + val newBaseColor = + if (behavior is ToggleRangeBehavior) { + ColorUtils.blendARGB(bg, newClipColor, toggleBackgroundIntensity) + } else { + bg + } clipLayer.drawable?.apply { clipLayer.alpha = ALPHA_DISABLED @@ -347,7 +360,11 @@ class ControlViewHolder( startBackgroundAnimation(this, newAlpha, newClipColor, newBaseColor) } else { applyBackgroundChange( - this, newAlpha, newClipColor, newBaseColor, newLayoutAlpha = 1f + this, + newAlpha, + newClipColor, + newBaseColor, + newLayoutAlpha = 1f, ) } } @@ -357,41 +374,45 @@ class ControlViewHolder( clipDrawable: Drawable, newAlpha: Int, @ColorInt newClipColor: Int, - @ColorInt newBaseColor: Int + @ColorInt newBaseColor: Int, ) { - val oldClipColor = if (clipDrawable is GradientDrawable) { - clipDrawable.color?.defaultColor ?: newClipColor - } else { - newClipColor - } + val oldClipColor = + if (clipDrawable is GradientDrawable) { + clipDrawable.color?.defaultColor ?: newClipColor + } else { + newClipColor + } val oldBaseColor = baseLayer.color?.defaultColor ?: newBaseColor val oldAlpha = layout.alpha - stateAnimator = ValueAnimator.ofInt(clipLayer.alpha, newAlpha).apply { - addUpdateListener { - val updatedAlpha = it.animatedValue as Int - val updatedClipColor = ColorUtils.blendARGB(oldClipColor, newClipColor, - it.animatedFraction) - val updatedBaseColor = ColorUtils.blendARGB(oldBaseColor, newBaseColor, - it.animatedFraction) - val updatedLayoutAlpha = MathUtils.lerp(oldAlpha, 1f, it.animatedFraction) - applyBackgroundChange( + stateAnimator = + ValueAnimator.ofInt(clipLayer.alpha, newAlpha).apply { + addUpdateListener { + val updatedAlpha = it.animatedValue as Int + val updatedClipColor = + ColorUtils.blendARGB(oldClipColor, newClipColor, it.animatedFraction) + val updatedBaseColor = + ColorUtils.blendARGB(oldBaseColor, newBaseColor, it.animatedFraction) + val updatedLayoutAlpha = MathUtils.lerp(oldAlpha, 1f, it.animatedFraction) + applyBackgroundChange( clipDrawable, updatedAlpha, updatedClipColor, updatedBaseColor, - updatedLayoutAlpha + updatedLayoutAlpha, + ) + } + addListener( + object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + stateAnimator = null + } + } ) + duration = STATE_ANIMATION_DURATION + interpolator = Interpolators.CONTROL_STATE + start() } - addListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - stateAnimator = null - } - }) - duration = STATE_ANIMATION_DURATION - interpolator = Interpolators.CONTROL_STATE - start() - } } /** @@ -405,7 +426,7 @@ class ControlViewHolder( newAlpha: Int, @ColorInt newClipColor: Int, @ColorInt newBaseColor: Int, - newLayoutAlpha: Float + newLayoutAlpha: Float, ) { clipDrawable.alpha = newAlpha if (clipDrawable is GradientDrawable) { @@ -425,38 +446,46 @@ class ControlViewHolder( if (isLoading) { statusRowUpdater.invoke() - statusAnimator = ObjectAnimator.ofFloat(status, "alpha", STATUS_ALPHA_DIMMED).apply { - repeatMode = ValueAnimator.REVERSE - repeatCount = ValueAnimator.INFINITE - duration = 500L - interpolator = Interpolators.LINEAR - startDelay = 900L - start() - } + statusAnimator = + ObjectAnimator.ofFloat(status, "alpha", STATUS_ALPHA_DIMMED).apply { + repeatMode = ValueAnimator.REVERSE + repeatCount = ValueAnimator.INFINITE + duration = 500L + interpolator = Interpolators.LINEAR + startDelay = 900L + start() + } } else { - val fadeOut = ObjectAnimator.ofFloat(status, "alpha", 0f).apply { - duration = 200L - interpolator = Interpolators.LINEAR - addListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - statusRowUpdater.invoke() - } - }) - } - val fadeIn = ObjectAnimator.ofFloat(status, "alpha", STATUS_ALPHA_ENABLED).apply { - duration = 200L - interpolator = Interpolators.LINEAR - } - statusAnimator = AnimatorSet().apply { - playSequentially(fadeOut, fadeIn) - addListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - status.alpha = STATUS_ALPHA_ENABLED - statusAnimator = null - } - }) - start() - } + val fadeOut = + ObjectAnimator.ofFloat(status, "alpha", 0f).apply { + duration = 200L + interpolator = Interpolators.LINEAR + addListener( + object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + statusRowUpdater.invoke() + } + } + ) + } + val fadeIn = + ObjectAnimator.ofFloat(status, "alpha", STATUS_ALPHA_ENABLED).apply { + duration = 200L + interpolator = Interpolators.LINEAR + } + statusAnimator = + AnimatorSet().apply { + playSequentially(fadeOut, fadeIn) + addListener( + object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + status.alpha = STATUS_ALPHA_ENABLED + statusAnimator = null + } + } + ) + start() + } } } @@ -466,7 +495,7 @@ class ControlViewHolder( text: CharSequence, drawable: Drawable, color: ColorStateList, - control: Control? + control: Control?, ) { setEnabled(enabled) @@ -475,29 +504,30 @@ class ControlViewHolder( status.setTextColor(color) - control?.customIcon - ?.takeIf(canUseIconPredicate) - ?.let { - icon.setImageIcon(it) + control?.customIcon?.takeIf(canUseIconPredicate)?.let { it -> + val loadedDrawable = safeIconLoader.load(it) + icon.setImageDrawable(loadedDrawable) icon.imageTintList = it.tintList - } ?: run { - if (drawable is StateListDrawable) { - // Only reset the drawable if it is a different resource, as it will interfere - // with the image state and animation. - if (icon.drawable == null || !(icon.drawable is StateListDrawable)) { + loadedDrawable + } + ?: run { + if (drawable is StateListDrawable) { + // Only reset the drawable if it is a different resource, as it will interfere + // with the image state and animation. + if (icon.drawable == null || !(icon.drawable is StateListDrawable)) { + icon.setImageDrawable(drawable) + } + val state = if (enabled) ATTR_ENABLED else ATTR_DISABLED + icon.setImageState(state, true) + } else { icon.setImageDrawable(drawable) } - val state = if (enabled) ATTR_ENABLED else ATTR_DISABLED - icon.setImageState(state, true) - } else { - icon.setImageDrawable(drawable) - } - // do not color app icons - if (deviceType != DeviceTypes.TYPE_ROUTINE) { - icon.imageTintList = color + // do not color app icons + if (deviceType != DeviceTypes.TYPE_ROUTINE) { + icon.imageTintList = color + } } - } chevronIcon.imageTintList = icon.imageTintList } diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt index 8831dc61e452..66bfa986901d 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt @@ -52,7 +52,6 @@ import android.widget.Space import android.widget.TextView import androidx.annotation.VisibleForTesting import com.android.systemui.Dumpable -import com.android.systemui.res.R import com.android.systemui.controls.ControlsMetricsLogger import com.android.systemui.controls.ControlsServiceInfo import com.android.systemui.controls.CustomIconCache @@ -74,11 +73,13 @@ import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager import com.android.systemui.flags.FeatureFlags import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.res.R import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.asIndenting import com.android.systemui.util.concurrency.DelayableExecutor import com.android.systemui.util.withIncreasedIndent +import com.android.systemui.utils.SafeIconLoader import com.android.wm.shell.taskview.TaskViewFactory import dagger.Lazy import java.io.PrintWriter @@ -90,26 +91,29 @@ import javax.inject.Inject private data class ControlKey(val componentName: ComponentName, val controlId: String) @SysUISingleton -class ControlsUiControllerImpl @Inject constructor ( - val controlsController: Lazy<ControlsController>, - val context: Context, - private val packageManager: PackageManager, - @Main val uiExecutor: DelayableExecutor, - @Background val bgExecutor: DelayableExecutor, - val controlsListingController: Lazy<ControlsListingController>, - private val controlActionCoordinator: ControlActionCoordinator, - private val activityStarter: ActivityStarter, - private val iconCache: CustomIconCache, - private val controlsMetricsLogger: ControlsMetricsLogger, - private val keyguardStateController: KeyguardStateController, - private val userTracker: UserTracker, - private val taskViewFactory: Optional<TaskViewFactory>, - private val controlsSettingsRepository: ControlsSettingsRepository, - private val authorizedPanelsRepository: AuthorizedPanelsRepository, - private val selectedComponentRepository: SelectedComponentRepository, - private val featureFlags: FeatureFlags, - private val dialogsFactory: ControlsDialogsFactory, - dumpManager: DumpManager +class ControlsUiControllerImpl +@Inject +constructor( + val controlsController: Lazy<ControlsController>, + val context: Context, + private val packageManager: PackageManager, + @Main val uiExecutor: DelayableExecutor, + @Background val bgExecutor: DelayableExecutor, + val controlsListingController: Lazy<ControlsListingController>, + private val controlActionCoordinator: ControlActionCoordinator, + private val activityStarter: ActivityStarter, + private val iconCache: CustomIconCache, + private val controlsMetricsLogger: ControlsMetricsLogger, + private val keyguardStateController: KeyguardStateController, + private val userTracker: UserTracker, + private val taskViewFactory: Optional<TaskViewFactory>, + private val controlsSettingsRepository: ControlsSettingsRepository, + private val authorizedPanelsRepository: AuthorizedPanelsRepository, + private val selectedComponentRepository: SelectedComponentRepository, + private val featureFlags: FeatureFlags, + private val safeIconLoaderFactory: SafeIconLoader.Factory, + private val dialogsFactory: ControlsDialogsFactory, + dumpManager: DumpManager, ) : ControlsUiController, Dumpable { companion object { @@ -139,26 +143,26 @@ class ControlsUiControllerImpl @Inject constructor ( private var taskViewController: PanelTaskViewController? = null private val collator = Collator.getInstance(context.resources.configuration.locales[0]) - private val localeComparator = compareBy<SelectionItem, CharSequence>(collator) { - it.getTitle() - } + private val localeComparator = + compareBy<SelectionItem, CharSequence>(collator) { it.getTitle() } private var openAppIntent: Intent? = null private var overflowMenuAdapter: BaseAdapter? = null private var removeAppDialog: Dialog? = null - private val onSeedingComplete = Consumer<Boolean> { - accepted -> + private val onSeedingComplete = + Consumer<Boolean> { accepted -> if (accepted) { - selectedItem = controlsController.get().getFavorites().maxByOrNull { - it.controls.size - }?.let { - SelectedItem.StructureItem(it) - } ?: SelectedItem.EMPTY_SELECTION + selectedItem = + controlsController + .get() + .getFavorites() + .maxByOrNull { it.controls.size } + ?.let { SelectedItem.StructureItem(it) } ?: SelectedItem.EMPTY_SELECTION updatePreferences(selectedItem) } reload(parent) - } + } private lateinit var activityContext: Context private lateinit var listingCallback: ControlsListingController.ControlsListingCallback @@ -176,10 +180,11 @@ class ControlsUiControllerImpl @Inject constructor ( return object : ControlsListingController.ControlsListingCallback { override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) { val authorizedPanels = authorizedPanelsRepository.getAuthorizedPanels() - val lastItems = serviceInfos.map { - val uid = it.serviceInfo.applicationInfo.uid + val lastItems = + serviceInfos.map { + val uid = it.serviceInfo.applicationInfo.uid - SelectionItem( + SelectionItem( it.loadLabel(), "", it.loadIcon(), @@ -189,9 +194,9 @@ class ControlsUiControllerImpl @Inject constructor ( it.panelActivity } else { null - } - ) - } + }, + ) + } uiExecutor.execute { parent.removeAllViews() if (lastItems.size > 0) { @@ -205,8 +210,8 @@ class ControlsUiControllerImpl @Inject constructor ( override fun resolveActivity(): Class<*> { val allStructures = controlsController.get().getFavorites() val selected = getPreferredSelectedItem(allStructures) - val anyPanels = controlsListingController.get().getCurrentServices() - .any { it.panelActivity != null } + val anyPanels = + controlsListingController.get().getCurrentServices().any { it.panelActivity != null } return if (controlsController.get().addSeedingFavoritesCallback(onSeedingComplete)) { ControlsActivity::class.java @@ -217,11 +222,7 @@ class ControlsUiControllerImpl @Inject constructor ( } } - override fun show( - parent: ViewGroup, - onDismiss: Runnable, - activityContext: Context - ) { + override fun show(parent: ViewGroup, onDismiss: Runnable, activityContext: Context) { Log.d(ControlsUiController.TAG, "show()") Trace.instant(Trace.TRACE_TAG_APP, "ControlsUiControllerImpl#show") this.parent = parent @@ -241,7 +242,7 @@ class ControlsUiControllerImpl @Inject constructor ( if (controlsController.get().addSeedingFavoritesCallback(onSeedingComplete)) { listingCallback = createCallback(::showSeedingView) } else if ( - selectedItem !is SelectedItem.PanelItem && + selectedItem !is SelectedItem.PanelItem && !selectedItem.hasControls && allStructures.size <= 1 ) { @@ -250,11 +251,11 @@ class ControlsUiControllerImpl @Inject constructor ( } else { val selected = selectedItem if (selected is SelectedItem.StructureItem) { - selected.structure.controls.map { - ControlWithState(selected.structure.componentName, it, null) - }.associateByTo(controlsById) { - ControlKey(selected.structure.componentName, it.ci.controlId) - } + selected.structure.controls + .map { ControlWithState(selected.structure.componentName, it, null) } + .associateByTo(controlsById) { + ControlKey(selected.structure.componentName, it.ci.controlId) + } controlsController.get().subscribeToFavorites(selected.structure) } else { controlsController.get().bindComponentForPanel(selected.componentName) @@ -285,18 +286,20 @@ class ControlsUiControllerImpl @Inject constructor ( val fadeAnim = ObjectAnimator.ofFloat(parent, "alpha", 1.0f, 0.0f) fadeAnim.setInterpolator(AccelerateInterpolator(1.0f)) fadeAnim.setDuration(FADE_IN_MILLIS) - fadeAnim.addListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - controlViewsById.clear() - controlsById.clear() - - show(parent, onDismiss, activityContext) - val showAnim = ObjectAnimator.ofFloat(parent, "alpha", 0.0f, 1.0f) - showAnim.setInterpolator(DecelerateInterpolator(1.0f)) - showAnim.setDuration(FADE_IN_MILLIS) - showAnim.start() + fadeAnim.addListener( + object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + controlViewsById.clear() + controlsById.clear() + + show(parent, onDismiss, activityContext) + val showAnim = ObjectAnimator.ofFloat(parent, "alpha", 0.0f, 1.0f) + showAnim.setInterpolator(DecelerateInterpolator(1.0f)) + showAnim.setDuration(FADE_IN_MILLIS) + showAnim.start() + } } - }) + ) fadeAnim.start() } @@ -321,39 +324,48 @@ class ControlsUiControllerImpl @Inject constructor ( } private fun startDefaultActivity() { - openAppIntent?.let { - startActivity(it, animateExtra = false) - } + openAppIntent?.let { startActivity(it, animateExtra = false) } } @VisibleForTesting internal fun startRemovingApp(componentName: ComponentName, appName: CharSequence) { - activityStarter.dismissKeyguardThenExecute({ - showAppRemovalDialog(componentName, appName) - true - }, null, true) + activityStarter.dismissKeyguardThenExecute( + { + showAppRemovalDialog(componentName, appName) + true + }, + null, + true, + ) } private fun showAppRemovalDialog(componentName: ComponentName, appName: CharSequence) { removeAppDialog?.cancel() - removeAppDialog = dialogsFactory.createRemoveAppDialog(context, appName) { shouldRemove -> - if (!shouldRemove || !controlsController.get().removeFavorites(componentName)) { - return@createRemoveAppDialog - } + removeAppDialog = + dialogsFactory + .createRemoveAppDialog(context, appName) { shouldRemove -> + if (!shouldRemove || !controlsController.get().removeFavorites(componentName)) { + return@createRemoveAppDialog + } - if (selectedComponentRepository.getSelectedComponent()?.componentName == - componentName) { - selectedComponentRepository.removeSelectedComponent() - } + if ( + selectedComponentRepository.getSelectedComponent()?.componentName == + componentName + ) { + selectedComponentRepository.removeSelectedComponent() + } - val selectedItem = getPreferredSelectedItem(controlsController.get().getFavorites()) - if (selectedItem == SelectedItem.EMPTY_SELECTION) { - // User removed the last panel. In this case we start app selection flow and don't - // want to auto-add it again - selectedComponentRepository.setShouldAddDefaultComponent(false) - } - reload(parent) - }.apply { show() } + val selectedItem = + getPreferredSelectedItem(controlsController.get().getFavorites()) + if (selectedItem == SelectedItem.EMPTY_SELECTION) { + // User removed the last panel. In this case we start app selection flow and + // don't + // want to auto-add it again + selectedComponentRepository.setShouldAddDefaultComponent(false) + } + reload(parent) + } + .apply { show() } } private fun startTargetedActivity(si: StructureInfo, klazz: Class<*>) { @@ -366,8 +378,10 @@ class ControlsUiControllerImpl @Inject constructor ( private fun putIntentExtras(intent: Intent, si: StructureInfo) { intent.apply { - putExtra(ControlsFavoritingActivity.EXTRA_APP, - controlsListingController.get().getAppLabel(si.componentName)) + putExtra( + ControlsFavoritingActivity.EXTRA_APP, + controlsListingController.get().getAppLabel(si.componentName), + ) putExtra(ControlsFavoritingActivity.EXTRA_STRUCTURE, si.structure) putExtra(Intent.EXTRA_COMPONENT_NAME, si.componentName) } @@ -390,7 +404,7 @@ class ControlsUiControllerImpl @Inject constructor ( } else { activityContext.startActivity( intent, - ActivityOptions.makeSceneTransitionAnimation(activityContext as Activity).toBundle() + ActivityOptions.makeSceneTransitionAnimation(activityContext as Activity).toBundle(), ) } } @@ -401,8 +415,8 @@ class ControlsUiControllerImpl @Inject constructor ( val (panels, structures) = items.partition { it.isPanel } val panelComponents = panels.map { it.componentName }.toSet() - val itemsByComponent = structures.associateBy { it.componentName } - .filterNot { it.key in panelComponents } + val itemsByComponent = + structures.associateBy { it.componentName }.filterNot { it.key in panelComponents } val panelsAndStructures = mutableListOf<SelectionItem>() allStructures.mapNotNullTo(panelsAndStructures) { itemsByComponent.get(it.componentName)?.copy(structure = it.structure) @@ -413,7 +427,8 @@ class ControlsUiControllerImpl @Inject constructor ( lastSelections = panelsAndStructures - val selectionItem = findSelectionItem(selectedItem, panelsAndStructures) + val selectionItem = + findSelectionItem(selectedItem, panelsAndStructures) ?: if (panels.isNotEmpty()) { // If we couldn't find a good selected item, but there's at least one panel, // show a panel. @@ -428,8 +443,10 @@ class ControlsUiControllerImpl @Inject constructor ( if (taskViewFactory.isPresent && selectionItem.isPanel) { createPanelView(selectionItem.panelComponentName!!) } else if (!selectionItem.isPanel) { - controlsMetricsLogger - .refreshBegin(selectionItem.uid, !keyguardStateController.isUnlocked()) + controlsMetricsLogger.refreshBegin( + selectionItem.uid, + !keyguardStateController.isUnlocked(), + ) createListView(selectionItem) } else { Log.w(ControlsUiController.TAG, "Not TaskViewFactory to display panel $selectionItem") @@ -437,176 +454,200 @@ class ControlsUiControllerImpl @Inject constructor ( this.selectionItem = selectionItem bgExecutor.execute { - val intent = Intent(Intent.ACTION_MAIN) + val intent = + Intent(Intent.ACTION_MAIN) .addCategory(Intent.CATEGORY_LAUNCHER) .setPackage(selectionItem.componentName.packageName) - .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or - Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) - val intents = packageManager - .queryIntentActivities(intent, PackageManager.ResolveInfoFlags.of(0L)) - intents.firstOrNull { it.activityInfo.exported }?.let { resolved -> - intent.setPackage(null) - intent.setComponent(resolved.activityInfo.componentName) - openAppIntent = intent - parent.post { - // This will call show on the PopupWindow in the same thread, so make sure this - // happens in the view thread. - overflowMenuAdapter?.notifyDataSetChanged() + .addFlags( + Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED + ) + val intents = + packageManager.queryIntentActivities(intent, PackageManager.ResolveInfoFlags.of(0L)) + intents + .firstOrNull { it.activityInfo.exported } + ?.let { resolved -> + intent.setPackage(null) + intent.setComponent(resolved.activityInfo.componentName) + openAppIntent = intent + parent.post { + // This will call show on the PopupWindow in the same thread, so make sure + // this + // happens in the view thread. + overflowMenuAdapter?.notifyDataSetChanged() + } } - } } createDropDown(panelsAndStructures, selectionItem) val currentApps = panelsAndStructures.map { it.componentName }.toSet() - val allApps = controlsListingController.get() - .getCurrentServices().map { it.componentName }.toSet() - createMenu( - selectionItem = selectionItem, - extraApps = (allApps - currentApps).isNotEmpty(), - ) + val allApps = + controlsListingController.get().getCurrentServices().map { it.componentName }.toSet() + createMenu(selectionItem = selectionItem, extraApps = (allApps - currentApps).isNotEmpty()) } private fun createPanelView(componentName: ComponentName) { - val setting = controlsSettingsRepository - .allowActionOnTrivialControlsInLockscreen.value - val pendingIntent = PendingIntent.getActivityAsUser( + val setting = controlsSettingsRepository.allowActionOnTrivialControlsInLockscreen.value + val pendingIntent = + PendingIntent.getActivityAsUser( context, 0, Intent().apply { component = componentName putExtra( ControlsProviderService.EXTRA_LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS, - setting + setting, ) if (homePanelDream()) { - putExtra(ControlsProviderService.EXTRA_CONTROLS_SURFACE, - ControlsProviderService.CONTROLS_SURFACE_ACTIVITY_PANEL) + putExtra( + ControlsProviderService.EXTRA_CONTROLS_SURFACE, + ControlsProviderService.CONTROLS_SURFACE_ACTIVITY_PANEL, + ) } }, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT, ActivityOptions.makeBasic() .setPendingIntentCreatorBackgroundActivityStartMode( - ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED).toBundle(), - userTracker.userHandle - ) + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED + ) + .toBundle(), + userTracker.userHandle, + ) parent.requireViewById<View>(R.id.controls_scroll_view).visibility = View.GONE val container = parent.requireViewById<FrameLayout>(R.id.controls_panel) container.visibility = View.VISIBLE container.post { taskViewFactory.get().create(activityContext, uiExecutor) { taskView -> - taskViewController = PanelTaskViewController( - activityContext, - uiExecutor, - pendingIntent, - taskView, - onDismiss::run - ).also { - container.addView(taskView) - it.launchTaskView() - } + taskViewController = + PanelTaskViewController( + activityContext, + uiExecutor, + pendingIntent, + taskView, + onDismiss::run, + ) + .also { + container.addView(taskView) + it.launchTaskView() + } } } } private fun createMenu(selectionItem: SelectionItem, extraApps: Boolean) { val isPanel = selectedItem is SelectedItem.PanelItem - val selectedStructure = (selectedItem as? SelectedItem.StructureItem)?.structure - ?: EMPTY_STRUCTURE + val selectedStructure = + (selectedItem as? SelectedItem.StructureItem)?.structure ?: EMPTY_STRUCTURE val items = buildList { - add(OverflowMenuAdapter.MenuItem( + add( + OverflowMenuAdapter.MenuItem( context.getText(R.string.controls_open_app), - OPEN_APP_ID - )) + OPEN_APP_ID, + ) + ) if (extraApps) { - add(OverflowMenuAdapter.MenuItem( + add( + OverflowMenuAdapter.MenuItem( context.getText(R.string.controls_menu_add_another_app), - ADD_APP_ID - )) + ADD_APP_ID, + ) + ) } - add(OverflowMenuAdapter.MenuItem( + add( + OverflowMenuAdapter.MenuItem( context.getText(R.string.controls_menu_remove), REMOVE_APP_ID, - )) + ) + ) if (!isPanel) { - add(OverflowMenuAdapter.MenuItem( + add( + OverflowMenuAdapter.MenuItem( context.getText(R.string.controls_menu_edit), - EDIT_CONTROLS_ID - )) + EDIT_CONTROLS_ID, + ) + ) } } - val adapter = OverflowMenuAdapter(context, R.layout.controls_more_item, items) { position -> + val adapter = + OverflowMenuAdapter(context, R.layout.controls_more_item, items) { position -> getItemId(position) != OPEN_APP_ID || openAppIntent != null - } + } val anchor = parent.requireViewById<ImageView>(R.id.controls_more) - anchor.setOnClickListener(object : View.OnClickListener { - override fun onClick(v: View) { - popup = ControlsPopupMenu(popupThemedContext).apply { - width = ViewGroup.LayoutParams.WRAP_CONTENT - anchorView = anchor - setDropDownGravity(Gravity.END) - setAdapter(adapter) - - setOnItemClickListener(object : AdapterView.OnItemClickListener { - override fun onItemClick( - parent: AdapterView<*>, - view: View, - pos: Int, - id: Long - ) { - when (id) { - OPEN_APP_ID -> startDefaultActivity() - ADD_APP_ID -> startProviderSelectorActivity() - ADD_CONTROLS_ID -> startFavoritingActivity(selectedStructure) - EDIT_CONTROLS_ID -> startEditingActivity(selectedStructure) - REMOVE_APP_ID -> startRemovingApp( - selectionItem.componentName, selectionItem.appName - ) - } - dismiss() + anchor.setOnClickListener( + object : View.OnClickListener { + override fun onClick(v: View) { + popup = + ControlsPopupMenu(popupThemedContext).apply { + width = ViewGroup.LayoutParams.WRAP_CONTENT + anchorView = anchor + setDropDownGravity(Gravity.END) + setAdapter(adapter) + + setOnItemClickListener( + object : AdapterView.OnItemClickListener { + override fun onItemClick( + parent: AdapterView<*>, + view: View, + pos: Int, + id: Long, + ) { + when (id) { + OPEN_APP_ID -> startDefaultActivity() + ADD_APP_ID -> startProviderSelectorActivity() + ADD_CONTROLS_ID -> + startFavoritingActivity(selectedStructure) + EDIT_CONTROLS_ID -> + startEditingActivity(selectedStructure) + REMOVE_APP_ID -> + startRemovingApp( + selectionItem.componentName, + selectionItem.appName, + ) + } + dismiss() + } + } + ) + show() + listView?.post { listView?.requestAccessibilityFocus() } } - }) - show() - listView?.post { listView?.requestAccessibilityFocus() } } } - }) + ) overflowMenuAdapter = adapter } private fun createDropDown(items: List<SelectionItem>, selected: SelectionItem) { - items.forEach { - RenderInfo.registerComponentIcon(it.componentName, it.icon) - } + items.forEach { RenderInfo.registerComponentIcon(it.componentName, it.icon) } - val adapter = ItemAdapter(context, R.layout.controls_spinner_item).apply { - add(selected) - addAll(items - .filter { it !== selected } - .sortedBy { it.appName.toString() } - ) - } + val adapter = + ItemAdapter(context, R.layout.controls_spinner_item).apply { + add(selected) + addAll(items.filter { it !== selected }.sortedBy { it.appName.toString() }) + } - val iconSize = context.resources - .getDimensionPixelSize(R.dimen.controls_header_app_icon_size) + val iconSize = + context.resources.getDimensionPixelSize(R.dimen.controls_header_app_icon_size) /* * Default spinner widget does not work with the window type required * for this dialog. Use a textView with the ListPopupWindow to achieve * a similar effect */ - val spinner = parent.requireViewById<TextView>(R.id.app_or_structure_spinner).apply { - setText(selected.getTitle()) - // override the default color on the dropdown drawable - (getBackground() as LayerDrawable).getDrawable(0) - .setTint(context.resources.getColor(R.color.control_spinner_dropdown, null)) - selected.icon.setBounds(0, 0, iconSize, iconSize) - compoundDrawablePadding = (iconSize / 2.4f).toInt() - setCompoundDrawablesRelative(selected.icon, null, null, null) - } + val spinner = + parent.requireViewById<TextView>(R.id.app_or_structure_spinner).apply { + setText(selected.getTitle()) + // override the default color on the dropdown drawable + (getBackground() as LayerDrawable) + .getDrawable(0) + .setTint(context.resources.getColor(R.color.control_spinner_dropdown, null)) + selected.icon.setBounds(0, 0, iconSize, iconSize) + compoundDrawablePadding = (iconSize / 2.4f).toInt() + setCompoundDrawablesRelative(selected.icon, null, null, null) + } val anchor = parent.requireViewById<View>(R.id.app_or_structure_spinner) if (items.size == 1) { @@ -615,34 +656,40 @@ class ControlsUiControllerImpl @Inject constructor ( anchor.isClickable = false return } else { - spinner.background = parent.context.resources - .getDrawable(R.drawable.control_spinner_background) - } - - anchor.setOnClickListener(object : View.OnClickListener { - override fun onClick(v: View) { - popup = ControlsPopupMenu(popupThemedContext).apply { - anchorView = anchor - width = ViewGroup.LayoutParams.MATCH_PARENT - setAdapter(adapter) - - setOnItemClickListener(object : AdapterView.OnItemClickListener { - override fun onItemClick( - parent: AdapterView<*>, - view: View, - pos: Int, - id: Long - ) { - val listItem = parent.getItemAtPosition(pos) as SelectionItem - this@ControlsUiControllerImpl.switchAppOrStructure(listItem) - dismiss() + spinner.background = + parent.context.resources.getDrawable(R.drawable.control_spinner_background) + } + + anchor.setOnClickListener( + object : View.OnClickListener { + override fun onClick(v: View) { + popup = + ControlsPopupMenu(popupThemedContext).apply { + anchorView = anchor + width = ViewGroup.LayoutParams.MATCH_PARENT + setAdapter(adapter) + + setOnItemClickListener( + object : AdapterView.OnItemClickListener { + override fun onItemClick( + parent: AdapterView<*>, + view: View, + pos: Int, + id: Long, + ) { + val listItem = + parent.getItemAtPosition(pos) as SelectionItem + this@ControlsUiControllerImpl.switchAppOrStructure(listItem) + dismiss() + } + } + ) + show() + listView?.post { listView?.requestAccessibilityFocus() } } - }) - show() - listView?.post { listView?.requestAccessibilityFocus() } } } - }) + ) } private fun createControlsSpaceFrame() { @@ -658,6 +705,12 @@ class ControlsUiControllerImpl @Inject constructor ( private fun createListView(selected: SelectionItem) { if (selectedItem !is SelectedItem.StructureItem) return val selectedStructure = (selectedItem as SelectedItem.StructureItem).structure + val safeIconLoader = + safeIconLoaderFactory.create( + selected.uid, + selected.componentName.packageName, + controlsController.get().currentUserId, + ) val inflater = LayoutInflater.from(activityContext) val maxColumns = ControlAdapter.findMaxColumns(activityContext.resources) @@ -671,8 +724,8 @@ class ControlsUiControllerImpl @Inject constructor ( if (lastRow.getChildCount() == maxColumns) { lastRow = createRow(inflater, listView) } - val baseLayout = inflater.inflate( - R.layout.controls_base_item, lastRow, false) as ViewGroup + val baseLayout = + inflater.inflate(R.layout.controls_base_item, lastRow, false) as ViewGroup lastRow.addView(baseLayout) // Use ConstraintLayout in the future... for now, manually adjust margins @@ -680,16 +733,18 @@ class ControlsUiControllerImpl @Inject constructor ( val lp = baseLayout.getLayoutParams() as ViewGroup.MarginLayoutParams lp.setMarginStart(0) } - val cvh = ControlViewHolder( - baseLayout, - controlsController.get(), - uiExecutor, - bgExecutor, - controlActionCoordinator, - controlsMetricsLogger, - selected.uid, - controlsController.get().currentUserId, - ) + val cvh = + ControlViewHolder( + baseLayout, + controlsController.get(), + uiExecutor, + bgExecutor, + controlActionCoordinator, + controlsMetricsLogger, + selected.uid, + controlsController.get().currentUserId, + safeIconLoader, + ) cvh.bindData(it, false /* isLocked, will be ignored on initial load */) controlViewsById.put(key, cvh) } @@ -700,9 +755,7 @@ class ControlsUiControllerImpl @Inject constructor ( var spacersToAdd = if (mod == 0) 0 else maxColumns - mod val margin = context.resources.getDimensionPixelSize(R.dimen.control_spacing) while (spacersToAdd > 0) { - val lp = LinearLayout.LayoutParams(0, 0, 1f).apply { - setMarginStart(margin) - } + val lp = LinearLayout.LayoutParams(0, 0, 1f).apply { setMarginStart(margin) } lastRow.addView(Space(context), lp) spacersToAdd-- } @@ -715,27 +768,32 @@ class ControlsUiControllerImpl @Inject constructor ( SelectedItem.PanelItem(preferredPanel.name, component) } else { if (structures.isEmpty()) return SelectedItem.EMPTY_SELECTION - SelectedItem.StructureItem(structures.firstOrNull { - component == it.componentName && preferredPanel?.name == it.structure - } ?: structures[0]) + SelectedItem.StructureItem( + structures.firstOrNull { + component == it.componentName && preferredPanel?.name == it.structure + } ?: structures[0] + ) } } private fun updatePreferences(selectedItem: SelectedItem) { selectedComponentRepository.setSelectedComponent( - SelectedComponentRepository.SelectedComponent(selectedItem) + SelectedComponentRepository.SelectedComponent(selectedItem) ) } private fun maybeUpdateSelectedItem(item: SelectionItem): Boolean { - val newSelection = if (item.isPanel) { - SelectedItem.PanelItem(item.appName, item.componentName) - } else { - SelectedItem.StructureItem(allStructures.firstOrNull { - it.structure == item.structure && it.componentName == item.componentName - } ?: EMPTY_STRUCTURE) - } - return if (newSelection != selectedItem ) { + val newSelection = + if (item.isPanel) { + SelectedItem.PanelItem(item.appName, item.componentName) + } else { + SelectedItem.StructureItem( + allStructures.firstOrNull { + it.structure == item.structure && it.componentName == item.componentName + } ?: EMPTY_STRUCTURE + ) + } + return if (newSelection != selectedItem) { selectedItem = newSelection updatePreferences(selectedItem) true @@ -758,9 +816,7 @@ class ControlsUiControllerImpl @Inject constructor ( } popup = null - controlViewsById.forEach { - it.value.dismiss() - } + controlViewsById.forEach { it.value.dismiss() } controlActionCoordinator.closeDialogs() removeAppDialog?.cancel() } @@ -798,18 +854,14 @@ class ControlsUiControllerImpl @Inject constructor ( val key = ControlKey(componentName, c.getControlId()) controlsById.put(key, cws) - controlViewsById.get(key)?.let { - uiExecutor.execute { it.bindData(cws, isLocked) } - } + controlViewsById.get(key)?.let { uiExecutor.execute { it.bindData(cws, isLocked) } } } } } override fun onActionResponse(componentName: ComponentName, controlId: String, response: Int) { val key = ControlKey(componentName, controlId) - uiExecutor.execute { - controlViewsById.get(key)?.actionResponse(response) - } + uiExecutor.execute { controlViewsById.get(key)?.actionResponse(response) } } override fun onSizeChange() { @@ -830,16 +882,19 @@ class ControlsUiControllerImpl @Inject constructor ( private fun findSelectionItem(si: SelectedItem, items: List<SelectionItem>): SelectionItem? = items.firstOrNull { it.matches(si) } - override fun dump(pw: PrintWriter, args: Array<out String>) = pw.asIndenting().run { - println("ControlsUiControllerImpl:") - withIncreasedIndent { - println("hidden: $hidden") - println("selectedItem: $selectedItem") - println("lastSelections: $lastSelections") - println("setting: ${controlsSettingsRepository - .allowActionOnTrivialControlsInLockscreen.value}") + override fun dump(pw: PrintWriter, args: Array<out String>) = + pw.asIndenting().run { + println("ControlsUiControllerImpl:") + withIncreasedIndent { + println("hidden: $hidden") + println("selectedItem: $selectedItem") + println("lastSelections: $lastSelections") + println( + "setting: ${controlsSettingsRepository + .allowActionOnTrivialControlsInLockscreen.value}" + ) + } } - } } @VisibleForTesting @@ -849,9 +904,14 @@ internal data class SelectionItem( val icon: Drawable, val componentName: ComponentName, val uid: Int, - val panelComponentName: ComponentName? + val panelComponentName: ComponentName?, ) { - fun getTitle() = if (structure.isEmpty()) { appName } else { structure } + fun getTitle() = + if (structure.isEmpty()) { + appName + } else { + structure + } val isPanel: Boolean = panelComponentName != null @@ -872,7 +932,7 @@ internal data class SelectionItem( } private class ItemAdapter(parentContext: Context, val resource: Int) : - ArrayAdapter<SelectionItem>(parentContext, resource) { + ArrayAdapter<SelectionItem>(parentContext, resource) { private val layoutInflater = LayoutInflater.from(context)!! diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ThumbnailBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ThumbnailBehavior.kt index 41907882a443..e640be94073e 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ThumbnailBehavior.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ThumbnailBehavior.kt @@ -20,21 +20,21 @@ import android.graphics.BlendMode import android.graphics.BlendModeColorFilter import android.graphics.drawable.ClipDrawable import android.graphics.drawable.LayerDrawable -import android.view.View import android.service.controls.Control import android.service.controls.templates.TemperatureControlTemplate import android.service.controls.templates.ThumbnailTemplate import android.util.TypedValue - -import com.android.systemui.res.R +import android.view.View import com.android.systemui.controls.ui.ControlViewHolder.Companion.MAX_LEVEL import com.android.systemui.controls.ui.ControlViewHolder.Companion.MIN_LEVEL +import com.android.systemui.res.R +import com.android.systemui.utils.SafeIconLoader /** * Supports display of static images on the background of the tile. When marked active, the title * and subtitle will not be visible. To be used with {@link Thumbnailtemplate} only. */ -class ThumbnailBehavior(currentUserId: Int) : Behavior { +class ThumbnailBehavior(currentUserId: Int, private val safeIconLoader: SafeIconLoader) : Behavior { lateinit var template: ThumbnailTemplate lateinit var control: Control lateinit var cvh: ControlViewHolder @@ -61,17 +61,20 @@ class ThumbnailBehavior(currentUserId: Int) : Behavior { shadowRadius = outValue.getFloat() shadowColor = cvh.context.resources.getColor(R.color.control_thumbnail_shadow_color) - cvh.layout.setOnClickListener(View.OnClickListener() { - cvh.controlActionCoordinator.touch(cvh, template.getTemplateId(), control) - }) + cvh.layout.setOnClickListener( + View.OnClickListener() { + cvh.controlActionCoordinator.touch(cvh, template.getTemplateId(), control) + } + ) } override fun bind(cws: ControlWithState, colorOffset: Int) { this.control = cws.control!! cvh.setStatusText(control.getStatusText()) - template = control.controlTemplate as? ThumbnailTemplate + template = + control.controlTemplate as? ThumbnailTemplate ?: (control.controlTemplate as TemperatureControlTemplate).template - as ThumbnailTemplate + as ThumbnailTemplate val ld = cvh.layout.getBackground() as LayerDrawable val clipLayer = ld.findDrawableByLayerId(R.id.clip_layer) as ClipDrawable @@ -84,18 +87,22 @@ class ThumbnailBehavior(currentUserId: Int) : Behavior { cvh.status.setShadowLayer(shadowOffsetX, shadowOffsetY, shadowRadius, shadowColor) cvh.bgExecutor.execute { - val drawable = template.thumbnail - ?.takeIf(canUseIconPredicate) - ?.loadDrawable(cvh.context) + val drawable = + template.thumbnail.takeIf(canUseIconPredicate)?.let { safeIconLoader.load(it) } cvh.uiExecutor.execute { - val radius = cvh.context.getResources() - .getDimensionPixelSize(R.dimen.control_corner_radius).toFloat() + val radius = + cvh.context + .getResources() + .getDimensionPixelSize(R.dimen.control_corner_radius) + .toFloat() // TODO(b/290037843): Add a placeholder - drawable?.let { - clipLayer.drawable = CornerDrawable(it, radius) - } - clipLayer.setColorFilter(BlendModeColorFilter(cvh.context.resources - .getColor(R.color.control_thumbnail_tint), BlendMode.LUMINOSITY)) + drawable?.let { clipLayer.drawable = CornerDrawable(it, radius) } + clipLayer.setColorFilter( + BlendModeColorFilter( + cvh.context.resources.getColor(R.color.control_thumbnail_tint), + BlendMode.LUMINOSITY, + ) + ) cvh.applyRenderInfo(enabled, colorOffset) } } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java index 1125d2ccdd97..8e0beda9eff7 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java @@ -72,6 +72,7 @@ import android.hardware.display.DisplayManager; import android.hardware.face.FaceManager; import android.hardware.fingerprint.FingerprintManager; import android.hardware.input.InputManager; +import android.hardware.location.ContextHubManager; import android.location.LocationManager; import android.media.AudioManager; import android.media.IAudioService; @@ -238,6 +239,13 @@ public class FrameworkServicesModule { @Provides @Singleton + @Nullable + static ContextHubManager provideContextHubManager(Context context) { + return context.getSystemService(ContextHubManager.class); + } + + @Provides + @Singleton static DevicePolicyManager provideDevicePolicyManager(Context context) { return context.getSystemService(DevicePolicyManager.class); } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java index 3050cba12f09..3c68e3a09f02 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java @@ -30,6 +30,7 @@ import com.android.systemui.accessibility.SystemActionsModule; import com.android.systemui.accessibility.data.repository.AccessibilityRepositoryModule; import com.android.systemui.battery.BatterySaverModule; import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayOverrideModule; +import com.android.systemui.communal.posturing.dagger.NoopPosturingModule; import com.android.systemui.display.ui.viewmodel.ConnectingDisplayViewModel; import com.android.systemui.dock.DockManager; import com.android.systemui.dock.DockManagerImpl; @@ -74,9 +75,9 @@ import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.dagger.CentralSurfacesModule; import com.android.systemui.statusbar.dagger.StartCentralSurfacesModule; import com.android.systemui.statusbar.notification.dagger.ReferenceNotificationsModule; +import com.android.systemui.statusbar.notification.headsup.HeadsUpModule; import com.android.systemui.statusbar.phone.CentralSurfaces; import com.android.systemui.statusbar.phone.DozeServiceHost; -import com.android.systemui.statusbar.notification.headsup.HeadsUpModule; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.phone.dagger.StatusBarPhoneModule; import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragmentStartableModule; @@ -149,6 +150,7 @@ import javax.inject.Named; RearDisplayModule.class, RecentsModule.class, ReferenceNotificationsModule.class, + NoopPosturingModule.class, ReferenceScreenshotModule.class, RotationLockModule.class, RotationLockNewModule.class, diff --git a/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java index 81a520661cfe..643f3bb917d1 100644 --- a/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java +++ b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java @@ -30,6 +30,7 @@ import android.os.Looper; import android.os.Parcelable; import android.os.Trace; import android.util.ArrayMap; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; @@ -52,6 +53,8 @@ import javax.inject.Provider; public class FragmentHostManager { + private static final String TAG = "FragmentHostManager"; + private final Handler mHandler = new Handler(Looper.getMainLooper()); private final Context mContext; private final HashMap<String, ArrayList<FragmentListener>> mListeners = new HashMap<>(); @@ -84,7 +87,7 @@ public class FragmentHostManager { FragmentHostManager create(View rootView); } - private void createFragmentHost(Parcelable savedState) { + private void createFragmentHost(@Nullable Parcelable savedState) { mFragments = FragmentController.createController(new HostCallbacks()); mFragments.attachHost(null); mLifecycleCallbacks = new FragmentLifecycleCallbacks() { @@ -115,12 +118,21 @@ public class FragmentHostManager { mFragments.dispatchResume(); } + @Nullable private Parcelable destroyFragmentHost() { - mFragments.dispatchPause(); - Parcelable p = mFragments.saveAllState(); - mFragments.dispatchStop(); - mFragments.dispatchDestroy(); - mFragments.getFragmentManager().unregisterFragmentLifecycleCallbacks(mLifecycleCallbacks); + Parcelable p = null; + try { + mFragments.dispatchPause(); + p = mFragments.saveAllState(); + mFragments.dispatchStop(); + mFragments.dispatchDestroy(); + mFragments + .getFragmentManager() + .unregisterFragmentLifecycleCallbacks(mLifecycleCallbacks); + } catch (IllegalStateException e) { + Log.e(TAG, "Failed to destroy fragment host. This is expected to happen only in " + + "tests when displays are added and removed quickly"); + } return p; } diff --git a/packages/SystemUI/src/com/android/systemui/grid/ui/compose/SpannedGrids.kt b/packages/SystemUI/src/com/android/systemui/grid/ui/compose/SpannedGrids.kt index 62ab18bbb738..96ef03c996a9 100644 --- a/packages/SystemUI/src/com/android/systemui/grid/ui/compose/SpannedGrids.kt +++ b/packages/SystemUI/src/com/android/systemui/grid/ui/compose/SpannedGrids.kt @@ -20,6 +20,7 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxScope import androidx.compose.runtime.Composable +import androidx.compose.runtime.key import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.layout.Layout @@ -63,6 +64,7 @@ fun HorizontalSpannedGrid( rowSpacing: Dp, spans: List<Int>, modifier: Modifier = Modifier, + keys: (spanIndex: Int) -> Any = { it }, composables: @Composable BoxScope.(spanIndex: Int) -> Unit, ) { SpannedGrid( @@ -72,6 +74,7 @@ fun HorizontalSpannedGrid( spans = spans, isVertical = false, modifier = modifier, + keys = keys, composables = composables, ) } @@ -103,6 +106,7 @@ fun VerticalSpannedGrid( rowSpacing: Dp, spans: List<Int>, modifier: Modifier = Modifier, + keys: (spanIndex: Int) -> Any = { it }, composables: @Composable BoxScope.(spanIndex: Int) -> Unit, ) { SpannedGrid( @@ -112,6 +116,7 @@ fun VerticalSpannedGrid( spans = spans, isVertical = true, modifier = modifier, + keys = keys, composables = composables, ) } @@ -124,6 +129,7 @@ private fun SpannedGrid( spans: List<Int>, isVertical: Boolean, modifier: Modifier = Modifier, + keys: (spanIndex: Int) -> Any = { it }, composables: @Composable BoxScope.(spanIndex: Int) -> Unit, ) { val crossAxisArrangement = Arrangement.spacedBy(crossAxisSpacing) @@ -167,17 +173,19 @@ private fun SpannedGrid( Layout( { (0 until spans.size).map { spanIndex -> - Box( - Modifier.semantics { - collectionItemInfo = - if (isVertical) { - CollectionItemInfo(spanIndex, 1, 0, 1) - } else { - CollectionItemInfo(0, 1, spanIndex, 1) - } + key(keys(spanIndex)) { + Box( + Modifier.semantics { + collectionItemInfo = + if (isVertical) { + CollectionItemInfo(spanIndex, 1, 0, 1) + } else { + CollectionItemInfo(0, 1, spanIndex, 1) + } + } + ) { + composables(spanIndex) } - ) { - composables(spanIndex) } } }, diff --git a/packages/SystemUI/src/com/android/systemui/haptics/msdl/qs/TileHapticsViewModel.kt b/packages/SystemUI/src/com/android/systemui/haptics/msdl/qs/TileHapticsViewModel.kt index 84c4bdf1621a..49625f8f9360 100644 --- a/packages/SystemUI/src/com/android/systemui/haptics/msdl/qs/TileHapticsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/haptics/msdl/qs/TileHapticsViewModel.kt @@ -17,6 +17,7 @@ package com.android.systemui.haptics.msdl.qs import android.service.quicksettings.Tile +import androidx.compose.runtime.Stable import com.android.systemui.Flags import com.android.systemui.animation.Expandable import com.android.systemui.dagger.SysUISingleton @@ -175,6 +176,7 @@ constructor( } @SysUISingleton +@Stable class TileHapticsViewModelFactoryProvider @Inject constructor(private val tileHapticsViewModelFactory: TileHapticsViewModel.Factory) { 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 beff704d62a5..8cbcba2c3b1c 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 @@ -43,7 +43,6 @@ import androidx.compose.runtime.saveable.mapSaver 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 import androidx.compose.ui.res.stringResource @@ -216,7 +215,7 @@ fun TutorialDescription( Text( text = stringResource(id = bodyTextId), style = MaterialTheme.typography.bodyLarge, - color = Color.White, + color = config.colors.bodyText, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialScreenConfig.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialScreenConfig.kt index 26259912741a..eda23a51a1ae 100644 --- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialScreenConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialScreenConfig.kt @@ -16,9 +16,12 @@ package com.android.systemui.inputdevice.tutorial.ui.composable +import androidx.annotation.ColorInt import androidx.annotation.RawRes import androidx.annotation.StringRes import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.toArgb +import androidx.core.graphics.ColorUtils import com.airbnb.lottie.compose.LottieDynamicProperties data class TutorialScreenConfig( @@ -30,8 +33,23 @@ data class TutorialScreenConfig( data class Colors( val background: Color, val title: Color, + val bodyText: Color, val animationColors: LottieDynamicProperties, - ) + ) { + constructor( + background: Color, + title: Color, + animationColors: LottieDynamicProperties, + ) : this(background, title, textColorOnBackground(background.toArgb()), animationColors) + + companion object { + private fun textColorOnBackground(@ColorInt background: Int): Color { + val whiteContrast = ColorUtils.calculateContrast(Color.White.toArgb(), background) + val blackContrast = ColorUtils.calculateContrast(Color.Black.toArgb(), background) + return if (whiteContrast >= blackContrast) Color.White else Color.Black + } + } + } data class Strings( @StringRes val titleResId: Int, diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ShortcutHelperModule.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ShortcutHelperModule.kt index d8532c176619..ee10c9edaa39 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ShortcutHelperModule.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ShortcutHelperModule.kt @@ -21,12 +21,14 @@ import com.android.systemui.Flags.keyboardShortcutHelperRewrite 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.ShortcutCategoriesRepository +import com.android.systemui.keyboard.shortcut.data.source.AccessibilityShortcutsSource import com.android.systemui.keyboard.shortcut.data.source.AppCategoriesShortcutsSource import com.android.systemui.keyboard.shortcut.data.source.CurrentAppShortcutsSource import com.android.systemui.keyboard.shortcut.data.source.InputShortcutsSource import com.android.systemui.keyboard.shortcut.data.source.KeyboardShortcutGroupsSource import com.android.systemui.keyboard.shortcut.data.source.MultitaskingShortcutsSource import com.android.systemui.keyboard.shortcut.data.source.SystemShortcutsSource +import com.android.systemui.keyboard.shortcut.qualifiers.AccessibilityShortcuts import com.android.systemui.keyboard.shortcut.qualifiers.AppCategoriesShortcuts import com.android.systemui.keyboard.shortcut.qualifiers.CurrentAppShortcuts import com.android.systemui.keyboard.shortcut.qualifiers.CustomShortcutCategories @@ -68,6 +70,12 @@ interface ShortcutHelperModule { ): KeyboardShortcutGroupsSource @Binds + @AccessibilityShortcuts + fun accessibilityShortcutsSource( + impl: AccessibilityShortcutsSource + ): KeyboardShortcutGroupsSource + + @Binds @DefaultShortcutCategories fun defaultShortcutCategoriesRepository( impl: DefaultShortcutCategoriesRepository diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/DefaultShortcutCategoriesRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/DefaultShortcutCategoriesRepository.kt index db35d49e7598..c36b8af1a669 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/DefaultShortcutCategoriesRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/DefaultShortcutCategoriesRepository.kt @@ -23,6 +23,7 @@ import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.keyboard.shortcut.data.model.InternalKeyboardShortcutGroup import com.android.systemui.keyboard.shortcut.data.model.InternalKeyboardShortcutInfo import com.android.systemui.keyboard.shortcut.data.source.KeyboardShortcutGroupsSource +import com.android.systemui.keyboard.shortcut.qualifiers.AccessibilityShortcuts import com.android.systemui.keyboard.shortcut.qualifiers.AppCategoriesShortcuts import com.android.systemui.keyboard.shortcut.qualifiers.CurrentAppShortcuts import com.android.systemui.keyboard.shortcut.qualifiers.InputShortcuts @@ -30,6 +31,7 @@ import com.android.systemui.keyboard.shortcut.qualifiers.MultitaskingShortcuts import com.android.systemui.keyboard.shortcut.qualifiers.SystemShortcuts 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.ShortcutCategoryType.Accessibility import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.AppCategories import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.CurrentApp import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.InputMethodEditor @@ -47,11 +49,12 @@ class DefaultShortcutCategoriesRepository @Inject constructor( @Background private val backgroundScope: CoroutineScope, - @SystemShortcuts private val systemShortcutsSource: KeyboardShortcutGroupsSource, - @MultitaskingShortcuts private val multitaskingShortcutsSource: KeyboardShortcutGroupsSource, - @AppCategoriesShortcuts private val appCategoriesShortcutsSource: KeyboardShortcutGroupsSource, - @InputShortcuts private val inputShortcutsSource: KeyboardShortcutGroupsSource, - @CurrentAppShortcuts private val currentAppShortcutsSource: KeyboardShortcutGroupsSource, + @SystemShortcuts systemShortcutsSource: KeyboardShortcutGroupsSource, + @MultitaskingShortcuts multitaskingShortcutsSource: KeyboardShortcutGroupsSource, + @AppCategoriesShortcuts appCategoriesShortcutsSource: KeyboardShortcutGroupsSource, + @InputShortcuts inputShortcutsSource: KeyboardShortcutGroupsSource, + @CurrentAppShortcuts currentAppShortcutsSource: KeyboardShortcutGroupsSource, + @AccessibilityShortcuts accessibilityShortcutsSource: KeyboardShortcutGroupsSource, inputDeviceRepository: ShortcutHelperInputDeviceRepository, shortcutCategoriesUtils: ShortcutCategoriesUtils, ) : ShortcutCategoriesRepository { @@ -72,6 +75,10 @@ constructor( typeProvider = { InputMethodEditor }, ), InternalGroupsSource( + source = accessibilityShortcutsSource, + typeProvider = { Accessibility }, + ), + InternalGroupsSource( source = currentAppShortcutsSource, typeProvider = { groups -> getCurrentAppShortcutCategoryType(groups) }, ), 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 6a42cdc876ca..8e4c9349c604 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 @@ -39,10 +39,14 @@ import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFO import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT +import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_BOUNCE_KEYS import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAXIMIZE_FREEFORM_WINDOW +import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MOUSE_KEYS import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL +import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_SLOW_KEYS +import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_STICKY_KEYS import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS -import com.android.hardware.input.Flags.enableVoiceAccessKeyGestures +import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.Accessibility import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.AppCategories import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.MultiTasking import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.System @@ -65,7 +69,6 @@ class InputGestureMaps @Inject constructor(private val context: Context) { KEY_GESTURE_TYPE_LAUNCH_ASSISTANT to System, KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT to System, KEY_GESTURE_TYPE_ALL_APPS to System, - KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS to System, // Multitasking Category KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER to MultiTasking, @@ -82,6 +85,13 @@ class InputGestureMaps @Inject constructor(private val context: Context) { // App Category KEY_GESTURE_TYPE_LAUNCH_APPLICATION to AppCategories, + + // Accessibility Category + KEY_GESTURE_TYPE_TOGGLE_BOUNCE_KEYS to Accessibility, + KEY_GESTURE_TYPE_TOGGLE_MOUSE_KEYS to Accessibility, + KEY_GESTURE_TYPE_TOGGLE_STICKY_KEYS to Accessibility, + KEY_GESTURE_TYPE_TOGGLE_SLOW_KEYS to Accessibility, + KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS to Accessibility, ) val gestureToInternalKeyboardShortcutGroupLabelResIdMap = @@ -103,7 +113,6 @@ class InputGestureMaps @Inject constructor(private val context: Context) { KEY_GESTURE_TYPE_LAUNCH_ASSISTANT to R.string.shortcut_helper_category_system_apps, KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT to R.string.shortcut_helper_category_system_apps, - KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS to R.string.shortcut_helper_category_system_apps, // Multitasking Category KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT to @@ -128,6 +137,13 @@ class InputGestureMaps @Inject constructor(private val context: Context) { // App Category KEY_GESTURE_TYPE_LAUNCH_APPLICATION to R.string.keyboard_shortcut_group_applications, + + // Accessibility Category + KEY_GESTURE_TYPE_TOGGLE_BOUNCE_KEYS to R.string.shortcutHelper_category_accessibility, + KEY_GESTURE_TYPE_TOGGLE_MOUSE_KEYS to R.string.shortcutHelper_category_accessibility, + KEY_GESTURE_TYPE_TOGGLE_STICKY_KEYS to R.string.shortcutHelper_category_accessibility, + KEY_GESTURE_TYPE_TOGGLE_SLOW_KEYS to R.string.shortcutHelper_category_accessibility, + KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS to R.string.shortcutHelper_category_accessibility, ) /** @@ -152,7 +168,6 @@ class InputGestureMaps @Inject constructor(private val context: Context) { KEY_GESTURE_TYPE_LAUNCH_ASSISTANT to R.string.group_system_access_google_assistant, KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT to R.string.group_system_access_google_assistant, - KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS to R.string.group_system_toggle_voice_access, // Multitasking Category KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER to R.string.group_system_cycle_forward, @@ -169,6 +184,14 @@ class InputGestureMaps @Inject constructor(private val context: Context) { R.string.system_desktop_mode_toggle_maximize_window, KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY to R.string.system_multitasking_move_to_next_display, + + // Accessibility Category + KEY_GESTURE_TYPE_TOGGLE_BOUNCE_KEYS to R.string.group_accessibility_toggle_bounce_keys, + KEY_GESTURE_TYPE_TOGGLE_MOUSE_KEYS to R.string.group_accessibility_toggle_mouse_keys, + KEY_GESTURE_TYPE_TOGGLE_STICKY_KEYS to R.string.group_accessibility_toggle_sticky_keys, + KEY_GESTURE_TYPE_TOGGLE_SLOW_KEYS to R.string.group_accessibility_toggle_slow_keys, + KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS to + R.string.group_accessibility_toggle_voice_access, ) val shortcutLabelToKeyGestureTypeMap: Map<String, Int> diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/AccessibilityShortcutsSource.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/AccessibilityShortcutsSource.kt new file mode 100644 index 000000000000..0c98f81e7cef --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/AccessibilityShortcutsSource.kt @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.Resources +import android.hardware.input.InputSettings +import android.view.KeyEvent.KEYCODE_3 +import android.view.KeyEvent.KEYCODE_4 +import android.view.KeyEvent.KEYCODE_5 +import android.view.KeyEvent.KEYCODE_6 +import android.view.KeyEvent.KEYCODE_V +import android.view.KeyEvent.META_ALT_ON +import android.view.KeyEvent.META_META_ON +import android.view.KeyboardShortcutGroup +import android.view.KeyboardShortcutInfo +import com.android.hardware.input.Flags.enableVoiceAccessKeyGestures +import com.android.hardware.input.Flags.keyboardA11yShortcutControl +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.keyboard.shortcut.data.model.shortcutInfo +import com.android.systemui.res.R +import javax.inject.Inject + +class AccessibilityShortcutsSource @Inject constructor(@Main private val resources: Resources) : + KeyboardShortcutGroupsSource { + override suspend fun shortcutGroups(deviceId: Int): List<KeyboardShortcutGroup> = + listOf( + KeyboardShortcutGroup( + /* label= */ resources.getString(R.string.shortcutHelper_category_accessibility), + accessibilityShortcuts(), + ) + ) + + private fun accessibilityShortcuts(): List<KeyboardShortcutInfo> { + val shortcuts = mutableListOf<KeyboardShortcutInfo>() + + if (keyboardA11yShortcutControl()) { + if (InputSettings.isAccessibilityBounceKeysFeatureEnabled()) { + shortcuts.add( + // Toggle bounce keys: + // - Meta + Alt + 3 + shortcutInfo( + resources.getString(R.string.group_accessibility_toggle_bounce_keys) + ) { + command(META_META_ON or META_ALT_ON, KEYCODE_3) + } + ) + } + if (InputSettings.isAccessibilityMouseKeysFeatureFlagEnabled()) { + shortcuts.add( + // Toggle mouse keys: + // - Meta + Alt + 4 + shortcutInfo( + resources.getString(R.string.group_accessibility_toggle_mouse_keys) + ) { + command(META_META_ON or META_ALT_ON, KEYCODE_4) + } + ) + } + if (InputSettings.isAccessibilityStickyKeysFeatureEnabled()) { + shortcuts.add( + // Toggle sticky keys: + // - Meta + Alt + 5 + shortcutInfo( + resources.getString(R.string.group_accessibility_toggle_sticky_keys) + ) { + command(META_META_ON or META_ALT_ON, KEYCODE_5) + } + ) + } + if (InputSettings.isAccessibilitySlowKeysFeatureFlagEnabled()) { + shortcuts.add( + // Toggle slow keys: + // - Meta + Alt + 6 + shortcutInfo( + resources.getString(R.string.group_accessibility_toggle_slow_keys) + ) { + command(META_META_ON or META_ALT_ON, KEYCODE_6) + } + ) + } + } + + if (enableVoiceAccessKeyGestures()) { + shortcuts.add( + // Toggle voice access: + // - Meta + Alt + V + shortcutInfo( + resources.getString(R.string.group_accessibility_toggle_voice_access) + ) { + command(META_META_ON or META_ALT_ON, KEYCODE_V) + } + ) + } + + return shortcuts + } +} 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 d785b5b5a7e7..464201f6ec12 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 @@ -29,12 +29,12 @@ import android.view.KeyEvent.KEYCODE_RIGHT_BRACKET import android.view.KeyEvent.META_CTRL_ON import android.view.KeyEvent.META_META_ON import android.view.KeyboardShortcutGroup +import android.window.DesktopModeFlags 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 @@ -85,7 +85,8 @@ constructor(@Main private val resources: Resources, @Application private val con ) } if ( - DesktopModeStatus.canEnterDesktopMode(context) && enableTaskResizingKeyboardShortcuts() + DesktopModeStatus.canEnterDesktopMode(context) && + DesktopModeFlags.ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS.isTrue ) { // Snap a freeform window to the left // - Meta + Left bracket 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 c3c9df97a682..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 @@ -32,14 +32,12 @@ 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.KEYCODE_V 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.hardware.input.Flags.enableVoiceAccessKeyGestures import com.android.systemui.Flags.shortcutHelperKeyGlyph import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.keyboard.shortcut.data.model.shortcutInfo @@ -120,8 +118,8 @@ constructor(@Main private val resources: Resources, private val inputManager: In return shortcuts } - private fun systemControlsShortcuts(): List<KeyboardShortcutInfo> { - val shortcuts = mutableListOf( + private fun systemControlsShortcuts() = + listOf( // Access list of all apps and search (i.e. Search/Launcher): // - Meta shortcutInfo(resources.getString(R.string.group_system_access_all_apps_search)) { @@ -178,19 +176,6 @@ constructor(@Main private val resources: Resources, private val inputManager: In }, ) - if (enableVoiceAccessKeyGestures()) { - shortcuts.add( - // Toggle voice access: - // - Meta + Alt + V - shortcutInfo(resources.getString(R.string.group_system_toggle_voice_access)) { - command(META_META_ON or META_ALT_ON, KEYCODE_V) - } - ) - } - - return shortcuts - } - private fun systemAppsShortcuts() = listOf( // Pull up Notes app for quick memo: diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/qualifiers/AccessibilityShortcuts.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/qualifiers/AccessibilityShortcuts.kt new file mode 100644 index 000000000000..2f939145734b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/qualifiers/AccessibilityShortcuts.kt @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyboard.shortcut.qualifiers + +import javax.inject.Qualifier + +@Qualifier annotation class AccessibilityShortcuts diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCategory.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCategory.kt index 464805334c2a..110ea34e9b0e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCategory.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCategory.kt @@ -40,6 +40,11 @@ sealed interface ShortcutCategoryType { override val includeInCustomization: Boolean = true } + data object Accessibility : ShortcutCategoryType { + override val isTrusted: Boolean = false + override val includeInCustomization: Boolean = true + } + data class CurrentApp(val packageName: String) : ShortcutCategoryType { override val isTrusted: Boolean = false override val includeInCustomization: Boolean = false 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 a16b4a6892b4..bd3d46d09f5e 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 @@ -17,6 +17,7 @@ package com.android.systemui.keyboard.shortcut.ui import android.app.Dialog +import android.content.res.Resources import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_ALLOW_ACTION_KEY_EVENTS import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width @@ -26,6 +27,7 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo import com.android.systemui.keyboard.shortcut.ui.composable.ShortcutCustomizationDialog import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState @@ -34,6 +36,8 @@ import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiSt 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.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.AssistedFactory @@ -46,6 +50,7 @@ class ShortcutCustomizationDialogStarter constructor( viewModelFactory: ShortcutCustomizationViewModel.Factory, private val dialogFactory: SystemUIDialogFactory, + @Main private val resources: Resources, ) : ExclusiveActivatable() { private var dialog: Dialog? = null @@ -97,14 +102,39 @@ constructor( coroutineScope.launch { viewModel.resetAllCustomShortcuts() } }, ) - dialog.setOnDismissListener { viewModel.onDialogDismissed() } + setDialogProperties(dialog, uiState) + } + } + + private fun setDialogProperties(dialog: SystemUIDialog, uiState: ShortcutCustomizationUiState) { + dialog.setOnDismissListener { viewModel.onDialogDismissed() } + dialog.setTitle("${getDialogTitle(uiState)}. ${getDialogDescription(uiState)}") + // By default, apps cannot intercept action key. The system always handles it. This + // flag is needed to enable customisation dialog window to intercept action key + dialog.window?.addPrivateFlags(PRIVATE_FLAG_ALLOW_ACTION_KEY_EVENTS) + } - // By default, apps cannot intercept action key. The system always handles it. This - // flag is needed to enable customisation dialog window to intercept action key - dialog.window?.addPrivateFlags(PRIVATE_FLAG_ALLOW_ACTION_KEY_EVENTS) + private fun getDialogTitle(uiState: ShortcutCustomizationUiState): String { + return when (uiState) { + is AddShortcutDialog -> uiState.shortcutLabel + is DeleteShortcutDialog -> + resources.getString(R.string.shortcut_customize_mode_remove_shortcut_dialog_title) + else -> + resources.getString(R.string.shortcut_customize_mode_reset_shortcut_dialog_title) } } + private fun getDialogDescription(uiState: ShortcutCustomizationUiState): String { + return resources.getString( + when (uiState) { + is AddShortcutDialog -> R.string.shortcut_customize_mode_add_shortcut_description + is DeleteShortcutDialog -> + R.string.shortcut_customize_mode_remove_shortcut_description + else -> R.string.shortcut_customize_mode_reset_shortcut_description + } + ) + } + @AssistedFactory interface Factory { fun create(): ShortcutCustomizationDialogStarter 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 d8e2dabde8a9..9d43c48ee274 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 @@ -59,7 +59,12 @@ import androidx.compose.ui.input.key.onPreviewKeyEvent import androidx.compose.ui.input.key.type import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.LiveRegionMode +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.liveRegion +import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.android.compose.ui.graphics.painter.rememberDrawablePainter @@ -107,16 +112,13 @@ private fun AddShortcutDialog( onCancel: () -> Unit, onConfirmSetShortcut: () -> Unit, ) { - Column(modifier = modifier) { + Column(modifier = modifier, horizontalAlignment = Alignment.CenterHorizontally) { 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), + modifier = Modifier.padding(top = 24.dp).sizeIn(minWidth = 131.dp, minHeight = 48.dp), defaultModifierKey = uiState.defaultCustomShortcutModifierKey, ) SelectedKeyCombinationContainer( @@ -216,14 +218,14 @@ private fun DialogButtons( modifier = Modifier.heightIn(40.dp), contentColor = MaterialTheme.colorScheme.primary, text = stringResource(R.string.shortcut_helper_customize_dialog_cancel_button_label), - border = BorderStroke(width = 1.dp, color = MaterialTheme.colorScheme.outlineVariant) + border = BorderStroke(width = 1.dp, color = MaterialTheme.colorScheme.outlineVariant), ) Spacer(modifier = Modifier.width(8.dp)) ShortcutHelperButton( - modifier = Modifier - .heightIn(40.dp) - .focusRequester(focusRequester) - .focusProperties { canFocus = true }, // enable focus on touch/click mode + modifier = + Modifier.heightIn(40.dp).focusRequester(focusRequester).focusProperties { + canFocus = true + }, // enable focus on touch/click mode onClick = onConfirm, color = MaterialTheme.colorScheme.primary, contentColor = MaterialTheme.colorScheme.onPrimary, @@ -236,7 +238,10 @@ private fun DialogButtons( @Composable private fun ErrorMessageContainer(errorMessage: String) { if (errorMessage.isNotEmpty()) { - Box(modifier = Modifier.padding(horizontal = 16.dp).width(332.dp).height(40.dp)) { + Box( + modifier = + Modifier.padding(horizontal = 16.dp).sizeIn(minWidth = 332.dp, minHeight = 40.dp) + ) { Text( text = errorMessage, style = MaterialTheme.typography.bodyMedium, @@ -244,7 +249,10 @@ private fun ErrorMessageContainer(errorMessage: String) { lineHeight = 20.sp, fontWeight = FontWeight.W500, color = MaterialTheme.colorScheme.error, - modifier = Modifier.padding(start = 24.dp).width(252.dp), + modifier = Modifier.padding(start = 24.dp).width(252.dp).semantics { + contentDescription = errorMessage + liveRegion = LiveRegionMode.Polite + }, ) } } @@ -397,6 +405,7 @@ private fun Description(text: String) { .width(316.dp) .wrapContentSize(Alignment.Center), color = MaterialTheme.colorScheme.onSurfaceVariant, + textAlign = TextAlign.Center ) } @@ -405,7 +414,11 @@ private fun PromptShortcutModifier( modifier: Modifier, defaultModifierKey: ShortcutKey.Icon.ResIdIcon, ) { - Row(modifier = modifier, horizontalArrangement = Arrangement.spacedBy(2.dp)) { + Row( + modifier = modifier, + horizontalArrangement = Arrangement.spacedBy(2.dp), + verticalAlignment = Alignment.CenterVertically, + ) { ActionKeyContainer(defaultModifierKey) PlusIconContainer() } @@ -422,6 +435,7 @@ private fun ActionKeyContainer(defaultModifierKey: ShortcutKey.Icon.ResIdIcon) { ) .padding(all = 12.dp), horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically, ) { ActionKeyIcon(defaultModifierKey) ActionKeyText() 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 d61165c16625..f11205fd7246 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 @@ -21,6 +21,8 @@ import android.content.Context import android.content.pm.PackageManager.NameNotFoundException import android.util.Log import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Accessibility +import androidx.compose.material.icons.filled.AccessibilityNew import androidx.compose.material.icons.filled.Android import androidx.compose.material.icons.filled.Apps import androidx.compose.material.icons.filled.Keyboard @@ -41,6 +43,7 @@ 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,7 +54,6 @@ 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 @@ -89,8 +91,9 @@ constructor( searchQuery = query, shortcutCategories = shortcutCategoriesUi, defaultSelectedCategory = getDefaultSelectedCategory(filteredCategories), - isShortcutCustomizerFlagEnabled = keyboardShortcutHelperShortcutCustomizer(), - shouldShowResetButton = shouldShowResetButton(shortcutCategoriesUi) + isShortcutCustomizerFlagEnabled = + keyboardShortcutHelperShortcutCustomizer(), + shouldShowResetButton = shouldShowResetButton(shortcutCategoriesUi), ) } } @@ -137,6 +140,9 @@ constructor( IconSource(imageVector = Icons.Default.Android) } } + + ShortcutCategoryType.Accessibility -> + IconSource(imageVector = Icons.Default.AccessibilityNew) } } @@ -151,6 +157,8 @@ constructor( ShortcutCategoryType.AppCategories -> context.getString(R.string.shortcut_helper_category_app_shortcuts) is CurrentApp -> getApplicationLabelForCurrentApp(type) + ShortcutCategoryType.Accessibility -> + context.getString(R.string.shortcutHelper_category_accessibility) } private fun getApplicationLabelForCurrentApp(type: CurrentApp): String { @@ -245,7 +253,7 @@ constructor( searchQuery.value = query } - private fun resetSearchQuery(){ + private fun resetSearchQuery() { searchQuery.value = "" } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java index 591383999182..1d36076347bf 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java @@ -72,6 +72,7 @@ import com.android.internal.policy.IKeyguardDrawnCallback; import com.android.internal.policy.IKeyguardExitCallback; import com.android.internal.policy.IKeyguardService; import com.android.internal.policy.IKeyguardStateCallback; +import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.mediator.ScreenOnCoordinator; import com.android.systemui.SystemUIApplication; import com.android.systemui.dagger.qualifiers.Application; @@ -331,6 +332,7 @@ public class KeyguardService extends Service { } }; private final KeyguardServiceLockNowInteractor mKeyguardServiceLockNowInteractor; + private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; @Inject public KeyguardService( @@ -356,7 +358,8 @@ public class KeyguardService extends Service { KeyguardDismissInteractor keyguardDismissInteractor, Lazy<DeviceEntryInteractor> deviceEntryInteractorLazy, KeyguardStateCallbackInteractor keyguardStateCallbackInteractor, - KeyguardServiceLockNowInteractor keyguardServiceLockNowInteractor) { + KeyguardServiceLockNowInteractor keyguardServiceLockNowInteractor, + KeyguardUpdateMonitor keyguardUpdateMonitor) { super(); mKeyguardViewMediator = keyguardViewMediator; mKeyguardLifecyclesDispatcher = keyguardLifecyclesDispatcher; @@ -389,6 +392,7 @@ public class KeyguardService extends Service { mKeyguardWakeDirectlyToGoneInteractor = keyguardWakeDirectlyToGoneInteractor; mKeyguardDismissInteractor = keyguardDismissInteractor; mKeyguardServiceLockNowInteractor = keyguardServiceLockNowInteractor; + mKeyguardUpdateMonitor = keyguardUpdateMonitor; } @Override @@ -585,6 +589,7 @@ public class KeyguardService extends Service { mPowerInteractor.onScreenPowerStateUpdated(ScreenPowerState.SCREEN_TURNING_ON); mKeyguardLifecyclesDispatcher.dispatch(KeyguardLifecyclesDispatcher.SCREEN_TURNING_ON, callback); + mKeyguardUpdateMonitor.triggerTimeUpdate(); final String onDrawWaitingTraceTag = "Waiting for KeyguardDrawnCallback#onDrawn"; final int traceCookie = System.identityHashCode(callback); @@ -620,6 +625,7 @@ public class KeyguardService extends Service { checkPermission(); mPowerInteractor.onScreenPowerStateUpdated(ScreenPowerState.SCREEN_ON); mKeyguardLifecyclesDispatcher.dispatch(KeyguardLifecyclesDispatcher.SCREEN_TURNED_ON); + mKeyguardUpdateMonitor.triggerTimeUpdate(); mScreenOnCoordinator.onScreenTurnedOn(); Trace.endSection(); } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt index 5e0768a2fd24..f37e7685f21c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt @@ -27,6 +27,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor +import com.android.systemui.keyguard.domain.interactor.WallpaperFocalAreaInteractor import com.android.systemui.keyguard.ui.binder.KeyguardBlueprintViewBinder import com.android.systemui.keyguard.ui.binder.KeyguardRootViewBinder import com.android.systemui.keyguard.ui.binder.LightRevealScrimViewBinder @@ -79,6 +80,7 @@ constructor( private val keyguardClockViewModel: KeyguardClockViewModel, private val smartspaceViewModel: KeyguardSmartspaceViewModel, private val clockInteractor: KeyguardClockInteractor, + private val wallpaperFocalAreaInteractor: WallpaperFocalAreaInteractor, private val keyguardViewMediator: KeyguardViewMediator, private val deviceEntryUnlockTrackerViewBinder: Optional<DeviceEntryUnlockTrackerViewBinder>, private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager, @@ -139,6 +141,7 @@ constructor( screenOffAnimationController, shadeInteractor, clockInteractor, + wallpaperFocalAreaInteractor, keyguardClockViewModel, interactionJankMonitor, deviceEntryHapticsInteractor, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt index a45204d41718..80675d373b8e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt @@ -28,7 +28,6 @@ object BuiltInKeyguardQuickAffordanceKeys { const val CREATE_NOTE = "create_note" const val DO_NOT_DISTURB = "do_not_disturb" const val FLASHLIGHT = "flashlight" - const val GLANCEABLE_HUB = "glanceable_hub" const val HOME_CONTROLS = "home" const val MUTE = "mute" const val QR_CODE_SCANNER = "qr_code_scanner" 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 deleted file mode 100644 index 96b07cc84705..000000000000 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfig.kt +++ /dev/null @@ -1,119 +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.keyguard.data.quickaffordance - -import android.content.Context -import android.content.Intent -import android.provider.Settings -import android.util.Log -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 -import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.res.R -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 javax.inject.Inject -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map - -/** Lockscreen affordance that opens the glanceable hub. */ -@SysUISingleton -class GlanceableHubQuickAffordanceConfig -@Inject -constructor( - @Application private val context: Context, - private val communalSceneRepository: CommunalSceneRepository, - private val communalInteractor: CommunalInteractor, - private val communalSettingsInteractor: CommunalSettingsInteractor, - private val sceneInteractor: SceneInteractor, -) : KeyguardQuickAffordanceConfig { - - private val pickerNameResourceId = R.string.glanceable_hub_lockscreen_affordance_label - - override val key: String = BuiltInKeyguardQuickAffordanceKeys.GLANCEABLE_HUB - - override fun pickerName(): String = context.getString(pickerNameResourceId) - - override val pickerIconResourceId: Int - get() = R.drawable.ic_widgets - - override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> - get() = - communalInteractor.isCommunalAvailable.map { available -> - if (!communalSettingsInteractor.isV2FlagEnabled()) { - Log.i(TAG, "Button hidden on lockscreen: flag not enabled.") - KeyguardQuickAffordanceConfig.LockScreenState.Hidden - } else if (!available) { - Log.i(TAG, "Button hidden on lockscreen: hub not available.") - KeyguardQuickAffordanceConfig.LockScreenState.Hidden - } else { - KeyguardQuickAffordanceConfig.LockScreenState.Visible( - icon = - Icon.Resource( - pickerIconResourceId, - ContentDescription.Resource(pickerNameResourceId), - ) - ) - } - } - - override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState { - 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( - 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() - } - } - - override fun onTriggered( - expandable: Expandable? - ): KeyguardQuickAffordanceConfig.OnTriggeredResult { - if (SceneContainerFlag.isEnabled) { - sceneInteractor.changeScene(Scenes.Communal, "lockscreen to communal from shortcut") - } else { - communalSceneRepository.changeScene( - CommunalScenes.Communal, - transitionKey = CommunalTransitionKeys.SimpleFade, - ) - } - return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled(true) - } - - companion object { - private const val TAG = "GlanceableHubQuickAffordanceConfig" - } -} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt index 8c6fdb989daf..787a9837b860 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt @@ -36,7 +36,6 @@ interface KeyguardDataQuickAffordanceModule { camera: CameraQuickAffordanceConfig, doNotDisturb: DoNotDisturbQuickAffordanceConfig, flashlight: FlashlightQuickAffordanceConfig, - glanceableHub: GlanceableHubQuickAffordanceConfig, home: HomeControlsKeyguardQuickAffordanceConfig, mute: MuteQuickAffordanceConfig, quickAccessWallet: QuickAccessWalletKeyguardQuickAffordanceConfig, @@ -47,7 +46,6 @@ interface KeyguardDataQuickAffordanceModule { camera, doNotDisturb, flashlight, - glanceableHub, home, mute, quickAccessWallet, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt index a39982dd31e7..11477fb6cad1 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.data.repository import android.graphics.Point +import android.graphics.RectF import com.android.app.tracing.coroutines.launchTraced as launch import com.android.internal.widget.LockPatternUtils import com.android.keyguard.KeyguardUpdateMonitor @@ -258,6 +259,8 @@ interface KeyguardRepository { val notificationStackAbsoluteBottom: StateFlow<Float> + val wallpaperFocalAreaBounds: StateFlow<RectF> + /** * Returns `true` if the keyguard is showing; `false` otherwise. * @@ -329,6 +332,8 @@ interface KeyguardRepository { * this value */ fun setNotificationStackAbsoluteBottom(bottom: Float) + + fun setWallpaperFocalAreaBounds(bounds: RectF) } /** Encapsulates application state for the keyguard. */ @@ -380,7 +385,6 @@ constructor( override val onCameraLaunchDetected = MutableStateFlow(CameraLaunchSourceModel()) override val panelAlpha: MutableStateFlow<Float> = MutableStateFlow(1f) - override val topClippingBounds = MutableStateFlow<Int?>(null) override val isKeyguardShowing: MutableStateFlow<Boolean> = @@ -622,6 +626,10 @@ constructor( private val _notificationStackAbsoluteBottom = MutableStateFlow(0F) override val notificationStackAbsoluteBottom = _notificationStackAbsoluteBottom.asStateFlow() + private val _wallpaperFocalAreaBounds = MutableStateFlow(RectF(0F, 0F, 0F, 0F)) + override val wallpaperFocalAreaBounds: StateFlow<RectF> = + _wallpaperFocalAreaBounds.asStateFlow() + init { val callback = object : KeyguardStateController.Callback { @@ -700,6 +708,10 @@ constructor( _notificationStackAbsoluteBottom.value = bottom } + override fun setWallpaperFocalAreaBounds(bounds: RectF) { + _wallpaperFocalAreaBounds.value = bounds + } + private fun dozeMachineStateToModel(state: DozeMachine.State): DozeStateModel { return when (state) { DozeMachine.State.UNINITIALIZED -> DozeStateModel.UNINITIALIZED diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt index b42da5265d86..717437923e57 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt @@ -36,6 +36,7 @@ import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.debounce @SysUISingleton @@ -80,6 +81,7 @@ constructor( /** * Listen for the signal that we're waking up and figure what state we need to transition to. */ + @OptIn(FlowPreview::class) private fun listenForAodToAwake() { // Use PowerInteractor's wakefulness, which is the earliest wake signal available. We // have all of the information we need at this time to make a decision about where to diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt index 021cce6d1e23..4291181d8336 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt @@ -41,6 +41,7 @@ import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.debounce @SysUISingleton @@ -114,6 +115,7 @@ constructor( } } + @OptIn(FlowPreview::class) @SuppressLint("MissingPermission") private fun listenForDozingToAny() { if (KeyguardWmStateRefactor.isEnabled) { 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 a9992112f893..82b8ca2a890b 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 @@ -125,6 +125,7 @@ constructor( * the power button is pressed quickly, we may need to go directly from DREAMING to * GLANCEABLE_HUB as the transition to DOZING has not occurred yet. */ + @OptIn(FlowPreview::class) @SuppressLint("MissingPermission") private fun listenForDreamingToGlanceableHubFromPowerButton() { if (!communalSettingsInteractor.isCommunalFlagEnabled()) return diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt index 8f7f2a0a8cbb..1f3c08ca9f7a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt @@ -56,6 +56,7 @@ import javax.inject.Inject import javax.inject.Provider import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow @@ -203,6 +204,7 @@ constructor( * examining the value of this flow, to let other consumers have enough time to also see that * same new value. */ + @OptIn(FlowPreview::class) val isAbleToDream: Flow<Boolean> = dozeTransitionModel .flatMapLatest { dozeTransitionModel -> diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt index cd62d5f3b6e1..3dc123a81bde 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt @@ -30,6 +30,7 @@ import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel import javax.inject.Inject import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.debounce private val TAG = KeyguardTransitionAuditLogger::class.simpleName!! @@ -52,6 +53,7 @@ constructor( private val deviceEntryInteractor: DeviceEntryInteractor, ) { + @OptIn(FlowPreview::class) fun start() { scope.launch { powerInteractor.detailedWakefulness.collect { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WallpaperFocalAreaInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WallpaperFocalAreaInteractor.kt new file mode 100644 index 000000000000..934afe248a36 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WallpaperFocalAreaInteractor.kt @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.domain.interactor + +import android.content.Context +import android.content.res.Resources +import android.graphics.RectF +import android.util.TypedValue +import com.android.app.animation.MathUtils.max +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.keyguard.data.repository.KeyguardClockRepository +import com.android.systemui.keyguard.data.repository.KeyguardRepository +import com.android.systemui.res.R +import com.android.systemui.shade.data.repository.ShadeRepository +import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor +import com.android.systemui.wallpapers.data.repository.WallpaperRepository +import javax.inject.Inject +import kotlin.math.min +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.stateIn + +@SysUISingleton +class WallpaperFocalAreaInteractor +@Inject +constructor( + @Application private val applicationScope: CoroutineScope, + context: Context, + private val keyguardRepository: KeyguardRepository, + shadeRepository: ShadeRepository, + activeNotificationsInteractor: ActiveNotificationsInteractor, + keyguardClockRepository: KeyguardClockRepository, + wallpaperRepository: WallpaperRepository, +) { + // When there's notifications in splitshade, magic portrait shape effects should be left + // aligned in foldable + private val notificationInShadeWideLayout: Flow<Boolean> = + combine( + shadeRepository.isShadeLayoutWide, + activeNotificationsInteractor.areAnyNotificationsPresent, + ) { isShadeLayoutWide, areAnyNotificationsPresent: Boolean -> + when { + !isShadeLayoutWide -> false + !areAnyNotificationsPresent -> false + else -> true + } + } + + val shouldSendFocalArea = wallpaperRepository.shouldSendFocalArea + val wallpaperFocalAreaBounds: StateFlow<RectF?> = + combine( + shadeRepository.isShadeLayoutWide, + notificationInShadeWideLayout, + keyguardRepository.notificationStackAbsoluteBottom, + keyguardRepository.shortcutAbsoluteTop, + keyguardClockRepository.notificationDefaultTop, + ) { + isShadeLayoutWide, + notificationInShadeWideLayout, + notificationStackAbsoluteBottom, + shortcutAbsoluteTop, + notificationDefaultTop -> + // Wallpaper will be zoomed in with config_wallpaperMaxScale in lockscreen + // so we need to give a bounds taking this scale in consideration + val wallpaperZoomedInScale = getSystemWallpaperMaximumScale(context) + val screenBounds = + RectF( + 0F, + 0F, + context.resources.displayMetrics.widthPixels.toFloat(), + context.resources.displayMetrics.heightPixels.toFloat(), + ) + val scaledBounds = + RectF( + screenBounds.centerX() - screenBounds.width() / 2F / wallpaperZoomedInScale, + screenBounds.centerY() - + screenBounds.height() / 2F / wallpaperZoomedInScale, + screenBounds.centerX() + screenBounds.width() / 2F / wallpaperZoomedInScale, + screenBounds.centerY() + screenBounds.height() / 2F / wallpaperZoomedInScale, + ) + val maxFocalAreaWidth = + TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, + FOCAL_AREA_MAX_WIDTH_DP.toFloat(), + context.resources.displayMetrics, + ) + val (left, right) = + // tablet landscape + if (context.resources.getBoolean(R.bool.center_align_magic_portrait_shape)) { + Pair( + scaledBounds.centerX() - maxFocalAreaWidth / 2F, + scaledBounds.centerX() + maxFocalAreaWidth / 2F, + ) + // unfold foldable landscape + } else if (isShadeLayoutWide) { + if (notificationInShadeWideLayout) { + Pair(scaledBounds.left, scaledBounds.centerX()) + } else { + Pair(scaledBounds.centerX(), scaledBounds.right) + } + // handheld / portrait + } else { + val focalAreaWidth = min(scaledBounds.width(), maxFocalAreaWidth) + Pair( + scaledBounds.centerX() - focalAreaWidth / 2F, + scaledBounds.centerX() + focalAreaWidth / 2F, + ) + } + val scaledBottomMargin = + (context.resources.displayMetrics.heightPixels - shortcutAbsoluteTop) / + wallpaperZoomedInScale + val top = + // tablet landscape + if (context.resources.getBoolean(R.bool.center_align_magic_portrait_shape)) { + // no strict constraints for top, use bottom margin to make it symmetric + // vertically + scaledBounds.top + scaledBottomMargin + } + // unfold foldable landscape + else if (isShadeLayoutWide) { + // For all landscape, we should use bottom of smartspace to constrain + scaledBounds.top + notificationDefaultTop / wallpaperZoomedInScale + // handheld / portrait + } else { + scaledBounds.top + + max(notificationDefaultTop, notificationStackAbsoluteBottom) / + wallpaperZoomedInScale + } + val bottom = scaledBounds.bottom - scaledBottomMargin + RectF(left, top, right, bottom) + } + .stateIn( + applicationScope, + started = SharingStarted.WhileSubscribed(), + initialValue = null, + ) + + fun setWallpaperFocalAreaBounds(bounds: RectF) { + keyguardRepository.setWallpaperFocalAreaBounds(bounds) + } + + companion object { + fun getSystemWallpaperMaximumScale(context: Context): Float { + return context.resources.getFloat( + Resources.getSystem() + .getIdentifier( + /* name= */ "config_wallpaperMaxScale", + /* defType= */ "dimen", + /* defPackage= */ "android", + ) + ) + } + + // A max width for magic portrait shape effects bounds, to avoid it going too large + // in large screen portrait mode + const val FOCAL_AREA_MAX_WIDTH_DP = 500 + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt index 92b49ed6156c..21c9b0b82b2d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt @@ -23,6 +23,7 @@ import android.widget.TextView import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle import com.android.app.tracing.coroutines.launchTraced as launch +import com.android.systemui.Flags import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.res.R @@ -73,7 +74,6 @@ object KeyguardIndicationAreaBinder { disposables += view.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.STARTED) { - launch("$TAG#viewModel.indicationAreaTranslationX") { viewModel.indicationAreaTranslationX.collect { translationX -> view.translationX = translationX @@ -119,6 +119,9 @@ object KeyguardIndicationAreaBinder { launch("$TAG#viewModel.configurationChange") { viewModel.configurationChange.collect { configurationBasedDimensions.value = loadFromResources(view) + if (Flags.indicationTextA11yFix()) { + indicationController.onConfigurationChanged() + } } } @@ -140,7 +143,7 @@ object KeyguardIndicationAreaBinder { view.resources.getDimensionPixelOffset(R.dimen.keyguard_indication_area_padding), indicationTextSizePx = view.resources.getDimensionPixelSize( - com.android.internal.R.dimen.text_size_small_material, + com.android.internal.R.dimen.text_size_small_material ), ) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt index 6d270b219c81..d8bd4452f2a6 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt @@ -54,6 +54,7 @@ import com.android.systemui.customization.R as customR import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor import com.android.systemui.keyguard.KeyguardViewMediator import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor +import com.android.systemui.keyguard.domain.interactor.WallpaperFocalAreaInteractor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters @@ -87,6 +88,7 @@ import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.DisposableHandle import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update @@ -105,6 +107,7 @@ object KeyguardRootViewBinder { screenOffAnimationController: ScreenOffAnimationController, shadeInteractor: ShadeInteractor, clockInteractor: KeyguardClockInteractor, + wallpaperFocalAreaInteractor: WallpaperFocalAreaInteractor, clockViewModel: KeyguardClockViewModel, interactionJankMonitor: InteractionJankMonitor?, deviceEntryHapticsInteractor: DeviceEntryHapticsInteractor?, @@ -309,12 +312,15 @@ object KeyguardRootViewBinder { .setTag(clockId) jankMonitor.begin(builder) } + TransitionState.CANCELED -> jankMonitor.cancel(CUJ_SCREEN_OFF_SHOW_AOD) + TransitionState.FINISHED -> { keyguardViewMediator?.maybeHandlePendingLock() jankMonitor.end(CUJ_SCREEN_OFF_SHOW_AOD) } + TransitionState.RUNNING -> Unit } } @@ -378,6 +384,21 @@ object KeyguardRootViewBinder { } disposables += + view.repeatWhenAttached { + repeatOnLifecycle(Lifecycle.State.STARTED) { + if (viewModel.shouldSendFocalArea.value) { + launch { + wallpaperFocalAreaInteractor.wallpaperFocalAreaBounds + .filterNotNull() + .collect { + wallpaperFocalAreaInteractor.setWallpaperFocalAreaBounds(it) + } + } + } + } + } + + disposables += view.onLayoutChanged( OnLayoutChange( viewModel, @@ -523,6 +544,7 @@ object KeyguardRootViewBinder { View.INVISIBLE } } + else -> { if (isVisible.value) { CrossFadeHelper.fadeIn(this, animatorListener) 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 a2107871a585..7605bdd3d7b5 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 @@ -55,6 +55,7 @@ import com.android.systemui.customization.R as customR import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.keyguard.domain.interactor.WallpaperFocalAreaInteractor import com.android.systemui.keyguard.shared.model.ClockSizeSetting import com.android.systemui.keyguard.ui.binder.KeyguardPreviewClockViewBinder import com.android.systemui.keyguard.ui.binder.KeyguardPreviewSmartspaceViewBinder @@ -117,6 +118,7 @@ constructor( private val secureSettings: SecureSettings, private val defaultShortcutsSection: DefaultShortcutsSection, private val keyguardQuickAffordanceViewBinder: KeyguardQuickAffordanceViewBinder, + private val wallpaperFocalAreaInteractor: WallpaperFocalAreaInteractor, ) { val hostToken: IBinder? = bundle.getBinder(KEY_HOST_TOKEN) private val width: Int = bundle.getInt(KEY_VIEW_WIDTH) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/BlurConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/BlurConfig.kt index 3eb8522e0338..542fb9b46bef 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/BlurConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/BlurConfig.kt @@ -23,10 +23,4 @@ data class BlurConfig(val minBlurRadiusPx: Float, val maxBlurRadiusPx: Float) { // No-op config that will be used by dagger of other SysUI variants which don't blur the // background surface. @Inject constructor() : this(0.0f, 0.0f) - - companion object { - // Blur the shade much lesser than the background surface so that the surface is - // distinguishable from the background. - @JvmStatic fun Float.maxBlurRadiusToNotificationPanelBlurRadius(): Float = this / 3.0f - } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt index f34cc07f26ae..82abb05177e1 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt @@ -23,6 +23,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.keyguard.ui.view.layout.sections.AccessibilityActionsSection import com.android.systemui.keyguard.ui.view.layout.sections.AodBurnInSection import com.android.systemui.keyguard.ui.view.layout.sections.AodNotificationIconsSection +import com.android.systemui.keyguard.ui.view.layout.sections.AodPromotedNotificationSection import com.android.systemui.keyguard.ui.view.layout.sections.ClockSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultDeviceEntrySection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection @@ -60,6 +61,7 @@ constructor( defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection, defaultStatusBarSection: DefaultStatusBarSection, defaultNotificationStackScrollLayoutSection: DefaultNotificationStackScrollLayoutSection, + aodPromotedNotificationSection: AodPromotedNotificationSection, aodNotificationIconsSection: AodNotificationIconsSection, aodBurnInSection: AodBurnInSection, clockSection: ClockSection, @@ -79,6 +81,7 @@ constructor( defaultStatusBarSection, defaultNotificationStackScrollLayoutSection, aodNotificationIconsSection, + aodPromotedNotificationSection, smartspaceSection, aodBurnInSection, clockSection, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt index 6f7872c9cb96..4f4442350873 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt @@ -38,6 +38,7 @@ import com.android.systemui.statusbar.notification.icon.ui.viewbinder.AlwaysOnDi import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel +import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiAod import com.android.systemui.statusbar.phone.NotificationIconContainer import com.android.systemui.statusbar.ui.SystemBarUtilsState import com.android.systemui.util.ui.value @@ -94,7 +95,11 @@ constructor( context.resources.getDimensionPixelSize(R.dimen.keyguard_status_view_bottom_margin) val isVisible = rootViewModel.isNotifIconContainerVisible.value constraintSet.apply { - connect(nicId, TOP, R.id.smart_space_barrier_bottom, BOTTOM, bottomMargin) + if (PromotedNotificationUiAod.isEnabled) { + connect(nicId, TOP, AodPromotedNotificationSection.viewId, BOTTOM, bottomMargin) + } else { + connect(nicId, TOP, R.id.smart_space_barrier_bottom, BOTTOM, bottomMargin) + } setGoneMargin(nicId, BOTTOM, bottomMargin) setVisibility(nicId, if (isVisible.value) VISIBLE else GONE) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodPromotedNotificationSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodPromotedNotificationSection.kt new file mode 100644 index 000000000000..ed1bdb0e2922 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodPromotedNotificationSection.kt @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.layout.sections + +import androidx.compose.ui.platform.ComposeView +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.constraintlayout.widget.ConstraintSet +import androidx.constraintlayout.widget.ConstraintSet.BOTTOM +import androidx.constraintlayout.widget.ConstraintSet.END +import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID +import androidx.constraintlayout.widget.ConstraintSet.START +import androidx.constraintlayout.widget.ConstraintSet.TOP +import com.android.systemui.keyguard.shared.model.KeyguardSection +import com.android.systemui.res.R +import com.android.systemui.statusbar.notification.promoted.AODPromotedNotification +import com.android.systemui.statusbar.notification.promoted.PromotedNotificationLogger +import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiAod +import com.android.systemui.statusbar.notification.promoted.ui.viewmodel.AODPromotedNotificationViewModel +import javax.inject.Inject + +class AodPromotedNotificationSection +@Inject +constructor( + private val viewModelFactory: AODPromotedNotificationViewModel.Factory, + private val logger: PromotedNotificationLogger, +) : KeyguardSection() { + var view: ComposeView? = null + + override fun addViews(constraintLayout: ConstraintLayout) { + if (!PromotedNotificationUiAod.isEnabled) { + return + } + + check(view == null) + + view = + ComposeView(constraintLayout.context).apply { + setContent { AODPromotedNotification(viewModelFactory) } + id = viewId + constraintLayout.addView(this) + } + + logger.logSectionAddedViews() + } + + override fun bindData(constraintLayout: ConstraintLayout) { + if (!PromotedNotificationUiAod.isEnabled) { + return + } + + checkNotNull(view) + + // Do nothing; the binding happens in the AODPromotedNotification Composable. + + logger.logSectionBoundData() + } + + override fun applyConstraints(constraintSet: ConstraintSet) { + if (!PromotedNotificationUiAod.isEnabled) { + return + } + + checkNotNull(view) + + constraintSet.apply { + connect(viewId, TOP, R.id.smart_space_barrier_bottom, BOTTOM, 0) + connect(viewId, START, PARENT_ID, START, 0) + connect(viewId, END, PARENT_ID, END, 0) + + constrainWidth(viewId, ConstraintSet.MATCH_CONSTRAINT) + constrainHeight(viewId, ConstraintSet.WRAP_CONTENT) + } + + logger.logSectionAppliedConstraints() + } + + override fun removeViews(constraintLayout: ConstraintLayout) { + if (!PromotedNotificationUiAod.isEnabled) { + return + } + + constraintLayout.removeView(checkNotNull(view)) + + view = null + + logger.logSectionRemovedViews() + } + + companion object { + val viewId = R.id.aod_promoted_notification_frame + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt index a595d815e016..29bda7623675 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt @@ -86,15 +86,56 @@ class ClockSizeTransition( transition.values[SMARTSPACE_BOUNDS] = targetSSView.getRect() } - open fun mutateBounds( - view: View, - fromIsVis: Boolean, - toIsVis: Boolean, - fromBounds: Rect, - toBounds: Rect, - fromSSBounds: Rect?, - toSSBounds: Rect?, - ) {} + open fun initTargets(from: Target, to: Target) {} + + open fun mutateTargets(from: Target, to: Target) {} + + data class Target( + var view: View, + var visibility: Int, + var isVisible: Boolean, + var alpha: Float, + var bounds: Rect, + var ssBounds: Rect?, + ) { + companion object { + fun fromStart(startValues: TransitionValues): Target { + var fromVis = startValues.values[PROP_VISIBILITY] as Int + var fromIsVis = fromVis == View.VISIBLE + var fromAlpha = startValues.values[PROP_ALPHA] as Float + + // Align starting visibility and alpha + if (!fromIsVis) fromAlpha = 0f + else if (fromAlpha <= 0f) { + fromIsVis = false + fromVis = View.INVISIBLE + } + + return Target( + view = startValues.view, + visibility = fromVis, + isVisible = fromIsVis, + alpha = fromAlpha, + bounds = startValues.values[PROP_BOUNDS] as Rect, + ssBounds = startValues.values[SMARTSPACE_BOUNDS] as Rect?, + ) + } + + fun fromEnd(endValues: TransitionValues): Target { + val toVis = endValues.values[PROP_VISIBILITY] as Int + val toIsVis = toVis == View.VISIBLE + + return Target( + view = endValues.view, + visibility = toVis, + isVisible = toIsVis, + alpha = if (toIsVis) 1f else 0f, + bounds = endValues.values[PROP_BOUNDS] as Rect, + ssBounds = endValues.values[SMARTSPACE_BOUNDS] as Rect?, + ) + } + } + } override fun createAnimator( sceenRoot: ViewGroup, @@ -109,72 +150,58 @@ class ClockSizeTransition( return null } - var fromVis = startValues.values[PROP_VISIBILITY] as Int - var fromIsVis = fromVis == View.VISIBLE - var fromAlpha = startValues.values[PROP_ALPHA] as Float - val fromBounds = startValues.values[PROP_BOUNDS] as Rect - val fromSSBounds = startValues.values[SMARTSPACE_BOUNDS] as Rect? - - val toView = endValues.view - val toVis = endValues.values[PROP_VISIBILITY] as Int - val toBounds = endValues.values[PROP_BOUNDS] as Rect - val toSSBounds = endValues.values[SMARTSPACE_BOUNDS] as Rect? - val toIsVis = toVis == View.VISIBLE - val toAlpha = if (toIsVis) 1f else 0f - - // Align starting visibility and alpha - if (!fromIsVis) fromAlpha = 0f - else if (fromAlpha <= 0f) { - fromIsVis = false - fromVis = View.INVISIBLE - } + val from = Target.fromStart(startValues) + val to = Target.fromEnd(endValues) + initTargets(from, to) + mutateTargets(from, to) - mutateBounds(toView, fromIsVis, toIsVis, fromBounds, toBounds, fromSSBounds, toSSBounds) - if (fromIsVis == toIsVis && fromBounds.equals(toBounds)) { + if (from.isVisible == to.isVisible && from.bounds.equals(to.bounds)) { if (DEBUG) { Log.w( TAG, - "Skipping no-op transition: $toView; " + - "vis: $fromVis -> $toVis; " + - "alpha: $fromAlpha -> $toAlpha; " + - "bounds: $fromBounds -> $toBounds; ", + "Skipping no-op transition: ${to.view}; " + + "vis: ${from.visibility} -> ${to.visibility}; " + + "alpha: ${from.alpha} -> ${to.alpha}; " + + "bounds: ${from.bounds} -> ${to.bounds}; ", ) } return null } - val sendToBack = fromIsVis && !toIsVis + val sendToBack = from.isVisible && !to.isVisible fun lerp(start: Int, end: Int, fract: Float): Int = MathUtils.lerp(start.toFloat(), end.toFloat(), fract).toInt() fun computeBounds(fract: Float): Rect = Rect( - lerp(fromBounds.left, toBounds.left, fract), - lerp(fromBounds.top, toBounds.top, fract), - lerp(fromBounds.right, toBounds.right, fract), - lerp(fromBounds.bottom, toBounds.bottom, fract), + lerp(from.bounds.left, to.bounds.left, fract), + lerp(from.bounds.top, to.bounds.top, fract), + lerp(from.bounds.right, to.bounds.right, fract), + lerp(from.bounds.bottom, to.bounds.bottom, fract), ) fun assignAnimValues(src: String, fract: Float, vis: Int? = null) { + mutateTargets(from, to) val bounds = computeBounds(fract) - val alpha = MathUtils.lerp(fromAlpha, toAlpha, fract) + val alpha = MathUtils.lerp(from.alpha, to.alpha, fract) if (DEBUG) { Log.i( TAG, - "$src: $toView; fract=$fract; alpha=$alpha; vis=$vis; bounds=$bounds;", + "$src: ${to.view}; fract=$fract; alpha=$alpha; vis=$vis; bounds=$bounds;", ) } - toView.setVisibility(vis ?: View.VISIBLE) - toView.setAlpha(alpha) - toView.setRect(bounds) + + to.view.setVisibility(vis ?: View.VISIBLE) + to.view.setAlpha(alpha) + to.view.setRect(bounds) } if (DEBUG) { Log.i( TAG, - "transitioning: $toView; " + - "vis: $fromVis -> $toVis; " + - "alpha: $fromAlpha -> $toAlpha; " + - "bounds: $fromBounds -> $toBounds; ", + "transitioning: ${to.view}; " + + "vis: ${from.visibility} -> ${to.visibility}; " + + "alpha: ${from.alpha} -> ${to.alpha}; " + + "bounds: ${from.bounds} -> ${to.bounds}; ", ) } @@ -190,11 +217,11 @@ class ClockSizeTransition( this@VisibilityBoundsTransition.addListener( object : TransitionListenerAdapter() { override fun onTransitionStart(t: Transition) { - toView.viewTreeObserver.addOnPreDrawListener(predrawCallback) + to.view.viewTreeObserver.addOnPreDrawListener(predrawCallback) } override fun onTransitionEnd(t: Transition) { - toView.viewTreeObserver.removeOnPreDrawListener(predrawCallback) + to.view.viewTreeObserver.removeOnPreDrawListener(predrawCallback) } } ) @@ -202,17 +229,17 @@ class ClockSizeTransition( val listener = object : AnimatorListenerAdapter() { override fun onAnimationStart(anim: Animator) { - assignAnimValues("start", 0f, fromVis) + assignAnimValues("start", 0f, from.visibility) } override fun onAnimationEnd(anim: Animator) { - assignAnimValues("end", 1f, toVis) - if (sendToBack) toView.translationZ = 0f + assignAnimValues("end", 1f, to.visibility) + if (sendToBack) to.view.translationZ = 0f } } anim.addListener(listener) - assignAnimValues("init", 0f, fromVis) + assignAnimValues("init", 0f, from.visibility) } } @@ -251,31 +278,23 @@ class ClockSizeTransition( } } - override fun mutateBounds( - view: View, - fromIsVis: Boolean, - toIsVis: Boolean, - fromBounds: Rect, - toBounds: Rect, - fromSSBounds: Rect?, - toSSBounds: Rect?, - ) { + override fun initTargets(from: Target, to: Target) { // Move normally if clock is not changing visibility - if (fromIsVis == toIsVis) return + if (from.isVisible == to.isVisible) return - fromBounds.set(toBounds) + from.bounds.set(to.bounds) if (isLargeClock) { // Large clock shouldn't move; fromBounds already set - } else if (toSSBounds != null && fromSSBounds != null) { + } else if (to.ssBounds != null && from.ssBounds != null) { // Instead of moving the small clock the full distance, we compute the distance // smartspace will move. We then scale this to match the duration of this animation // so that the small clock moves at the same speed as smartspace. val ssTranslation = - abs((toSSBounds.top - fromSSBounds.top) * smallClockMoveScale).toInt() - fromBounds.top = toBounds.top - ssTranslation - fromBounds.bottom = toBounds.bottom - ssTranslation + abs((to.ssBounds!!.top - from.ssBounds!!.top) * smallClockMoveScale).toInt() + from.bounds.top = to.bounds.top - ssTranslation + from.bounds.bottom = to.bounds.bottom - ssTranslation } else { - Log.e(TAG, "mutateBounds: smallClock received no smartspace bounds") + Log.e(TAG, "initTargets: smallClock received no smartspace bounds") } } } @@ -320,10 +339,9 @@ class ClockSizeTransition( } } - // TODO: Might need a mechanism to update this one while in-progress class SmartspaceMoveTransition( val config: IntraBlueprintTransition.Config, - viewModel: KeyguardClockViewModel, + val viewModel: KeyguardClockViewModel, ) : VisibilityBoundsTransition() { private val isLargeClock = viewModel.isLargeClockVisible.value override val captureSmartspace = false @@ -340,23 +358,23 @@ class ClockSizeTransition( addTarget(R.id.status_view_media_container) } - override fun mutateBounds( - view: View, - fromIsVis: Boolean, - toIsVis: Boolean, - fromBounds: Rect, - toBounds: Rect, - fromSSBounds: Rect?, - toSSBounds: Rect?, - ) { + override fun initTargets(from: Target, to: Target) { // If view is changing visibility, hold it in place - if (fromIsVis == toIsVis) return - if (DEBUG) Log.i(TAG, "Holding position of ${view.id}") + if (from.isVisible == to.isVisible) return + if (DEBUG) Log.i(TAG, "Holding position of ${to.view.id}") - if (fromIsVis) { - toBounds.set(fromBounds) + if (from.isVisible) { + to.bounds.set(from.bounds) } else { - fromBounds.set(toBounds) + from.bounds.set(to.bounds) + } + } + + override fun mutateTargets(from: Target, to: Target) { + if (to.view.id == sharedR.id.date_smartspace_view) { + to.isVisible = !viewModel.hasCustomWeatherDataDisplay.value + to.visibility = if (to.isVisible) View.VISIBLE else View.GONE + to.alpha = if (to.isVisible) 1f else 0f } } 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 733d7d71061e..b531c7fa49ec 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 @@ -24,7 +24,6 @@ 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.BlurConfig -import com.android.systemui.keyguard.ui.transitions.BlurConfig.Companion.maxBlurRadiusToNotificationPanelBlurRadius import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition import com.android.systemui.scene.shared.flag.SceneContainerFlag @@ -89,9 +88,7 @@ constructor( shadeDependentFlows.transitionFlow( flowWhenShadeIsNotExpanded = emptyFlow(), flowWhenShadeIsExpanded = - transitionAnimation.immediatelyTransitionTo( - blurConfig.maxBlurRadiusPx.maxBlurRadiusToNotificationPanelBlurRadius() - ), + transitionAnimation.immediatelyTransitionTo(blurConfig.maxBlurRadiusPx), ) } else { emptyFlow<Float>() diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardMediaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardMediaViewModel.kt index e68e465ed55a..ba03c48c65e9 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardMediaViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardMediaViewModel.kt @@ -16,10 +16,50 @@ package com.android.systemui.keyguard.ui.viewmodel +import androidx.compose.runtime.getValue +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor +import com.android.systemui.lifecycle.ExclusiveActivatable +import com.android.systemui.lifecycle.Hydrator import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor -import javax.inject.Inject -import kotlinx.coroutines.flow.StateFlow +import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlinx.coroutines.flow.flowOf -class KeyguardMediaViewModel @Inject constructor(mediaCarouselInteractor: MediaCarouselInteractor) { - val isMediaVisible: StateFlow<Boolean> = mediaCarouselInteractor.hasActiveMediaOrRecommendation +class KeyguardMediaViewModel +@AssistedInject +constructor( + mediaCarouselInteractor: MediaCarouselInteractor, + keyguardInteractor: KeyguardInteractor, +) : ExclusiveActivatable() { + + private val hydrator = Hydrator("KeyguardMediaViewModel.hydrator") + /** + * Whether media carousel is visible on lockscreen. Media may be presented on lockscreen but + * still hidden on certain surfaces like AOD + */ + val isMediaVisible: Boolean by + hydrator.hydratedStateOf( + traceName = "isMediaVisible", + source = + keyguardInteractor.isDozing.flatMapLatestConflated { isDozing -> + if (isDozing) { + flowOf(false) + } else { + mediaCarouselInteractor.hasActiveMediaOrRecommendation + } + }, + initialValue = + !keyguardInteractor.isDozing.value && + mediaCarouselInteractor.hasActiveMediaOrRecommendation.value, + ) + + override suspend fun onActivated(): Nothing { + hydrator.activate() + } + + @AssistedFactory + interface Factory { + fun create(): KeyguardMediaViewModel + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt index e51e05b8ab61..aa4293a201ac 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt @@ -28,6 +28,7 @@ import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.domain.interactor.PulseExpansionInteractor +import com.android.systemui.keyguard.domain.interactor.WallpaperFocalAreaInteractor import com.android.systemui.keyguard.shared.model.Edge import com.android.systemui.keyguard.shared.model.KeyguardState.AOD import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING @@ -133,6 +134,7 @@ constructor( private val screenOffAnimationController: ScreenOffAnimationController, private val aodBurnInViewModel: AodBurnInViewModel, private val shadeInteractor: ShadeInteractor, + wallpaperFocalAreaInteractor: WallpaperFocalAreaInteractor, ) { val burnInLayerVisibility: Flow<Int> = keyguardTransitionInteractor.startedKeyguardTransitionStep @@ -362,6 +364,8 @@ constructor( initialValue = AnimatedValue.NotAnimating(false), ) + val shouldSendFocalArea = wallpaperFocalAreaInteractor.shouldSendFocalArea + fun onNotificationContainerBoundsChanged(top: Float, bottom: Float, animate: Boolean = false) { keyguardInteractor.setNotificationContainerBounds( NotificationContainerBounds(top = top, bottom = bottom, isAnimated = animate) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt index 44c4c8723dcb..89dcbf6aa52b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt @@ -24,7 +24,6 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.BlurConfig -import com.android.systemui.keyguard.ui.transitions.BlurConfig.Companion.maxBlurRadiusToNotificationPanelBlurRadius import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition import com.android.systemui.scene.shared.flag.SceneContainerFlag @@ -88,9 +87,7 @@ constructor( shadeDependentFlows.transitionFlow( flowWhenShadeIsNotExpanded = emptyFlow(), flowWhenShadeIsExpanded = - transitionAnimation.immediatelyTransitionTo( - blurConfig.maxBlurRadiusPx.maxBlurRadiusToNotificationPanelBlurRadius() - ), + transitionAnimation.immediatelyTransitionTo(blurConfig.maxBlurRadiusPx), ) } else { emptyFlow() diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt index 425e674ec804..fb69b793d975 100644 --- a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt @@ -21,6 +21,7 @@ import com.android.systemui.dump.DumpManager import com.android.systemui.log.LogBufferHelper.Companion.adjustMaxSize import com.android.systemui.log.LogcatEchoTracker import com.android.systemui.util.time.SystemClock +import java.util.concurrent.ConcurrentHashMap import javax.inject.Inject @SysUISingleton @@ -31,7 +32,7 @@ constructor( private val systemClock: SystemClock, private val logcatEchoTracker: LogcatEchoTracker, ) { - private val existingBuffers = mutableMapOf<String, TableLogBuffer>() + private val existingBuffers = ConcurrentHashMap<String, TableLogBuffer>() /** * Creates a new [TableLogBuffer]. This method should only be called from static contexts, where @@ -42,17 +43,9 @@ constructor( * @param maxSize the buffer max size. See [adjustMaxSize] * @return a new [TableLogBuffer] registered with [DumpManager] */ - fun create( - name: String, - maxSize: Int, - ): TableLogBuffer { + fun create(name: String, maxSize: Int): TableLogBuffer { val tableBuffer = - TableLogBuffer( - adjustMaxSize(maxSize), - name, - systemClock, - logcatEchoTracker, - ) + TableLogBuffer(adjustMaxSize(maxSize), name, systemClock, logcatEchoTracker) dumpManager.registerTableLogBuffer(name, tableBuffer) return tableBuffer } @@ -66,13 +59,12 @@ constructor( * * @return a [TableLogBuffer] suitable for reuse */ - fun getOrCreate( - name: String, - maxSize: Int, - ): TableLogBuffer = - existingBuffers.getOrElse(name) { - val buffer = create(name, maxSize) - existingBuffers[name] = buffer - buffer + fun getOrCreate(name: String, maxSize: Int): TableLogBuffer = + synchronized(existingBuffers) { + existingBuffers.getOrElse(name) { + val buffer = create(name, maxSize) + existingBuffers[name] = buffer + buffer + } } } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java index 173a964cc5d3..0de8c40bddaa 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java @@ -536,10 +536,12 @@ public final class NavBarHelper implements } /** - * @return Whether the IME is shown on top of the screen given the {@code vis} flag of - * {@link InputMethodService} and the keyguard states. + * Checks whether the IME is visible on top of the screen, based on the given IME window + * visibility flags, and the current keyguard state. + * + * @param vis the IME window visibility. */ - public boolean isImeShown(@ImeWindowVisibility int vis) { + public boolean isImeVisible(@ImeWindowVisibility int vis) { View shadeWindowView = mNotificationShadeWindowController.getWindowRootView(); boolean isKeyguardShowing = mKeyguardStateController.isShowing(); boolean imeVisibleOnShade = shadeWindowView != null && shadeWindowView.isAttachedToWindow() diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java index 8f04896fbb45..645bd0b4b441 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java @@ -18,6 +18,7 @@ package com.android.systemui.navigationbar; import static com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler.DEBUG_MISSING_GESTURE_TAG; import static com.android.systemui.shared.recents.utilities.Utilities.isLargeScreen; +import static com.android.server.display.feature.flags.Flags.enableDisplayContentModeManagement; import static com.android.wm.shell.Flags.enableTaskbarNavbarUnification; import static com.android.wm.shell.Flags.enableTaskbarOnPhones; @@ -273,6 +274,11 @@ public class NavigationBarControllerImpl implements private final CommandQueue.Callbacks mCommandQueueCallbacks = new CommandQueue.Callbacks() { @Override public void onDisplayRemoved(int displayId) { + onDisplayRemoveSystemDecorations(displayId); + } + + @Override + public void onDisplayRemoveSystemDecorations(int displayId) { removeNavigationBar(displayId); mHasNavBar.delete(displayId); } @@ -309,6 +315,13 @@ public class NavigationBarControllerImpl implements navBarView.showPinningEscapeToast(); } } + + @Override + public void setHasNavigationBar(int displayId, boolean hasNavigationBar) { + if (enableDisplayContentModeManagement()) { + mHasNavBar.put(displayId, hasNavigationBar); + } + } }; /** diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java index 9270fff61c43..c2dacd6690a0 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java @@ -18,7 +18,8 @@ package com.android.systemui.navigationbar; import static android.app.ActivityManager.LOCK_TASK_MODE_PINNED; import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT; -import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_SHOWN; +import static android.app.StatusBarManager.NAVIGATION_HINT_IME_VISIBLE; +import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_BUTTON_VISIBLE; import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; @@ -30,13 +31,15 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BACK_DISABLED; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED; -import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING; -import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_SHOWING; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_ALT_BACK; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_VISIBLE; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_BUTTON_VISIBLE; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING; import android.app.StatusBarManager; +import android.app.StatusBarManager.NavigationHint; import android.app.StatusBarManager.WindowVisibleState; import android.content.Context; import android.graphics.Rect; @@ -111,6 +114,7 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, private TaskStackChangeListeners mTaskStackChangeListeners; private Optional<Pip> mPipOptional; private int mDefaultDisplayId; + @NavigationHint private int mNavigationIconHints; private final NavBarHelper.NavbarTaskbarStateUpdater mNavbarTaskbarStateUpdater = new NavBarHelper.NavbarTaskbarStateUpdater() { @@ -261,6 +265,20 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, } } + @Override + public void onDisplayRemoveSystemDecorations(int displayId) { + CommandQueue.Callbacks.super.onDisplayRemoveSystemDecorations(displayId); + if (mOverviewProxyService.getProxy() == null) { + return; + } + + try { + mOverviewProxyService.getProxy().onDisplayRemoveSystemDecorations(displayId); + } catch (RemoteException e) { + Log.e(TAG, "onDisplaySystemDecorationsRemoved() failed", e); + } + } + // Separated into a method to keep setDependencies() clean/readable. private LightBarTransitionsController createLightBarTransitionsController() { @@ -360,10 +378,12 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, mSysUiState.setFlag(SYSUI_STATE_A11Y_BUTTON_CLICKABLE, clickable) .setFlag(SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE, longClickable) - .setFlag(SYSUI_STATE_IME_SHOWING, + .setFlag(SYSUI_STATE_IME_VISIBLE, + (mNavigationIconHints & NAVIGATION_HINT_IME_VISIBLE) != 0) + .setFlag(SYSUI_STATE_IME_SWITCHER_BUTTON_VISIBLE, + (mNavigationIconHints & NAVIGATION_HINT_IME_SWITCHER_BUTTON_VISIBLE) != 0) + .setFlag(SYSUI_STATE_IME_ALT_BACK, (mNavigationIconHints & NAVIGATION_HINT_BACK_ALT) != 0) - .setFlag(SYSUI_STATE_IME_SWITCHER_SHOWING, - (mNavigationIconHints & NAVIGATION_HINT_IME_SWITCHER_SHOWN) != 0) .setFlag(SYSUI_STATE_OVERVIEW_DISABLED, (mDisabledFlags & View.STATUS_BAR_DISABLE_RECENT) != 0) .setFlag(SYSUI_STATE_HOME_DISABLED, @@ -483,18 +503,17 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, @Override public void setImeWindowStatus(int displayId, @ImeWindowVisibility int vis, @BackDispositionMode int backDisposition, boolean showImeSwitcher) { - boolean imeShown = mNavBarHelper.isImeShown(vis); - if (!imeShown) { - // Count imperceptible changes as visible so we transition taskbar out quickly. - imeShown = (vis & InputMethodService.IME_VISIBLE_IMPERCEPTIBLE) != 0; - } - showImeSwitcher = imeShown && showImeSwitcher; - int hints = Utilities.calculateBackDispositionHints(mNavigationIconHints, backDisposition, - imeShown, showImeSwitcher); - if (hints != mNavigationIconHints) { - mNavigationIconHints = hints; - updateSysuiFlags(); + // Count imperceptible changes as visible so we transition taskbar out quickly. + final boolean isImeVisible = mNavBarHelper.isImeVisible(vis) + || (vis & InputMethodService.IME_VISIBLE_IMPERCEPTIBLE) != 0; + final int hints = Utilities.calculateNavigationIconHints(mNavigationIconHints, + backDisposition, isImeVisible, showImeSwitcher); + if (hints == mNavigationIconHints) { + return; } + + mNavigationIconHints = hints; + updateSysuiFlags(); } @Override 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 c098b1675b87..2744f9f84ccf 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java @@ -18,11 +18,13 @@ package com.android.systemui.navigationbar.views; import static android.app.ActivityManager.LOCK_TASK_MODE_PINNED; import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT; -import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_SHOWN; +import static android.app.StatusBarManager.NAVIGATION_HINT_IME_VISIBLE; +import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_BUTTON_VISIBLE; import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN; import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; import static android.app.StatusBarManager.WindowType; import static android.app.StatusBarManager.WindowVisibleState; +import static android.app.StatusBarManager.navigationHintsToString; import static android.app.StatusBarManager.windowStateToString; import static android.app.WindowConfiguration.ROTATION_UNDEFINED; import static android.view.InsetsSource.FLAG_SUPPRESS_SCRIM; @@ -42,8 +44,9 @@ import static com.android.systemui.shared.statusbar.phone.BarTransitions.Transit import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY; -import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING; -import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_SHOWING; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_ALT_BACK; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_VISIBLE; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_BUTTON_VISIBLE; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING; import static com.android.systemui.shared.system.QuickStepContract.isGesturalMode; @@ -56,6 +59,7 @@ import android.annotation.NonNull; import android.app.ActivityTaskManager; import android.app.IActivityTaskManager; import android.app.StatusBarManager; +import android.app.StatusBarManager.NavigationHint; import android.content.Context; import android.content.res.Configuration; import android.graphics.Insets; @@ -233,6 +237,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements private @WindowVisibleState int mNavigationBarWindowState = WINDOW_STATE_SHOWING; + @NavigationHint private int mNavigationIconHints = 0; private @TransitionMode int mTransitionMode; private boolean mLongPressHomeEnabled; @@ -649,18 +654,18 @@ public class NavigationBar extends ViewController<NavigationBarView> implements if (!mEdgeBackGestureHandler.isHandlingGestures()) { // We're in 2/3 button mode OR back button force-shown in SUW if (!mImeVisible) { - // IME not showing, take all touches + // IME is not visible, take all touches info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_FRAME); return; } if (!mView.isImeRenderingNavButtons()) { - // IME showing but not drawing any buttons, take all touches + // IME is visible but not drawing any buttons, take all touches info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_FRAME); return; } } - // When in gestural and the IME is showing, don't use the nearest region since it will + // When in gestural and the IME is visible, don't use the nearest region since it will // take gesture space away from the IME info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_REGION); info.touchableRegion.set( @@ -817,7 +822,6 @@ public class NavigationBar extends ViewController<NavigationBarView> implements if (mSavedState != null) { getBarTransitions().getLightTransitionsController().restoreState(mSavedState); } - setNavigationIconHints(mNavigationIconHints); setWindowVisible(isNavBarWindowVisible()); mView.setBehavior(mBehavior); setNavBarMode(mNavBarMode); @@ -1111,6 +1115,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements pw.println(" mLongPressHomeEnabled=" + mLongPressHomeEnabled); pw.println(" mNavigationBarWindowState=" + windowStateToString(mNavigationBarWindowState)); + pw.println(" mNavigationIconHints=" + navigationHintsToString(mNavigationIconHints)); pw.println(" mTransitionMode=" + BarTransitions.modeToString(mTransitionMode)); pw.println(" mTransientShown=" + mTransientShown); @@ -1135,11 +1140,12 @@ public class NavigationBar extends ViewController<NavigationBarView> implements if (displayId != mDisplayId) { return; } - boolean imeShown = mNavBarHelper.isImeShown(vis); - showImeSwitcher = imeShown && showImeSwitcher; - int hints = Utilities.calculateBackDispositionHints(mNavigationIconHints, backDisposition, - imeShown, showImeSwitcher); - if (hints == mNavigationIconHints) return; + final boolean isImeVisible = mNavBarHelper.isImeVisible(vis); + final int hints = Utilities.calculateNavigationIconHints(mNavigationIconHints, + backDisposition, isImeVisible, showImeSwitcher); + if (hints == mNavigationIconHints) { + return; + } setNavigationIconHints(hints); checkBarModes(); @@ -1680,10 +1686,12 @@ public class NavigationBar extends ViewController<NavigationBarView> implements mSysUiFlagsContainer.setFlag(SYSUI_STATE_A11Y_BUTTON_CLICKABLE, clickable) .setFlag(SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE, longClickable) .setFlag(SYSUI_STATE_NAV_BAR_HIDDEN, !isNavBarWindowVisible()) - .setFlag(SYSUI_STATE_IME_SHOWING, + .setFlag(SYSUI_STATE_IME_VISIBLE, + (mNavigationIconHints & NAVIGATION_HINT_IME_VISIBLE) != 0) + .setFlag(SYSUI_STATE_IME_SWITCHER_BUTTON_VISIBLE, + (mNavigationIconHints & NAVIGATION_HINT_IME_SWITCHER_BUTTON_VISIBLE) != 0) + .setFlag(SYSUI_STATE_IME_ALT_BACK, (mNavigationIconHints & NAVIGATION_HINT_BACK_ALT) != 0) - .setFlag(SYSUI_STATE_IME_SWITCHER_SHOWING, - (mNavigationIconHints & NAVIGATION_HINT_IME_SWITCHER_SHOWN) != 0) .setFlag(SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY, allowSystemGestureIgnoringBarVisibility()) .commitUpdate(mDisplayId); @@ -1926,28 +1934,35 @@ public class NavigationBar extends ViewController<NavigationBarView> implements }; @VisibleForTesting + @NavigationHint int getNavigationIconHints() { return mNavigationIconHints; } - private void setNavigationIconHints(int hints) { - if (hints == mNavigationIconHints) return; + /** + * Updates the navigation icons based on {@code hints}. + * + * @param hints bit flags defined in {@link StatusBarManager}. + */ + private void setNavigationIconHints(@NavigationHint int hints) { + if (hints == mNavigationIconHints) { + return; + } if (!isLargeScreen(mContext)) { // All IME functions handled by launcher via Sysui flags for large screen final boolean newBackAlt = (hints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0; final boolean oldBackAlt = (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0; if (newBackAlt != oldBackAlt) { - mView.onImeVisibilityChanged(newBackAlt); - mImeVisible = newBackAlt; + mView.onBackAltChanged(newBackAlt); } + mImeVisible = (hints & NAVIGATION_HINT_IME_VISIBLE) != 0; mView.setNavigationIconHints(hints); } if (DEBUG) { - android.widget.Toast.makeText(mContext, - "Navigation icon hints = " + hints, - 500).show(); + android.widget.Toast.makeText(mContext, "Navigation icon hints = " + hints, 500) + .show(); } mNavigationIconHints = hints; } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java index c4abcd2afc4f..de61d404b1e8 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java @@ -16,6 +16,9 @@ package com.android.systemui.navigationbar.views; +import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT; +import static android.app.StatusBarManager.NAVIGATION_HINT_IME_VISIBLE; +import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_BUTTON_VISIBLE; import static android.inputmethodservice.InputMethodService.canImeRenderGesturalNavButtons; import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL; @@ -31,7 +34,7 @@ import android.animation.PropertyValuesHolder; import android.animation.TimeInterpolator; import android.animation.ValueAnimator; import android.annotation.DrawableRes; -import android.app.StatusBarManager; +import android.app.StatusBarManager.NavigationHint; import android.content.Context; import android.content.res.Configuration; import android.graphics.Canvas; @@ -113,6 +116,7 @@ public class NavigationBarView extends FrameLayout { boolean mLongClickableAccessibilityButton; int mDisabledFlags = 0; + @NavigationHint int mNavigationIconHints = 0; private int mNavBarMode; private boolean mImeDrawsImeNavBar; @@ -499,8 +503,7 @@ public class NavigationBarView extends FrameLayout { } private void orientBackButton(KeyButtonDrawable drawable) { - final boolean useAltBack = - (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0; + final boolean useAltBack = (mNavigationIconHints & NAVIGATION_HINT_BACK_ALT) != 0; final boolean isRtl = mConfiguration.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; float degrees = useAltBack ? (isRtl ? 90 : -90) : 0; if (drawable.getRotation() == degrees) { @@ -555,14 +558,23 @@ public class NavigationBarView extends FrameLayout { super.setLayoutDirection(layoutDirection); } - void setNavigationIconHints(int hints) { - if (hints == mNavigationIconHints) return; + void setNavigationIconHints(@NavigationHint int hints) { + if (hints == mNavigationIconHints) { + return; + } mNavigationIconHints = hints; updateNavButtonIcons(); } - void onImeVisibilityChanged(boolean visible) { - if (!visible) { + /** + * Called when the boolean value of whether to adjust the back button for the IME changed. + * + * @param useBackAlt whether to adjust the back button for the IME. + * + * @see android.inputmethodservice.InputMethodService.BackDispositionMode + */ + void onBackAltChanged(boolean useBackAlt) { + if (!useBackAlt) { mTransitionListener.onBackAltCleared(); } } @@ -587,8 +599,7 @@ public class NavigationBarView extends FrameLayout { // We have to replace or restore the back and home button icons when exiting or entering // carmode, respectively. Recents are not available in CarMode in nav bar so change // to recent icon is not required. - final boolean useAltBack = - (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0; + final boolean useAltBack = (mNavigationIconHints & NAVIGATION_HINT_BACK_ALT) != 0; KeyButtonDrawable backIcon = mBackIcon; orientBackButton(backIcon); KeyButtonDrawable homeIcon = mHomeDefaultIcon; @@ -600,11 +611,12 @@ public class NavigationBarView extends FrameLayout { updateRecentsIcon(); - // Update IME button visibility, a11y and rotate button always overrides the appearance - boolean disableImeSwitcher = - (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_SHOWN) == 0 - || isImeRenderingNavButtons(); - mContextualButtonGroup.setButtonVisibility(R.id.ime_switcher, !disableImeSwitcher); + // Update IME switcher button visibility, a11y and rotate button always overrides + // the appearance. + final boolean isImeSwitcherButtonVisible = + (mNavigationIconHints & NAVIGATION_HINT_IME_SWITCHER_BUTTON_VISIBLE) != 0 + && !isImeRenderingNavButtons(); + mContextualButtonGroup.setButtonVisibility(R.id.ime_switcher, isImeSwitcherButtonVisible); mBarTransitions.reapplyDarkIntensity(); @@ -658,7 +670,7 @@ public class NavigationBarView extends FrameLayout { boolean isImeRenderingNavButtons() { return mImeDrawsImeNavBar && mImeCanRenderGesturalNavButtons - && (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) != 0; + && (mNavigationIconHints & NAVIGATION_HINT_IME_VISIBLE) != 0; } @VisibleForTesting diff --git a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModel.kt index 4fe63379aed4..8aad61a8c7cb 100644 --- a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModel.kt @@ -52,6 +52,13 @@ constructor( private val hydrator = Hydrator("NotificationsShadeOverlayContentViewModel.hydrator") + val isShadeLayoutWide: Boolean by + hydrator.hydratedStateOf( + traceName = "isShadeLayoutWide", + initialValue = shadeInteractor.isShadeLayoutWide.value, + source = shadeInteractor.isShadeLayoutWide, + ) + val showHeader: Boolean by hydrator.hydratedStateOf( traceName = "showHeader", diff --git a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeUserActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeUserActionsViewModel.kt deleted file mode 100644 index 398ace4b67f4..000000000000 --- a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeUserActionsViewModel.kt +++ /dev/null @@ -1,52 +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.notifications.ui.viewmodel - -import com.android.compose.animation.scene.Back -import com.android.compose.animation.scene.Swipe -import com.android.compose.animation.scene.UserAction -import com.android.compose.animation.scene.UserActionResult -import com.android.compose.animation.scene.UserActionResult.ReplaceByOverlay -import com.android.systemui.scene.shared.model.Overlays -import com.android.systemui.scene.shared.model.SceneFamilies -import com.android.systemui.scene.ui.viewmodel.SceneContainerEdge -import com.android.systemui.scene.ui.viewmodel.UserActionsViewModel -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject - -/** - * Models the UI state for the user actions that the user can perform to navigate to other scenes. - */ -class NotificationsShadeUserActionsViewModel @AssistedInject constructor() : - UserActionsViewModel() { - - override suspend fun hydrateActions(setActions: (Map<UserAction, UserActionResult>) -> Unit) { - setActions( - mapOf( - Back to SceneFamilies.Home, - Swipe.Up to SceneFamilies.Home, - Swipe.Down(fromSource = SceneContainerEdge.TopRight) to - ReplaceByOverlay(Overlays.QuickSettingsShade), - ) - ) - } - - @AssistedFactory - interface Factory { - fun create(): NotificationsShadeUserActionsViewModel - } -} diff --git a/packages/SystemUI/src/com/android/systemui/qs/HeaderPrivacyIconsController.kt b/packages/SystemUI/src/com/android/systemui/qs/HeaderPrivacyIconsController.kt index 3fd9803882ed..4a9e72f23446 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/HeaderPrivacyIconsController.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/HeaderPrivacyIconsController.kt @@ -30,6 +30,7 @@ import javax.inject.Inject import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.shade.ShadeViewProviderModule.Companion.SHADE_HEADER +import com.android.systemui.shade.domain.interactor.ShadeDialogContextInteractor import com.android.systemui.statusbar.policy.DeviceProvisionedController import javax.inject.Named @@ -63,7 +64,8 @@ class HeaderPrivacyIconsController @Inject constructor( private val broadcastDispatcher: BroadcastDispatcher, private val safetyCenterManager: SafetyCenterManager, private val deviceProvisionedController: DeviceProvisionedController, - private val featureFlags: FeatureFlags + private val featureFlags: FeatureFlags, + private val shadeDialogContextInteractor: ShadeDialogContextInteractor, ) { var chipVisibilityListener: ChipVisibilityListener? = null @@ -75,6 +77,8 @@ class HeaderPrivacyIconsController @Inject constructor( private val cameraSlot = privacyChip.resources.getString(R.string.status_bar_camera) private val micSlot = privacyChip.resources.getString(R.string.status_bar_microphone) private val locationSlot = privacyChip.resources.getString(R.string.status_bar_location) + private val dialogContext: Context + get() = shadeDialogContextInteractor.context private val safetyCenterReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { @@ -149,12 +153,12 @@ class HeaderPrivacyIconsController @Inject constructor( uiEventLogger.log(PrivacyChipEvent.ONGOING_INDICATORS_CHIP_CLICK) if (safetyCenterEnabled) { if (featureFlags.isEnabled(Flags.ENABLE_NEW_PRIVACY_DIALOG)) { - privacyDialogControllerV2.showDialog(privacyChip.context, privacyChip) + privacyDialogControllerV2.showDialog(dialogContext, privacyChip) } else { showSafetyCenter() } } else { - privacyDialogController.showDialog(privacyChip.context) + privacyDialogController.showDialog(dialogContext) } } setChipVisibility(privacyChip.visibility == View.VISIBLE) 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 6d9078432ae0..07de4662e82f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt @@ -102,6 +102,7 @@ import com.android.compose.modifiers.thenIf import com.android.compose.theme.PlatformTheme import com.android.mechanics.GestureContext import com.android.systemui.Dumpable +import com.android.systemui.Flags import com.android.systemui.brightness.ui.compose.BrightnessSliderContainer import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.dump.DumpManager @@ -252,12 +253,14 @@ constructor( Box( modifier = Modifier.graphicsLayer { alpha = viewModel.viewAlpha } - // Clipping before translation to match QSContainerImpl.onDraw - .offset { - IntOffset( - x = 0, - y = viewModel.viewTranslationY.fastRoundToInt(), - ) + .thenIf(!Flags.notificationShadeBlur()) { + // Clipping before translation to match QSContainerImpl.onDraw + Modifier.offset { + IntOffset( + x = 0, + y = viewModel.viewTranslationY.fastRoundToInt(), + ) + } } .thenIf(notificationScrimClippingParams.isEnabled) { Modifier.notificationScrimClip { diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/BounceableInfo.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/BounceableInfo.kt index c9d767e6d152..302242ca11dd 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/BounceableInfo.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/BounceableInfo.kt @@ -16,7 +16,7 @@ package com.android.systemui.qs.panels.ui.compose -import android.processor.immutability.Immutable +import androidx.compose.runtime.Stable import com.android.compose.animation.Bounceable import com.android.systemui.qs.panels.shared.model.SizedTile import com.android.systemui.qs.panels.ui.model.GridCell @@ -24,7 +24,7 @@ import com.android.systemui.qs.panels.ui.model.TileGridCell import com.android.systemui.qs.panels.ui.viewmodel.BounceableTileViewModel import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel -@Immutable +@Stable data class BounceableInfo( val bounceable: BounceableTileViewModel, val previousTile: Bounceable?, diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt index 5cb30b999e13..b084f79a5bba 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt @@ -19,8 +19,8 @@ package com.android.systemui.qs.panels.ui.compose import androidx.compose.foundation.layout.Box import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue -import androidx.compose.runtime.key import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier @@ -49,6 +49,8 @@ fun ContentScope.QuickQuickSettings( val squishiness by viewModel.squishinessViewModel.squishiness.collectAsStateWithLifecycle() val scope = rememberCoroutineScope() + val spans by remember(sizedTiles) { derivedStateOf { sizedTiles.fastMap { it.width } } } + DisposableEffect(tiles) { val token = Any() tiles.forEach { it.startListening(token) } @@ -62,26 +64,24 @@ fun ContentScope.QuickQuickSettings( columns = columns, columnSpacing = dimensionResource(R.dimen.qs_tile_margin_horizontal), rowSpacing = dimensionResource(R.dimen.qs_tile_margin_vertical), - spans = sizedTiles.fastMap { it.width }, + spans = spans, modifier = Modifier.sysuiResTag("qqs_tile_layout"), + keys = { sizedTiles[it].tile.spec }, ) { spanIndex -> val it = sizedTiles[spanIndex] val column = cellIndex % columns cellIndex += it.width - key(it.tile.spec) { - Tile( - tile = it.tile, - iconOnly = it.isIcon, - modifier = Modifier.element(it.tile.spec.toElementKey(spanIndex)), - squishiness = { squishiness }, - coroutineScope = scope, - bounceableInfo = bounceables.bounceableInfo(it, spanIndex, column, columns), - tileHapticsViewModelFactoryProvider = - viewModel.tileHapticsViewModelFactoryProvider, - // There should be no QuickQuickSettings when the details view is enabled. - detailsViewModel = null, - ) - } + Tile( + tile = it.tile, + iconOnly = it.isIcon, + modifier = Modifier.element(it.tile.spec.toElementKey(spanIndex)), + squishiness = { squishiness }, + coroutineScope = scope, + bounceableInfo = bounceables.bounceableInfo(it, spanIndex, column, columns), + tileHapticsViewModelFactoryProvider = viewModel.tileHapticsViewModelFactoryProvider, + // There should be no QuickQuickSettings when the details view is enabled. + detailsViewModel = null, + ) } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileDetails.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileDetails.kt index 1bfbbe1964dc..30fb50db82a2 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileDetails.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileDetails.kt @@ -38,12 +38,11 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.em import com.android.systemui.qs.flags.QsDetailedView import com.android.systemui.qs.panels.ui.viewmodel.DetailsViewModel @Composable -fun TileDetails(detailsViewModel: DetailsViewModel) { +fun TileDetails(modifier: Modifier = Modifier, detailsViewModel: DetailsViewModel) { if (!QsDetailedView.isEnabled) { throw IllegalStateException("QsDetailedView should be enabled") @@ -54,10 +53,11 @@ fun TileDetails(detailsViewModel: DetailsViewModel) { DisposableEffect(Unit) { onDispose { detailsViewModel.closeDetailedView() } } Column( - modifier = Modifier - .fillMaxWidth() - // The height of the details view is TBD. - .fillMaxHeight() + modifier = + modifier + .fillMaxWidth() + // The height of the details view is TBD. + .fillMaxHeight() ) { CompositionLocalProvider( value = LocalContentColor provides MaterialTheme.colorScheme.onSurfaceVariant @@ -65,15 +65,14 @@ fun TileDetails(detailsViewModel: DetailsViewModel) { Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically + verticalAlignment = Alignment.CenterVertically, ) { - IconButton( onClick = { detailsViewModel.closeDetailedView() }, - modifier = Modifier - .align(Alignment.CenterVertically) - .height(TileDetailsDefaults.IconHeight) - .padding(start = TileDetailsDefaults.IconPadding), + modifier = + Modifier.align(Alignment.CenterVertically) + .height(TileDetailsDefaults.IconHeight) + .padding(start = TileDetailsDefaults.IconPadding), ) { Icon( imageVector = Icons.AutoMirrored.Filled.ArrowBack, @@ -83,17 +82,16 @@ fun TileDetails(detailsViewModel: DetailsViewModel) { } Text( text = tileDetailedViewModel.getTitle(), - modifier = Modifier - .align(Alignment.CenterVertically), + modifier = Modifier.align(Alignment.CenterVertically), textAlign = TextAlign.Center, - style = MaterialTheme.typography.titleLarge + style = MaterialTheme.typography.titleLarge, ) IconButton( onClick = { tileDetailedViewModel.clickOnSettingsButton() }, - modifier = Modifier - .align(Alignment.CenterVertically) - .height(TileDetailsDefaults.IconHeight) - .padding(end = TileDetailsDefaults.IconPadding), + modifier = + Modifier.align(Alignment.CenterVertically) + .height(TileDetailsDefaults.IconHeight) + .padding(end = TileDetailsDefaults.IconPadding), ) { Icon( imageVector = Icons.Default.Settings, @@ -104,11 +102,9 @@ fun TileDetails(detailsViewModel: DetailsViewModel) { } Text( text = tileDetailedViewModel.getSubTitle(), - modifier = Modifier - .fillMaxWidth(), + modifier = Modifier.fillMaxWidth(), textAlign = TextAlign.Center, - style = MaterialTheme.typography.titleSmall - + style = MaterialTheme.typography.titleSmall, ) } tileDetailedViewModel.GetContentView() 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 d2ee126ace91..2cccaddc02a8 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 @@ -498,11 +498,9 @@ fun gridHeight(rows: Int, tileHeight: Dp, tilePadding: Dp, gridPadding: Dp): Dp return ((tileHeight + tilePadding) * rows) + gridPadding * 2 } -private fun GridCell.key(index: Int, dragAndDropState: DragAndDropState): Any { +private fun GridCell.key(index: Int): Any { return when (this) { - is TileGridCell -> { - if (dragAndDropState.isMoving(tile.tileSpec)) index else key - } + is TileGridCell -> key is SpacerGridCell -> index } } @@ -510,10 +508,13 @@ private fun GridCell.key(index: Int, dragAndDropState: DragAndDropState): Any { /** * Adds a list of [GridCell] to the lazy grid * - * @param cells the pairs of [GridCell] to [AnimatableTileViewModel] + * @param cells the pairs of [GridCell] to [BounceableTileViewModel] + * @param columns the number of columns of this tile grid * @param dragAndDropState the [DragAndDropState] for this grid * @param selectionState the [MutableSelectionState] for this grid - * @param onToggleSize the callback when a tile's size is toggled + * @param coroutineScope the [CoroutineScope] to be used for the tiles + * @param largeTilesSpan the width used for large tiles + * @param onResize the callback when a tile has a new [ResizeOperation] */ fun LazyGridScope.EditTiles( cells: List<Pair<GridCell, BounceableTileViewModel>>, @@ -526,7 +527,7 @@ fun LazyGridScope.EditTiles( ) { items( count = cells.size, - key = { cells[it].first.key(it, dragAndDropState) }, + key = { cells[it].first.key(it) }, span = { cells[it].first.span }, contentType = { TileType }, ) { index -> @@ -536,13 +537,12 @@ fun LazyGridScope.EditTiles( // If the tile is being moved, replace it with a visible spacer SpacerGridCell( Modifier.background( - color = - MaterialTheme.colorScheme.secondary.copy( - alpha = EditModeTileDefaults.PLACEHOLDER_ALPHA - ), - shape = RoundedCornerShape(InactiveCornerRadius), - ) - .animateItem() + color = + MaterialTheme.colorScheme.secondary.copy( + alpha = EditModeTileDefaults.PLACEHOLDER_ALPHA + ), + shape = RoundedCornerShape(InactiveCornerRadius), + ) ) } else { TileGridCell( diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt index 4432d336237f..cc4c3af1dc63 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt @@ -18,8 +18,8 @@ package com.android.systemui.qs.panels.ui.compose.infinitegrid import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue -import androidx.compose.runtime.key import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier @@ -86,27 +86,28 @@ constructor( val scope = rememberCoroutineScope() var cellIndex = 0 + val spans by remember(sizedTiles) { derivedStateOf { sizedTiles.fastMap { it.width } } } + VerticalSpannedGrid( columns = columns, columnSpacing = dimensionResource(R.dimen.qs_tile_margin_horizontal), rowSpacing = dimensionResource(R.dimen.qs_tile_margin_vertical), - spans = sizedTiles.fastMap { it.width }, + spans = spans, + keys = { sizedTiles[it].tile.spec }, ) { spanIndex -> val it = sizedTiles[spanIndex] val column = cellIndex % columns cellIndex += it.width - key(it.tile.spec) { - Tile( - tile = it.tile, - iconOnly = iconTilesViewModel.isIconTile(it.tile.spec), - modifier = Modifier.element(it.tile.spec.toElementKey(spanIndex)), - squishiness = { squishiness }, - tileHapticsViewModelFactoryProvider = tileHapticsViewModelFactoryProvider, - coroutineScope = scope, - bounceableInfo = bounceables.bounceableInfo(it, spanIndex, column, columns), - detailsViewModel = detailsViewModel, - ) - } + Tile( + tile = it.tile, + iconOnly = iconTilesViewModel.isIconTile(it.tile.spec), + modifier = Modifier.element(it.tile.spec.toElementKey(spanIndex)), + squishiness = { squishiness }, + tileHapticsViewModelFactoryProvider = tileHapticsViewModelFactoryProvider, + coroutineScope = scope, + bounceableInfo = bounceables.bounceableInfo(it, spanIndex, column, columns), + detailsViewModel = detailsViewModel, + ) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt index 16c27223a471..8a627c452081 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt @@ -18,6 +18,7 @@ package com.android.systemui.qs.pipeline.shared import android.content.ComponentName import android.text.TextUtils +import androidx.compose.runtime.Stable import com.android.systemui.qs.external.CustomTile /** @@ -34,6 +35,7 @@ sealed class TileSpec private constructor(open val spec: String) { data object Invalid : TileSpec("") /** Container for the spec of a tile provided by SystemUI. */ + @Stable data class PlatformTileSpec internal constructor(override val spec: String) : TileSpec(spec) { override fun toString(): String { return "P($spec)" @@ -45,6 +47,7 @@ sealed class TileSpec private constructor(open val spec: String) { * * [componentName] indicates the associated `TileService`. */ + @Stable data class CustomTileSpec internal constructor(override val spec: String, val componentName: ComponentName) : TileSpec(spec) { 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 0051bf5de7f2..ad5dd27f07c2 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt @@ -35,12 +35,14 @@ import com.android.systemui.modes.shared.ModesUiIcons import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.FalsingManager import com.android.systemui.plugins.qs.QSTile +import com.android.systemui.plugins.qs.TileDetailsViewModel import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.asQSTileIcon import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSTileImpl +import com.android.systemui.qs.tiles.dialog.ModesDetailsViewModel import com.android.systemui.qs.tiles.impl.modes.domain.interactor.ModesTileDataInteractor import com.android.systemui.qs.tiles.impl.modes.domain.interactor.ModesTileUserActionInteractor import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesTileModel @@ -48,6 +50,7 @@ import com.android.systemui.qs.tiles.impl.modes.ui.ModesTileMapper import com.android.systemui.qs.tiles.viewmodel.QSTileConfigProvider import com.android.systemui.qs.tiles.viewmodel.QSTileState import com.android.systemui.res.R +import com.android.systemui.statusbar.policy.ui.dialog.viewmodel.ModesDialogViewModel import javax.inject.Inject import kotlinx.coroutines.runBlocking @@ -67,6 +70,7 @@ constructor( private val dataInteractor: ModesTileDataInteractor, private val tileMapper: ModesTileMapper, private val userActionInteractor: ModesTileUserActionInteractor, + private val modesDialogViewModel: ModesDialogViewModel, ) : QSTileImpl<QSTile.State>( host, @@ -114,6 +118,13 @@ constructor( userActionInteractor.handleToggleClick(model) } + override fun getDetailsViewModel(): TileDetailsViewModel { + return ModesDetailsViewModel( + onSettingsClick = { userActionInteractor.handleLongClick(null) }, + viewModel = modesDialogViewModel, + ) + } + override fun getLongClickIntent(): Intent = userActionInteractor.longClickIntent @VisibleForTesting diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/ModesDetailsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/ModesDetailsViewModel.kt new file mode 100644 index 000000000000..511597d05d37 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/ModesDetailsViewModel.kt @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.dialog + +import androidx.compose.runtime.Composable +import com.android.systemui.plugins.qs.TileDetailsViewModel +import com.android.systemui.statusbar.policy.ui.dialog.composable.ModeTileGrid +import com.android.systemui.statusbar.policy.ui.dialog.viewmodel.ModesDialogViewModel + +/** The view model used for the modes details view in the Quick Settings */ +class ModesDetailsViewModel( + private val onSettingsClick: () -> Unit, + private val viewModel: ModesDialogViewModel, +) : TileDetailsViewModel() { + @Composable + override fun GetContentView() { + // TODO(b/378513940): Finish implementing this function. + ModeTileGrid(viewModel = viewModel) + } + + override fun clickOnSettingsButton() { + onSettingsClick() + } + + override fun getTitle(): String { + // TODO(b/388321032): Replace this string with a string in a translatable xml file. + return "Modes" + } + + override fun getSubTitle(): String { + // TODO(b/388321032): Replace this string with a string in a translatable xml file. + return "Silences interruptions from people and apps in different circumstances" + } +} 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 5ce7f0d039c8..b5da044b886a 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 @@ -54,7 +54,7 @@ constructor( handleToggleClick(input.data) } is QSTileUserAction.LongClick -> { - qsTileIntentUserInputHandler.handle(action.expandable, longClickIntent) + handleLongClick(action.expandable) } } } @@ -95,6 +95,10 @@ constructor( } } + fun handleLongClick(expandable: Expandable?) { + qsTileIntentUserInputHandler.handle(expandable, longClickIntent) + } + companion object { const val TAG = "ModesTileUserActionInteractor" } 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 91d907952bc6..c7db04a6b7b2 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 @@ -16,6 +16,7 @@ package com.android.systemui.qs.ui.viewmodel +import androidx.compose.runtime.getValue import com.android.systemui.brightness.ui.viewmodel.BrightnessSliderViewModel import com.android.systemui.lifecycle.ExclusiveActivatable import com.android.systemui.qs.panels.ui.viewmodel.DetailsViewModel @@ -23,6 +24,7 @@ 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 com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -35,6 +37,7 @@ class QuickSettingsContainerViewModel constructor( brightnessSliderViewModelFactory: BrightnessSliderViewModel.Factory, quickQuickSettingsViewModelFactory: QuickQuickSettingsViewModel.Factory, + shadeHeaderViewModelFactory: ShadeHeaderViewModel.Factory, @Assisted supportsBrightnessMirroring: Boolean, val tileGridViewModel: TileGridViewModel, val editModeViewModel: EditModeViewModel, @@ -47,10 +50,13 @@ constructor( val quickQuickSettingsViewModel = quickQuickSettingsViewModelFactory.create() + val shadeHeaderViewModel = shadeHeaderViewModelFactory.create() + override suspend fun onActivated(): Nothing { coroutineScope { launch { brightnessSliderViewModel.activate() } launch { quickQuickSettingsViewModel.activate() } + launch { shadeHeaderViewModel.activate() } awaitCancellation() } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModel.kt index a108bc2c4a8c..d9df1ef36847 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModel.kt @@ -52,6 +52,13 @@ constructor( private val hydrator = Hydrator("QuickSettingsContainerViewModel.hydrator") + val isShadeLayoutWide: Boolean by + hydrator.hydratedStateOf( + traceName = "isShadeLayoutWide", + initialValue = shadeInteractor.isShadeLayoutWide.value, + source = shadeInteractor.isShadeLayoutWide, + ) + val showHeader: Boolean by hydrator.hydratedStateOf( traceName = "showHeader", @@ -61,6 +68,13 @@ constructor( val quickSettingsContainerViewModel = quickSettingsContainerViewModelFactory.create(false) + val showQuickSettingsOverlayHeader: Boolean by + hydrator.hydratedStateOf( + traceName = "showQuickSettingsOverlayHeader", + initialValue = shadeInteractor.isShadeLayoutWide.value, + source = shadeInteractor.isShadeLayoutWide, + ) + override suspend fun onActivated(): Nothing { coroutineScope { launch { hydrator.activate() } diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index adf9eb44e162..60c2cca1ae8b 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -61,7 +61,6 @@ import android.os.IBinder; import android.os.IRemoteCallback; import android.os.Looper; import android.os.PatternMatcher; -import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; @@ -100,6 +99,7 @@ import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.navigationbar.views.NavigationBar; import com.android.systemui.navigationbar.views.NavigationBarView; import com.android.systemui.navigationbar.views.buttons.KeyButtonView; +import com.android.systemui.process.ProcessWrapper; import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener; import com.android.systemui.scene.domain.interactor.SceneInteractor; import com.android.systemui.scene.shared.flag.SceneContainerFlag; @@ -675,11 +675,12 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis DumpManager dumpManager, Optional<UnfoldTransitionProgressForwarder> unfoldTransitionProgressForwarder, BroadcastDispatcher broadcastDispatcher, - Optional<BackAnimation> backAnimation + Optional<BackAnimation> backAnimation, + ProcessWrapper processWrapper ) { // b/241601880: This component should only be running for primary users or // secondaryUsers when visibleBackgroundUsers are supported. - boolean isSystemUser = Process.myUserHandle().equals(UserHandle.SYSTEM); + boolean isSystemUser = processWrapper.isSystemUser(); boolean isVisibleBackgroundUser = userManager.isVisibleBackgroundUsersSupported() && !userManager.isUserForeground(); if (!isSystemUser && isVisibleBackgroundUser) { diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/resolver/HomeSceneFamilyResolver.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/resolver/HomeSceneFamilyResolver.kt index 2a0a22f32601..6e79d568761f 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/resolver/HomeSceneFamilyResolver.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/resolver/HomeSceneFamilyResolver.kt @@ -18,6 +18,7 @@ package com.android.systemui.scene.domain.resolver +import android.util.Log import com.android.compose.animation.scene.SceneKey import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application @@ -85,8 +86,8 @@ constructor( isUnlocked: Boolean, isDreamingWithOverlay: Boolean, isAbleToDream: Boolean, - ): SceneKey = - when { + ): SceneKey { + val result = when { // Dream can run even if Keyguard is disabled, thus it has the highest priority here. isDreamingWithOverlay && isAbleToDream -> Scenes.Dream !isKeyguardEnabled -> Scenes.Gone @@ -95,8 +96,21 @@ constructor( !isUnlocked -> Scenes.Lockscreen else -> Scenes.Gone } + Log.d(TAG, "homeScene emitting $result, values:") + Log.d(TAG, " isKeyguardEnabled=$isKeyguardEnabled") + Log.d(TAG, " canSwipeToEnter=$canSwipeToEnter") + Log.d(TAG, " isDeviceEntered=$isDeviceEntered" ) + Log.d(TAG, " isUnlocked=$isUnlocked") + Log.d(TAG, " isDreamingWithOverlay=$isDreamingWithOverlay") + Log.d(TAG, " isAbleToDream=$isAbleToDream") + Log.d(TAG, "") + return result + } companion object { + + private const val TAG = "HomeSceneFamilyResolver" + val homeScenes = setOf( Scenes.Gone, diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt index 8657c1723507..63c10c9b971a 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt @@ -564,7 +564,7 @@ constructor( .collect { switchToScene( targetSceneKey = Scenes.Lockscreen, - loggingReason = "device became non-interactive", + loggingReason = "device became non-interactive (SceneContainerStartable)", ) } } @@ -945,10 +945,6 @@ constructor( override fun onTransitionAnimationEnd() { sceneInteractor.onTransitionAnimationEnd() } - - override fun onTransitionAnimationCancelled() { - sceneInteractor.onTransitionAnimationCancelled() - } } ) } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.kt index 08214c456897..f5c605211520 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.kt @@ -35,7 +35,6 @@ import android.util.Log import android.view.Display import android.view.ScrollCaptureResponse import android.view.ViewRootImpl.ActivityConfigCallback -import android.view.WindowManager import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE import android.widget.Toast import android.window.WindowContext @@ -218,9 +217,7 @@ internal constructor( window.setFocusable(true) viewProxy.requestFocus() - if (screenshot.type != WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE) { - enqueueScrollCaptureRequest(requestId, screenshot.userHandle) - } + enqueueScrollCaptureRequest(requestId, screenshot.userHandle) window.attachWindow() diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotCrossProfileService.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotCrossProfileService.kt index 2e6c7567259f..d82a8bd0ddcd 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotCrossProfileService.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotCrossProfileService.kt @@ -31,7 +31,7 @@ class ScreenshotCrossProfileService : Service() { private val mBinder: IBinder = object : ICrossProfileService.Stub() { - override fun launchIntent(intent: Intent, bundle: Bundle) { + override fun launchIntent(intent: Intent, bundle: Bundle?) { startActivity(intent, bundle) } } diff --git a/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java b/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java index 49f3cfc4ceaf..917869a66ca4 100644 --- a/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java +++ b/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java @@ -27,6 +27,8 @@ import android.graphics.PorterDuff; import android.graphics.PorterDuff.Mode; import android.graphics.PorterDuffColorFilter; import android.graphics.Rect; +import android.graphics.RenderEffect; +import android.graphics.Shader; import android.graphics.drawable.Drawable; import android.os.Looper; import android.util.AttributeSet; @@ -373,4 +375,18 @@ public class ScrimView extends View { ((ScrimDrawable) mDrawable).setRoundedCorners(radius); } } + + /** + * Blur the view with the specific blur radius or clear any blurs if the radius is 0 + */ + public void setBlurRadius(float blurRadius) { + if (blurRadius > 0) { + setRenderEffect(RenderEffect.createBlurEffect( + blurRadius, + blurRadius, + Shader.TileMode.CLAMP)); + } else { + setRenderEffect(null); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 19bf4c0bab81..a1b4def09ba9 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -109,7 +109,6 @@ import com.android.systemui.keyguard.shared.model.Edge; import com.android.systemui.keyguard.shared.model.TransitionState; import com.android.systemui.keyguard.shared.model.TransitionStep; import com.android.systemui.keyguard.ui.binder.KeyguardLongPressViewBinder; -import com.android.systemui.keyguard.ui.transitions.BlurConfig; import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel; import com.android.systemui.keyguard.ui.viewmodel.KeyguardTouchHandlingViewModel; import com.android.systemui.media.controls.domain.pipeline.MediaDataManager; @@ -915,8 +914,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump if (!com.android.systemui.Flags.bouncerUiRevamp()) return; if (isBouncerShowing && isExpanded()) { - float shadeBlurEffect = BlurConfig.maxBlurRadiusToNotificationPanelBlurRadius( - mDepthController.getMaxBlurRadiusPx()); + float shadeBlurEffect = mDepthController.getMaxBlurRadiusPx(); mView.setRenderEffect(RenderEffect.createBlurEffect( shadeBlurEffect, shadeBlurEffect, diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java index 48bbb0407ee3..48e374746bf5 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java @@ -17,6 +17,7 @@ package com.android.systemui.shade; import static android.os.Trace.TRACE_TAG_APP; +import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED; import static com.android.systemui.Flags.enableViewCaptureTracing; import static com.android.systemui.statusbar.phone.CentralSurfaces.DEBUG; @@ -49,10 +50,12 @@ import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.view.Window; import android.view.WindowInsetsController; +import android.view.accessibility.AccessibilityEvent; import com.android.app.viewcapture.ViewCaptureFactory; import com.android.internal.view.FloatingActionMode; import com.android.internal.widget.floatingtoolbar.FloatingToolbar; +import com.android.systemui.Flags; import com.android.systemui.scene.ui.view.WindowRootView; import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround; import com.android.systemui.statusbar.phone.ConfigurationForwarder; @@ -77,6 +80,8 @@ public class NotificationShadeWindowView extends WindowRootView { private SafeCloseable mViewCaptureCloseable; + private boolean mAnimatingContentLaunch = false; + public NotificationShadeWindowView(Context context, AttributeSet attrs) { super(context, attrs); setMotionEventSplittingEnabled(false); @@ -188,6 +193,22 @@ public class NotificationShadeWindowView extends WindowRootView { } } + @Override + public boolean requestSendAccessibilityEvent(View child, AccessibilityEvent event) { + if (Flags.shadeLaunchAccessibility() && mAnimatingContentLaunch + && event.getEventType() == TYPE_VIEW_ACCESSIBILITY_FOCUSED) { + // Block accessibility focus events during launch animations to avoid stray TalkBack + // announcements. + return false; + } + + return super.requestSendAccessibilityEvent(child, event); + } + + public void setAnimatingContentLaunch(boolean animating) { + mAnimatingContentLaunch = animating; + } + public void setConfigurationForwarder(ConfigurationForwarder configurationForwarder) { ShadeWindowGoesAround.isUnexpectedlyInLegacyMode(); mConfigurationForwarder = configurationForwarder; diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java index e5dcd2338b9d..ffec8f284c48 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java @@ -16,10 +16,12 @@ package com.android.systemui.shade; +import static com.android.systemui.Flags.shadeLaunchAccessibility; import static com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING; import static com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN; import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow; +import static com.android.systemui.util.kotlin.JavaAdapterKt.combineFlows; import android.app.StatusBarManager; import android.util.Log; @@ -59,6 +61,7 @@ import com.android.systemui.res.R; import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirrorShowingInteractor; import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor; +import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor; import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround; import com.android.systemui.shared.animation.DisableSubpixelTextTransitionListener; import com.android.systemui.statusbar.BlurUtils; @@ -85,6 +88,7 @@ import com.android.systemui.window.ui.WindowRootViewBinder; import com.android.systemui.window.ui.viewmodel.WindowRootViewModel; import kotlinx.coroutines.ExperimentalCoroutinesApi; +import kotlinx.coroutines.flow.Flow; import java.io.PrintWriter; import java.util.Optional; @@ -174,6 +178,7 @@ public class NotificationShadeWindowViewController implements Dumpable { NotificationShadeDepthController depthController, NotificationShadeWindowView notificationShadeWindowView, ShadeViewController shadeViewController, + ShadeAnimationInteractor shadeAnimationInteractor, PanelExpansionInteractor panelExpansionInteractor, ShadeExpansionStateManager shadeExpansionStateManager, NotificationStackScrollLayoutController notificationStackScrollLayoutController, @@ -238,9 +243,17 @@ public class NotificationShadeWindowViewController implements Dumpable { collectFlow(mView, keyguardTransitionInteractor.transition( Edge.create(LOCKSCREEN, DREAMING)), mLockscreenToDreamingTransition); + Flow<Boolean> isLaunchAnimationRunning = + shadeLaunchAccessibility() + ? combineFlows( + notificationLaunchAnimationInteractor.isLaunchAnimationRunning(), + shadeAnimationInteractor.isLaunchingActivity(), + (notificationLaunching, shadeLaunching) -> + notificationLaunching || shadeLaunching) + : notificationLaunchAnimationInteractor.isLaunchAnimationRunning(); collectFlow( mView, - notificationLaunchAnimationInteractor.isLaunchAnimationRunning(), + isLaunchAnimationRunning, this::setExpandAnimationRunning); if (QSComposeFragment.isEnabled()) { collectFlow(mView, @@ -726,9 +739,17 @@ public class NotificationShadeWindowViewController implements Dumpable { if (ActivityTransitionAnimator.DEBUG_TRANSITION_ANIMATION) { Log.d(TAG, "Setting mExpandAnimationRunning=" + running); } + if (running) { mLaunchAnimationTimeout = mClock.uptimeMillis() + 5000; } + + if (shadeLaunchAccessibility()) { + // The view needs to know when an animation is ongoing so it can intercept + // unnecessary accessibility events. + mView.setAnimatingContentLaunch(running); + } + mExpandAnimationRunning = running; mNotificationShadeWindowController.setLaunchingActivity(mExpandAnimationRunning); } diff --git a/packages/SystemUI/src/com/android/systemui/shade/QsBatteryModeController.kt b/packages/SystemUI/src/com/android/systemui/shade/QsBatteryModeController.kt index 7a70966c2b12..b15615a83698 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/QsBatteryModeController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/QsBatteryModeController.kt @@ -5,6 +5,7 @@ import android.view.DisplayCutout import com.android.systemui.battery.BatteryMeterView import com.android.systemui.res.R import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStore +import com.android.systemui.statusbar.layout.StatusBarContentInsetsProvider import javax.inject.Inject /** @@ -15,11 +16,9 @@ class QsBatteryModeController @Inject constructor( @ShadeDisplayAware private val context: Context, - insetsProviderStore: StatusBarContentInsetsProviderStore, + private val insetsProviderStore: StatusBarContentInsetsProviderStore, ) { - private val insetsProvider = insetsProviderStore.defaultDisplay - private companion object { // MotionLayout frames are in [0, 100]. Where 0 and 100 are reserved for start and end // frames. @@ -43,17 +42,19 @@ constructor( * animation. */ @BatteryMeterView.BatteryPercentMode - fun getBatteryMode(cutout: DisplayCutout?, qsExpandedFraction: Float): Int? = - when { + fun getBatteryMode(cutout: DisplayCutout?, qsExpandedFraction: Float): Int? { + val insetsProvider = insetsProviderStore.forDisplay(context.displayId) + return when { qsExpandedFraction > fadeInStartFraction -> BatteryMeterView.MODE_ESTIMATE - qsExpandedFraction < fadeOutCompleteFraction -> - if (hasCenterCutout(cutout)) { + insetsProvider != null && qsExpandedFraction < fadeOutCompleteFraction -> + if (hasCenterCutout(cutout, insetsProvider)) { BatteryMeterView.MODE_ON } else { BatteryMeterView.MODE_ESTIMATE } else -> null } + } fun updateResources() { fadeInStartFraction = @@ -64,7 +65,10 @@ constructor( MOTION_LAYOUT_MAX_FRAME.toFloat() } - private fun hasCenterCutout(cutout: DisplayCutout?): Boolean = + private fun hasCenterCutout( + cutout: DisplayCutout?, + insetsProvider: StatusBarContentInsetsProvider, + ): Boolean = cutout?.let { !insetsProvider.currentRotationHasCornerCutout() && !it.boundingRectTop.isEmpty } ?: false diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt index 747642097327..f926d39760fe 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt @@ -36,7 +36,6 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.res.R import com.android.systemui.scene.ui.view.WindowRootView -import com.android.systemui.shade.data.repository.MutableShadeDisplaysRepository import com.android.systemui.shade.data.repository.ShadeDisplaysRepository import com.android.systemui.shade.data.repository.ShadeDisplaysRepositoryImpl import com.android.systemui.shade.display.ShadeDisplayPolicyModule @@ -211,15 +210,6 @@ object ShadeDisplayAwareModule { return impl } - @SysUISingleton - @Provides - fun provideMutableShadePositionRepository( - impl: ShadeDisplaysRepositoryImpl - ): MutableShadeDisplaysRepository { - ShadeWindowGoesAround.isUnexpectedlyInLegacyMode() - return impl - } - @Provides @SysUISingleton fun provideShadeDialogContextInteractor( diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayChangeLatencyTracker.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayChangeLatencyTracker.kt index ec9bba7c1f0f..13b540aa54ba 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayChangeLatencyTracker.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayChangeLatencyTracker.kt @@ -16,7 +16,6 @@ package com.android.systemui.shade import android.util.Log -import android.view.Display import com.android.app.tracing.coroutines.TrackTracer import com.android.internal.util.LatencyTracker import com.android.systemui.common.ui.data.repository.ConfigurationRepository @@ -32,11 +31,9 @@ import javax.inject.Inject import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job -import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import kotlinx.coroutines.withTimeout @@ -73,12 +70,7 @@ constructor( /** * We need to keep this always up to date eagerly to avoid delays receiving the new display ID. */ - private val onMovedToDisplayFlow: StateFlow<Int> = - configurationRepository.onMovedToDisplay.stateIn( - bgScope, - SharingStarted.Eagerly, - Display.DEFAULT_DISPLAY, - ) + private val onMovedToDisplayFlow: StateFlow<Int> = configurationRepository.onMovedToDisplay private var previousJob: Job? = null diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt index e8a792c30aa2..d82f8e722744 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt @@ -57,6 +57,7 @@ import com.android.systemui.shade.ShadeHeaderController.Companion.QS_HEADER_CONS import com.android.systemui.shade.ShadeViewProviderModule.Companion.SHADE_HEADER import com.android.systemui.shade.carrier.ShadeCarrierGroup import com.android.systemui.shade.carrier.ShadeCarrierGroupController +import com.android.systemui.shade.data.repository.ShadeDisplaysRepository import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStore import com.android.systemui.statusbar.phone.StatusBarLocation import com.android.systemui.statusbar.phone.StatusIconContainer @@ -90,8 +91,9 @@ constructor( private val statusBarIconController: StatusBarIconController, private val tintedIconManagerFactory: TintedIconManager.Factory, private val privacyIconsController: HeaderPrivacyIconsController, - private val insetsProviderStore: StatusBarContentInsetsProviderStore, + private val statusBarContentInsetsProviderStore: StatusBarContentInsetsProviderStore, @ShadeDisplayAware private val configurationController: ConfigurationController, + private val shadeDisplaysRepository: ShadeDisplaysRepository, private val variableDateViewControllerFactory: VariableDateViewController.Factory, @Named(SHADE_HEADER) private val batteryMeterViewController: BatteryMeterViewController, private val dumpManager: DumpManager, @@ -104,7 +106,9 @@ constructor( private val statusOverlayHoverListenerFactory: StatusOverlayHoverListenerFactory, ) : ViewController<View>(header), Dumpable { - private val insetsProvider = insetsProviderStore.defaultDisplay + private val statusBarContentInsetsProvider + get() = + statusBarContentInsetsProviderStore.forDisplay(shadeDisplaysRepository.displayId.value) companion object { /** IDs for transitions and constraints for the [MotionLayout]. */ @@ -222,10 +226,14 @@ constructor( private val insetListener = View.OnApplyWindowInsetsListener { view, insets -> - updateConstraintsForInsets(view as MotionLayout, insets) - lastInsets = WindowInsets(insets) - - view.onApplyWindowInsets(insets) + val windowInsets = WindowInsets(insets) + if (windowInsets != lastInsets) { + updateConstraintsForInsets(view as MotionLayout, insets) + lastInsets = windowInsets + view.onApplyWindowInsets(insets) + } else { + insets + } } private var singleCarrier = false @@ -414,6 +422,7 @@ constructor( } private fun updateConstraintsForInsets(view: MotionLayout, insets: WindowInsets) { + val insetsProvider = statusBarContentInsetsProvider ?: return val cutout = insets.displayCutout.also { this.cutout = it } val sbInsets: Insets = insetsProvider.getStatusBarContentInsetsForCurrentRotation() @@ -508,6 +517,9 @@ constructor( systemIconsHoverContainer.setOnClickListener(null) systemIconsHoverContainer.isClickable = false } + + lastInsets?.let { updateConstraintsForInsets(header, it) } + header.jumpToState(header.startState) updatePosition() updateScrollY() diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadePrimaryDisplayCommand.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadePrimaryDisplayCommand.kt index 7bfe40c3d811..173da336c62f 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadePrimaryDisplayCommand.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadePrimaryDisplayCommand.kt @@ -20,7 +20,7 @@ import android.provider.Settings.Global.DEVELOPMENT_SHADE_DISPLAY_AWARENESS import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.display.data.repository.DisplayRepository -import com.android.systemui.shade.data.repository.MutableShadeDisplaysRepository +import com.android.systemui.shade.data.repository.ShadeDisplaysRepository import com.android.systemui.shade.display.ShadeDisplayPolicy import com.android.systemui.statusbar.commandline.Command import com.android.systemui.statusbar.commandline.CommandRegistry @@ -35,7 +35,7 @@ constructor( private val globalSettings: GlobalSettings, private val commandRegistry: CommandRegistry, private val displaysRepository: DisplayRepository, - private val positionRepository: MutableShadeDisplaysRepository, + private val positionRepository: ShadeDisplaysRepository, private val policies: Set<@JvmSuppressWildcards ShadeDisplayPolicy>, private val defaultPolicy: ShadeDisplayPolicy, ) : Command, CoreStartable { @@ -103,7 +103,7 @@ constructor( } private fun printPolicies() { - val currentPolicyName = positionRepository.policy.value.name + val currentPolicyName = positionRepository.currentPolicy.name pw.println("Available policies: ") policies.forEach { pw.print(" - ${it.name}") diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/FakeShadeDisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/FakeShadeDisplayRepository.kt index 732d4d1500e7..3513334f2a5c 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/FakeShadeDisplayRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/FakeShadeDisplayRepository.kt @@ -17,6 +17,8 @@ package com.android.systemui.shade.data.repository import android.view.Display +import com.android.systemui.shade.display.FakeShadeDisplayPolicy +import com.android.systemui.shade.display.ShadeDisplayPolicy import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -30,7 +32,6 @@ class FakeShadeDisplayRepository : ShadeDisplaysRepository { override val displayId: StateFlow<Int> get() = _displayId - fun resetDisplayId() { - _displayId.value = Display.DEFAULT_DISPLAY - } + override val currentPolicy: ShadeDisplayPolicy + get() = FakeShadeDisplayPolicy } diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepository.kt index af48231e0a99..f959f7fe0c31 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepository.kt @@ -20,14 +20,18 @@ import android.provider.Settings.Global.DEVELOPMENT_SHADE_DISPLAY_AWARENESS import android.view.Display import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.keyguard.data.repository.KeyguardRepository +import com.android.systemui.shade.ShadeOnDefaultDisplayWhenLocked import com.android.systemui.shade.display.ShadeDisplayPolicy import com.android.systemui.util.settings.GlobalSettings import com.android.systemui.util.settings.SettingsProxyExt.observerFlow import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map @@ -38,12 +42,8 @@ import kotlinx.coroutines.flow.stateIn interface ShadeDisplaysRepository { /** ID of the display which currently hosts the shade */ val displayId: StateFlow<Int> -} - -/** Allows to change the policy that determines in which display the Shade window is visible. */ -interface MutableShadeDisplaysRepository : ShadeDisplaysRepository { - /** Updates the policy to select where the shade is visible. */ - val policy: StateFlow<ShadeDisplayPolicy> + /** The current policy set. */ + val currentPolicy: ShadeDisplayPolicy } /** Keeps the policy and propagates the display id for the shade from it. */ @@ -56,9 +56,11 @@ constructor( defaultPolicy: ShadeDisplayPolicy, @Background bgScope: CoroutineScope, policies: Set<@JvmSuppressWildcards ShadeDisplayPolicy>, -) : MutableShadeDisplaysRepository { + @ShadeOnDefaultDisplayWhenLocked private val shadeOnDefaultDisplayWhenLocked: Boolean, + keyguardRepository: KeyguardRepository, +) : ShadeDisplaysRepository { - override val policy: StateFlow<ShadeDisplayPolicy> = + private val policy: StateFlow<ShadeDisplayPolicy> = globalSettings .observerFlow(DEVELOPMENT_SHADE_DISPLAY_AWARENESS) .onStart { emit(Unit) } @@ -71,10 +73,32 @@ constructor( return@map defaultPolicy } .distinctUntilChanged() - .stateIn(bgScope, SharingStarted.WhileSubscribed(), defaultPolicy) + .stateIn(bgScope, SharingStarted.Eagerly, defaultPolicy) + + private val displayIdFromPolicy: Flow<Int> = policy.flatMapLatest { it.displayId } + + private val keyguardAwareDisplayPolicy: Flow<Int> = + if (!shadeOnDefaultDisplayWhenLocked) { + displayIdFromPolicy + } else { + keyguardRepository.isKeyguardShowing.combine(displayIdFromPolicy) { + isKeyguardShowing, + currentDisplayId -> + if (isKeyguardShowing) { + Display.DEFAULT_DISPLAY + } else { + currentDisplayId + } + } + } + + override val currentPolicy: ShadeDisplayPolicy + get() = policy.value override val displayId: StateFlow<Int> = - policy - .flatMapLatest { it.displayId } - .stateIn(bgScope, SharingStarted.WhileSubscribed(), Display.DEFAULT_DISPLAY) + keyguardAwareDisplayPolicy.stateIn( + bgScope, + SharingStarted.WhileSubscribed(), + Display.DEFAULT_DISPLAY, + ) } diff --git a/packages/SystemUI/src/com/android/systemui/shade/display/FakeShadeDisplayPolicy.kt b/packages/SystemUI/src/com/android/systemui/shade/display/FakeShadeDisplayPolicy.kt new file mode 100644 index 000000000000..e010bd6f9880 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/shade/display/FakeShadeDisplayPolicy.kt @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow + +/** Used only for testing. */ +object FakeShadeDisplayPolicy : ShadeDisplayPolicy { + override val name: String + get() = "fake_shade_policy" + + override val displayId: StateFlow<Int> + get() = _displayId + + private val _displayId = MutableStateFlow(Display.DEFAULT_DISPLAY) + + fun setDisplayId(displayId: Int) { + _displayId.value = displayId + } +} diff --git a/packages/SystemUI/src/com/android/systemui/shade/display/FocusShadeDisplayPolicy.kt b/packages/SystemUI/src/com/android/systemui/shade/display/FocusShadeDisplayPolicy.kt new file mode 100644 index 000000000000..7d8f7c59ad66 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/shade/display/FocusShadeDisplayPolicy.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.shade.display + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.display.data.repository.FocusedDisplayRepository +import javax.inject.Inject +import kotlinx.coroutines.flow.StateFlow + +/** Policy that just emits the [FocusedDisplayRepository] display id. */ +@SysUISingleton +class FocusShadeDisplayPolicy +@Inject +constructor(private val focusedDisplayRepository: FocusedDisplayRepository) : ShadeDisplayPolicy { + override val name: String + get() = "focused_display" + + override val displayId: StateFlow<Int> + get() = focusedDisplayRepository.focusedDisplayId +} 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 d53f9f7ec595..677e41a47afe 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/display/ShadeDisplayPolicy.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/display/ShadeDisplayPolicy.kt @@ -19,7 +19,8 @@ package com.android.systemui.shade.display import com.android.systemui.shade.domain.interactor.ShadeExpandedStateInteractor.ShadeElement import dagger.Binds import dagger.Module -import dagger.multibindings.IntoSet +import dagger.Provides +import dagger.multibindings.ElementsIntoSet import kotlinx.coroutines.flow.StateFlow /** Describes the display the shade should be shown in. */ @@ -53,27 +54,25 @@ interface ShadeExpansionIntent { fun consumeExpansionIntent(): ShadeElement? } -@Module +@Module(includes = [AllShadeDisplayPoliciesModule::class]) interface ShadeDisplayPolicyModule { - @Binds fun provideDefaultPolicy(impl: StatusBarTouchShadeDisplayPolicy): ShadeDisplayPolicy + @Binds fun provideDefaultPolicy(impl: DefaultDisplayShadePolicy): ShadeDisplayPolicy @Binds fun provideShadeExpansionIntent(impl: StatusBarTouchShadeDisplayPolicy): ShadeExpansionIntent +} - @IntoSet - @Binds - fun provideDefaultDisplayPolicyToSet(impl: DefaultDisplayShadePolicy): ShadeDisplayPolicy - - @IntoSet - @Binds - fun provideAnyExternalShadeDisplayPolicyToSet( - impl: AnyExternalShadeDisplayPolicy - ): ShadeDisplayPolicy - - @Binds - @IntoSet - fun provideStatusBarTouchShadeDisplayPolicy( - impl: StatusBarTouchShadeDisplayPolicy - ): ShadeDisplayPolicy +@Module +internal object AllShadeDisplayPoliciesModule { + @Provides + @ElementsIntoSet + fun provideShadeDisplayPolicies( + defaultPolicy: DefaultDisplayShadePolicy, + externalPolicy: AnyExternalShadeDisplayPolicy, + statusBarPolicy: StatusBarTouchShadeDisplayPolicy, + focusPolicy: FocusShadeDisplayPolicy, + ): Set<ShadeDisplayPolicy> { + return setOf(defaultPolicy, externalPolicy, statusBarPolicy, focusPolicy) + } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicy.kt b/packages/SystemUI/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicy.kt index 91020aa7bdb0..b155ada87efd 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicy.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicy.kt @@ -23,8 +23,6 @@ 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.keyguard.data.repository.KeyguardRepository -import com.android.systemui.shade.ShadeOnDefaultDisplayWhenLocked import com.android.systemui.shade.domain.interactor.NotificationShadeElement import com.android.systemui.shade.domain.interactor.QSShadeElement import com.android.systemui.shade.domain.interactor.ShadeExpandedStateInteractor.ShadeElement @@ -38,13 +36,10 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.stateIn /** * Moves the shade on the last display that received a status bar touch. @@ -57,9 +52,7 @@ class StatusBarTouchShadeDisplayPolicy @Inject constructor( displayRepository: DisplayRepository, - keyguardRepository: KeyguardRepository, @Background private val backgroundScope: CoroutineScope, - @ShadeOnDefaultDisplayWhenLocked private val shadeOnDefaultDisplayWhenLocked: Boolean, private val shadeInteractor: Lazy<ShadeInteractor>, private val qsShadeElement: Lazy<QSShadeElement>, private val notificationElement: Lazy<NotificationShadeElement>, @@ -72,20 +65,7 @@ constructor( private var latestIntent = AtomicReference<ShadeElement?>() private var timeoutJob: Job? = null - override val displayId: StateFlow<Int> = - if (shadeOnDefaultDisplayWhenLocked) { - keyguardRepository.isKeyguardShowing - .combine(currentDisplayId) { isKeyguardShowing, currentDisplayId -> - if (isKeyguardShowing) { - Display.DEFAULT_DISPLAY - } else { - currentDisplayId - } - } - .stateIn(backgroundScope, SharingStarted.WhileSubscribed(), currentDisplayId.value) - } else { - currentDisplayId - } + override val displayId: StateFlow<Int> = currentDisplayId private var removalListener: Job? = null diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PrivacyChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PrivacyChipInteractor.kt index 4c6c31809275..0caeead17cd5 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PrivacyChipInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PrivacyChipInteractor.kt @@ -41,6 +41,7 @@ constructor( private val privacyDialogController: PrivacyDialogController, private val privacyDialogControllerV2: PrivacyDialogControllerV2, private val deviceProvisionedController: DeviceProvisionedController, + private val shadeDialogContextInteractor: ShadeDialogContextInteractor, ) { /** The list of PrivacyItems to be displayed by the privacy chip. */ val privacyItems: StateFlow<List<PrivacyItem>> = repository.privacyItems @@ -80,9 +81,9 @@ constructor( if (!deviceProvisionedController.isDeviceProvisioned) return if (repository.isSafetyCenterEnabled.value) { - privacyDialogControllerV2.showDialog(privacyChip.context, privacyChip) + privacyDialogControllerV2.showDialog(shadeDialogContextInteractor.context, privacyChip) } else { - privacyDialogController.showDialog(privacyChip.context) + privacyDialogController.showDialog(shadeDialogContextInteractor.context) } } } 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 fc26499a27a7..b045db464674 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 @@ -20,24 +20,34 @@ import android.util.Log import android.window.WindowContext import androidx.annotation.UiThread import com.android.app.tracing.coroutines.launchTraced -import com.android.app.tracing.traceSection import com.android.systemui.CoreStartable +import com.android.systemui.common.ui.data.repository.ConfigurationRepository import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.shade.ShadeDisplayAware import com.android.systemui.shade.ShadeDisplayChangeLatencyTracker import com.android.systemui.shade.ShadeTraceLogger.logMoveShadeWindowTo +import com.android.systemui.shade.ShadeTraceLogger.t import com.android.systemui.shade.ShadeTraceLogger.traceReparenting import com.android.systemui.shade.data.repository.ShadeDisplaysRepository import com.android.systemui.shade.display.ShadeExpansionIntent import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround +import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor +import com.android.systemui.statusbar.notification.row.NotificationRebindingTracker +import com.android.systemui.statusbar.notification.stack.NotificationStackRebindingHider +import com.android.systemui.util.kotlin.getOrNull import com.android.window.flags.Flags import java.util.Optional import javax.inject.Inject import kotlin.coroutines.CoroutineContext +import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.first import kotlinx.coroutines.withContext +import kotlinx.coroutines.withTimeoutOrNull /** Handles Shade window display change when [ShadeDisplaysRepository.displayId] changes. */ @SysUISingleton @@ -46,27 +56,29 @@ class ShadeDisplaysInteractor constructor( private val shadePositionRepository: ShadeDisplaysRepository, @ShadeDisplayAware private val shadeContext: WindowContext, + @ShadeDisplayAware private val configurationRepository: ConfigurationRepository, @Background private val bgScope: CoroutineScope, @Main private val mainThreadContext: CoroutineContext, private val shadeDisplayChangeLatencyTracker: ShadeDisplayChangeLatencyTracker, shadeExpandedInteractor: Optional<ShadeExpandedStateInteractor>, private val shadeExpansionIntent: ShadeExpansionIntent, + private val activeNotificationsInteractor: ActiveNotificationsInteractor, + private val notificationRebindingTracker: NotificationRebindingTracker, + notificationStackRebindingHider: Optional<NotificationStackRebindingHider>, ) : CoreStartable { - private val shadeExpandedInteractor = - shadeExpandedInteractor.orElse(null) - ?: error( - """ - ShadeExpandedStateInteractor must be provided for ShadeDisplaysInteractor to work. - If it is not, it means this is being instantiated in a SystemUI variant that shouldn't. - """ - .trimIndent() - ) + private val shadeExpandedInteractor = requireOptional(shadeExpandedInteractor) + private val notificationStackRebindingHider = requireOptional(notificationStackRebindingHider) + + private val hasActiveNotifications: Boolean + get() = activeNotificationsInteractor.areAnyNotificationsPresentValue override fun start() { ShadeWindowGoesAround.isUnexpectedlyInLegacyMode() bgScope.launchTraced(TAG) { - shadePositionRepository.displayId.collect { displayId -> moveShadeWindowTo(displayId) } + shadePositionRepository.displayId.collectLatest { displayId -> + moveShadeWindowTo(displayId) + } } } @@ -74,24 +86,22 @@ constructor( private suspend fun moveShadeWindowTo(destinationId: Int) { Log.d(TAG, "Trying to move shade window to display with id $destinationId") logMoveShadeWindowTo(destinationId) - // Why using the shade context here instead of the view's Display? - // The context's display is updated before the view one, so it is a better indicator of - // which display the shade is supposed to be at. The View display is updated after the first - // rendering with the new config. - val currentDisplay = shadeContext.display - if (currentDisplay == null) { - Log.w(TAG, "Current shade display is null") - return - } - val currentId = currentDisplay.displayId - if (currentId == destinationId) { - Log.w(TAG, "Trying to move the shade to a display it was already in") - return - } + var currentId = -1 try { + // Why using the shade context here instead of the view's Display? + // The context's display is updated before the view one, so it is a better indicator of + // which display the shade is supposed to be at. The View display is updated after the + // first + // rendering with the new config. + val currentDisplay = shadeContext.display ?: error("Current shade display is null") + currentId = currentDisplay.displayId + if (currentId == destinationId) { + error("Trying to move the shade to a display it was already in") + } + withContext(mainThreadContext) { traceReparenting { - collapseAndExpandShadeIfNeeded { + collapseAndExpandShadeIfNeeded(destinationId) { shadeDisplayChangeLatencyTracker.onShadeDisplayChanging(destinationId) reparentToDisplayId(id = destinationId) } @@ -107,16 +117,71 @@ constructor( } } - private suspend fun collapseAndExpandShadeIfNeeded(wrapped: () -> Unit) { + private suspend fun collapseAndExpandShadeIfNeeded(newDisplayId: Int, reparent: () -> Unit) { val previouslyExpandedElement = shadeExpandedInteractor.currentlyExpandedElement.value previouslyExpandedElement?.collapse(reason = COLLAPSE_EXPAND_REASON) + val notificationStackHidden = + if (!hasActiveNotifications) { + // This covers the case the previous move was cancelled before setting the + // visibility back. As there are no notifications, nothing can flicker here, and + // showing them all of a sudden is ok. + notificationStackRebindingHider.setVisible(visible = true, animated = false) + false + } else { + // Hiding as otherwise there might be flickers as the inflation with new dimensions + // happens async and views with the old dimensions are not removed until the + // inflation succeeds. + notificationStackRebindingHider.setVisible(visible = false, animated = false) + true + } - wrapped() + reparent() + val elementToExpand = + shadeExpansionIntent.consumeExpansionIntent() ?: previouslyExpandedElement // If the user was trying to expand a specific shade element, let's make sure to expand // that one. Otherwise, we can just re-expand the previous expanded element. - shadeExpansionIntent.consumeExpansionIntent()?.expand(COLLAPSE_EXPAND_REASON) - ?: previouslyExpandedElement?.expand(reason = COLLAPSE_EXPAND_REASON) + elementToExpand?.expand(COLLAPSE_EXPAND_REASON) + if (notificationStackHidden) { + if (hasActiveNotifications) { + // "onMovedToDisplay" is what synchronously triggers the rebinding of views: we need + // to wait for it to be received. + waitForOnMovedToDisplayDispatchedToView(newDisplayId) + waitForNotificationsRebinding() + } + notificationStackRebindingHider.setVisible(visible = true, animated = true) + } + } + + private suspend fun waitForOnMovedToDisplayDispatchedToView(newDisplayId: Int) { + withContext(bgScope.coroutineContext) { + t.traceAsync({ + "waitForOnMovedToDisplayDispatchedToView(newDisplayId=$newDisplayId)" + }) { + withTimeoutOrNull(TIMEOUT) { + configurationRepository.onMovedToDisplay.filter { it == newDisplayId }.first() + t.instant { "onMovedToDisplay received with $newDisplayId" } + } + ?: errorLog( + "Timed out while waiting for onMovedToDisplay to be dispatched to " + + "the shade root view in ShadeDisplaysInteractor" + ) + } + } + } + + private suspend fun waitForNotificationsRebinding() { + // here we don't need to wait for rebinding to appear (e.g. going > 0), as it already + // happened synchronously when the new configuration was received by ViewConfigCoordinator. + t.traceAsync("waiting for notifications rebinding to finish") { + withTimeoutOrNull(TIMEOUT) { + notificationRebindingTracker.rebindingInProgressCount.first { it == 0 } + } ?: errorLog("Timed out while waiting for inflations to finish") + } + } + + private fun errorLog(s: String) { + Log.e(TAG, s) } private fun checkContextDisplayMatchesExpected(destinationId: Int) { @@ -133,11 +198,33 @@ constructor( @UiThread private fun reparentToDisplayId(id: Int) { - traceSection({ "reparentToDisplayId(id=$id)" }) { shadeContext.reparentToDisplay(id) } + t.traceSyncAndAsync({ "reparentToDisplayId(id=$id)" }) { + shadeContext.reparentToDisplay(id) + } } private companion object { const val TAG = "ShadeDisplaysInteractor" const val COLLAPSE_EXPAND_REASON = "Shade window move" + val TIMEOUT = 1.seconds + + /** + * [ShadeDisplaysInteractor] is bound in the SystemUI module for all variants, but needs + * some specific dependencies to be bound from each variant (e.g. + * [ShadeExpandedStateInteractor] or [NotificationStackRebindingHider]). When those are not + * bound, this class is not expected to be instantiated, and trying to instantiate it would + * crash. + */ + inline fun <reified T> requireOptional(optional: Optional<T>): T { + return optional.getOrNull() + ?: error( + """ + ${T::class.java.simpleName} must be provided for ShadeDisplaysInteractor to work. + If it is not, it means this is being instantiated in a SystemUI variant that + shouldn't. + """ + .trimIndent() + ) + } } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt index a653ca2f80a9..b5e171043741 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt @@ -16,6 +16,7 @@ package com.android.systemui.shade.domain.interactor +import android.util.Log import com.android.app.tracing.coroutines.flow.flowName import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application @@ -38,6 +39,7 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn /** The non-empty SceneInteractor implementation. */ @@ -98,18 +100,36 @@ constructor( override val isShadeTouchable: Flow<Boolean> = combine( - powerInteractor.isAsleep, - keyguardTransitionInteractor.isInTransition(Edge.create(to = KeyguardState.AOD)), - keyguardRepository.dozeTransitionModel.map { it.to == DozeStateModel.DOZE_PULSING }, - ) { isAsleep, goingToSleep, isPulsing -> - when { - // If the device is going to sleep, only accept touches if we're still - // animating - goingToSleep -> dozeParams.shouldControlScreenOff() + powerInteractor.isAsleep + .onEach { Log.d(TAG, "isShadeTouchable: upstream isAsleep=$it") }, + keyguardTransitionInteractor + .isInTransition(Edge.create(to = KeyguardState.AOD)) + .onEach { + Log.d( + TAG, + "isShadeTouchable: upstream isTransitioningToAod=$it", + ) + }, + keyguardRepository.dozeTransitionModel + .map { it.to == DozeStateModel.DOZE_PULSING } + .onEach { + Log.d(TAG, "isShadeTouchable: upstream isPulsing=$it") + }, + ) { isAsleep, isTransitioningToAod, isPulsing -> + val downstream = when { + // If the device is transitioning to AOD, only accept touches if + // still animating. + isTransitioningToAod -> dozeParams.shouldControlScreenOff() // If the device is asleep, only accept touches if there's a pulse isAsleep -> isPulsing else -> true } + Log.d(TAG, "isShadeTouchable emitting $downstream, values:") + Log.d(TAG, " isAsleep=$isAsleep") + Log.d(TAG, " isTransitioningToAod=$isTransitioningToAod") + Log.d(TAG, " isPulsing=$isPulsing") + Log.d(TAG, "") + downstream } override val isExpandToQsEnabled: Flow<Boolean> = @@ -128,4 +148,8 @@ constructor( disableFlags.isQuickSettingsEnabled() && !isDozing } + + companion object { + private const val TAG = "ShadeInteractor" + } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt index 765810810bb8..992385c8d80e 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt @@ -199,11 +199,14 @@ constructor( ) } else if (transitionKey == Instant) { // TODO(b/356596436): Define instant transition instead of snapToScene(). - sceneInteractor.snapToScene(toScene = SceneFamilies.Home, loggingReason = loggingReason) + sceneInteractor.snapToScene( + toScene = SceneFamilies.Home, + loggingReason = loggingReason + " (collapseNotificationsShade)", + ) } else { sceneInteractor.changeScene( toScene = SceneFamilies.Home, - loggingReason = loggingReason, + loggingReason = loggingReason + " (collapseNotificationsShade)", transitionKey = transitionKey ?: ToSplitShade.takeIf { shadeModeInteractor.isSplitShade }, ) @@ -230,11 +233,14 @@ constructor( if (bypassNotificationsShade || isSplitShade) SceneFamilies.Home else Scenes.Shade if (transitionKey == Instant) { // TODO(b/356596436): Define instant transition instead of snapToScene(). - sceneInteractor.snapToScene(toScene = targetScene, loggingReason = loggingReason) + sceneInteractor.snapToScene( + toScene = targetScene, + loggingReason = loggingReason + " (collapseQuickSettingsShade)", + ) } else { sceneInteractor.changeScene( toScene = targetScene, - loggingReason = loggingReason, + loggingReason = loggingReason + " (collapseQuickSettingsShade)", transitionKey = transitionKey ?: ToSplitShade.takeIf { isSplitShade }, ) } diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractor.kt index edf503d03f3e..59d812403777 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractor.kt @@ -16,17 +16,20 @@ package com.android.systemui.shade.domain.interactor +import android.provider.Settings import androidx.annotation.FloatRange import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.shade.data.repository.ShadeRepository import com.android.systemui.shade.shared.flag.DualShade import com.android.systemui.shade.shared.model.ShadeMode +import com.android.systemui.shared.settings.data.repository.SecureSettingsRepository import javax.inject.Inject import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.stateIn /** @@ -76,29 +79,53 @@ interface ShadeModeInteractor { class ShadeModeInteractorImpl @Inject -constructor(@Application applicationScope: CoroutineScope, repository: ShadeRepository) : - ShadeModeInteractor { +constructor( + @Application applicationScope: CoroutineScope, + repository: ShadeRepository, + secureSettingsRepository: SecureSettingsRepository, +) : ShadeModeInteractor { + + private val isDualShadeEnabled: Flow<Boolean> = + secureSettingsRepository.boolSetting( + Settings.Secure.DUAL_SHADE, + defaultValue = DUAL_SHADE_ENABLED_DEFAULT, + ) override val isShadeLayoutWide: StateFlow<Boolean> = repository.isShadeLayoutWide override val shadeMode: StateFlow<ShadeMode> = - isShadeLayoutWide - .map(this::determineShadeMode) + combine(isDualShadeEnabled, repository.isShadeLayoutWide, ::determineShadeMode) .stateIn( applicationScope, SharingStarted.Eagerly, - initialValue = determineShadeMode(isShadeLayoutWide.value), + initialValue = + determineShadeMode( + isDualShadeEnabled = DUAL_SHADE_ENABLED_DEFAULT, + isShadeLayoutWide = repository.isShadeLayoutWide.value, + ), ) @FloatRange(from = 0.0, to = 1.0) override fun getTopEdgeSplitFraction(): Float = 0.5f - private fun determineShadeMode(isShadeLayoutWide: Boolean): ShadeMode { + private fun determineShadeMode( + isDualShadeEnabled: Boolean, + isShadeLayoutWide: Boolean, + ): ShadeMode { return when { - DualShade.isEnabled -> ShadeMode.Dual + isDualShadeEnabled || + // TODO(b/388793191): This ensures that the dual_shade aconfig flag can also enable + // Dual Shade, to avoid breaking unit tests. Remove this once all references to the + // flag are removed. + DualShade.isEnabled -> ShadeMode.Dual isShadeLayoutWide -> ShadeMode.Split else -> ShadeMode.Single } } + + companion object { + /* Whether the Dual Shade setting is enabled by default. */ + private const val DUAL_SHADE_ENABLED_DEFAULT = false + } } class ShadeModeInteractorEmptyImpl @Inject constructor() : ShadeModeInteractor { diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/ShadeColors.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/ShadeColors.kt new file mode 100644 index 000000000000..8db622566e5e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/shade/ui/ShadeColors.kt @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.ui + +import android.content.res.Resources +import android.graphics.Color +import com.android.internal.graphics.ColorUtils +import com.android.systemui.res.R + +object ShadeColors { + @JvmStatic + fun Resources.shadePanel(): Int { + val layerAbove = + ColorUtils.setAlphaComponent(getColor(R.color.shade_panel_base), (0.4f * 255).toInt()) + val layerBelow = ColorUtils.setAlphaComponent(Color.WHITE, (0.1f * 255).toInt()) + return ColorUtils.compositeColors(layerAbove, layerBelow) + } + + @JvmStatic + fun Resources.notificationScrim(): Int { + return ColorUtils.setAlphaComponent( + getColor(R.color.notification_scrim_base), + (0.5f * 255).toInt(), + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt index 0d847d84c820..96128df1b723 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt @@ -23,18 +23,23 @@ import android.icu.text.DateFormat import android.icu.text.DisplayContext import android.os.UserHandle import android.provider.Settings +import androidx.compose.runtime.getValue import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.lifecycle.ExclusiveActivatable +import com.android.systemui.lifecycle.Hydrator import com.android.systemui.plugins.ActivityStarter import com.android.systemui.privacy.OngoingPrivacyChip import com.android.systemui.privacy.PrivacyItem import com.android.systemui.res.R +import com.android.systemui.scene.domain.interactor.SceneInteractor +import com.android.systemui.scene.shared.model.Overlays import com.android.systemui.scene.shared.model.TransitionKeys.SlightlyFasterShadeCollapse import com.android.systemui.shade.ShadeDisplayAware import com.android.systemui.shade.domain.interactor.PrivacyChipInteractor import com.android.systemui.shade.domain.interactor.ShadeHeaderClockInteractor import com.android.systemui.shade.domain.interactor.ShadeInteractor +import com.android.systemui.shade.shared.model.ShadeMode import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel import dagger.assisted.AssistedFactory @@ -56,6 +61,7 @@ class ShadeHeaderViewModel constructor( @ShadeDisplayAware context: Context, private val activityStarter: ActivityStarter, + private val sceneInteractor: SceneInteractor, private val shadeInteractor: ShadeInteractor, private val mobileIconsInteractor: MobileIconsInteractor, val mobileIconsViewModel: MobileIconsViewModel, @@ -63,6 +69,35 @@ constructor( private val clockInteractor: ShadeHeaderClockInteractor, private val broadcastDispatcher: BroadcastDispatcher, ) : ExclusiveActivatable() { + private val hydrator = Hydrator("ShadeHeaderViewModel.hydrator") + + val highlightNotificationIcons: Boolean by + hydrator.hydratedStateOf( + traceName = "highlightNotificationIcons", + initialValue = false, + source = + sceneInteractor.currentOverlays.map { overlays -> + Overlays.NotificationsShade in overlays + }, + ) + + val highlightQuickSettingsIcons: Boolean by + hydrator.hydratedStateOf( + traceName = "highlightQuickSettingsIcons", + initialValue = false, + source = + sceneInteractor.currentOverlays.map { overlays -> + Overlays.QuickSettingsShade in overlays + }, + ) + + val isShadeLayoutWide: Boolean by + hydrator.hydratedStateOf( + traceName = "isShadeLayoutWide", + initialValue = shadeInteractor.isShadeLayoutWide.value, + source = shadeInteractor.isShadeLayoutWide, + ) + /** True if there is exactly one mobile connection. */ val isSingleCarrier: StateFlow<Boolean> = mobileIconsInteractor.isSingleCarrier @@ -128,6 +163,8 @@ constructor( .collect { _mobileSubIds.value = it } } + launch { hydrator.activate() } + awaitCancellation() } } @@ -143,11 +180,41 @@ constructor( } /** Notifies that the system icons container was clicked. */ - fun onSystemIconContainerClicked() { - shadeInteractor.collapseEitherShade( - loggingReason = "ShadeHeaderViewModel.onSystemIconContainerClicked", - transitionKey = SlightlyFasterShadeCollapse, - ) + fun onNotificationIconChipClicked() { + if (shadeInteractor.shadeMode.value !is ShadeMode.Dual) { + return + } + val loggingReason = "ShadeHeaderViewModel.onNotificationIconChipClicked" + val currentOverlays = sceneInteractor.currentOverlays.value + if (Overlays.NotificationsShade in currentOverlays) { + shadeInteractor.collapseNotificationsShade( + loggingReason = loggingReason, + transitionKey = SlightlyFasterShadeCollapse, + ) + } else { + shadeInteractor.expandNotificationsShade(loggingReason) + } + } + + /** Notifies that the system icons container was clicked. */ + fun onSystemIconChipClicked() { + val loggingReason = "ShadeHeaderViewModel.onSystemIconChipClicked" + if (shadeInteractor.shadeMode.value is ShadeMode.Dual) { + val currentOverlays = sceneInteractor.currentOverlays.value + if (Overlays.QuickSettingsShade in currentOverlays) { + shadeInteractor.collapseQuickSettingsShade( + loggingReason = loggingReason, + transitionKey = SlightlyFasterShadeCollapse, + ) + } else { + shadeInteractor.expandQuickSettingsShade(loggingReason) + } + } else { + shadeInteractor.collapseEitherShade( + loggingReason = loggingReason, + transitionKey = SlightlyFasterShadeCollapse, + ) + } } /** Notifies that the shadeCarrierGroup was clicked. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index c6a4d15705f0..dcea8d85e10d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -184,6 +184,8 @@ public class CommandQueue extends IStatusBar.Stub implements private static final int MSG_SET_SPLITSCREEN_FOCUS = 81 << MSG_SHIFT; private static final int MSG_TOGGLE_QUICK_SETTINGS_PANEL = 82 << MSG_SHIFT; private static final int MSG_WALLET_ACTION_LAUNCH_GESTURE = 83 << MSG_SHIFT; + private static final int MSG_SET_HAS_NAVIGATION_BAR = 84 << MSG_SHIFT; + private static final int MSG_DISPLAY_REMOVE_SYSTEM_DECORATIONS = 85 << MSG_SHIFT; public static final int FLAG_EXCLUDE_NONE = 0; public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0; public static final int FLAG_EXCLUDE_RECENTS_PANEL = 1 << 1; @@ -271,12 +273,12 @@ public class CommandQueue extends IStatusBar.Stub implements default void toggleQuickSettingsPanel() { } /** - * Called to notify IME window status changes. + * Sets the new IME window status. * - * @param displayId The id of the display to notify. - * @param vis IME visibility. - * @param backDisposition Disposition mode of back button. - * @param showImeSwitcher {@code true} to show IME switch button. + * @param displayId The id of the display to which the IME is bound. + * @param vis The IME window visibility. + * @param backDisposition The IME back disposition mode. + * @param showImeSwitcher Whether the IME Switcher button should be shown. */ default void setImeWindowStatus(int displayId, @ImeWindowVisibility int vis, @BackDispositionMode int backDisposition, boolean showImeSwitcher) { } @@ -419,6 +421,12 @@ public class CommandQueue extends IStatusBar.Stub implements } /** + * @see IStatusBar#onDisplayRemoveSystemDecorations(int) + */ + default void onDisplayRemoveSystemDecorations(int displayId) { + } + + /** * @see DisplayTracker.Callback#onDisplayRemoved(int) */ default void onDisplayRemoved(int displayId) { @@ -580,6 +588,12 @@ public class CommandQueue extends IStatusBar.Stub implements * @see IStatusBar#moveFocusedTaskToDesktop(int) */ default void moveFocusedTaskToDesktop(int displayId) {} + + /** + * @see IStatusBar#setHasNavigationBar(int, boolean) + */ + default void setHasNavigationBar(int displayId, boolean hasNavigationBar) { + } } @VisibleForTesting @@ -1197,6 +1211,14 @@ public class CommandQueue extends IStatusBar.Stub implements } } + @Override + public void onDisplayRemoveSystemDecorations(int displayId) { + synchronized (mLock) { + mHandler.obtainMessage(MSG_DISPLAY_REMOVE_SYSTEM_DECORATIONS, displayId, 0) + .sendToTarget(); + } + } + // This was previously called from WM, but is now called from WMShell public void onRecentsAnimationStateChanged(boolean running) { synchronized (mLock) { @@ -1510,6 +1532,15 @@ public class CommandQueue extends IStatusBar.Stub implements mHandler.obtainMessage(MSG_ENTER_DESKTOP, args).sendToTarget(); } + @Override + public void setHasNavigationBar(int displayId, boolean hasNavigationBar) { + synchronized (mLock) { + mHandler.obtainMessage(MSG_SET_HAS_NAVIGATION_BAR, displayId, + hasNavigationBar ? 1 : 0).sendToTarget(); + } + } + + private final class H extends Handler { private H(Looper l) { super(l); @@ -1825,6 +1856,11 @@ public class CommandQueue extends IStatusBar.Stub implements mCallbacks.get(i).onDisplayReady(msg.arg1); } break; + case MSG_DISPLAY_REMOVE_SYSTEM_DECORATIONS: + for (int i = 0; i < mCallbacks.size(); i++) { + mCallbacks.get(i).onDisplayRemoveSystemDecorations(msg.arg1); + } + break; case MSG_RECENTS_ANIMATION_STATE_CHANGED: for (int i = 0; i < mCallbacks.size(); i++) { mCallbacks.get(i).onRecentsAnimationStateChanged(msg.arg1 > 0); @@ -2036,6 +2072,11 @@ public class CommandQueue extends IStatusBar.Stub implements } break; } + case MSG_SET_HAS_NAVIGATION_BAR: + for (int i = 0; i < mCallbacks.size(); i++) { + mCallbacks.get(i).setHasNavigationBar(msg.arg1, msg.arg2 != 0); + } + break; } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index a5595edcbb95..4269f60e1c2a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -88,6 +88,7 @@ import com.android.keyguard.TrustGrantFlags; import com.android.keyguard.logging.KeyguardLogger; import com.android.settingslib.Utils; import com.android.settingslib.fuelgauge.BatteryStatus; +import com.android.systemui.Flags; import com.android.systemui.biometrics.AuthController; import com.android.systemui.biometrics.FaceHelpMessageDeferral; import com.android.systemui.biometrics.FaceHelpMessageDeferralFactory; @@ -199,7 +200,7 @@ public class KeyguardIndicationController { private CharSequence mBiometricMessage; private CharSequence mBiometricMessageFollowUp; private BiometricSourceType mBiometricMessageSource; - protected ColorStateList mInitialTextColorState; + private ColorStateList mInitialTextColorState; private boolean mVisible; private boolean mOrganizationOwnedDevice; @@ -393,13 +394,27 @@ public class KeyguardIndicationController { return mIndicationArea; } + /** + * Notify controller about configuration changes. + */ + public void onConfigurationChanged() { + // Get new text color in case theme has changed + if (Flags.indicationTextA11yFix()) { + setIndicationColorToThemeColor(); + } + } + public void setIndicationArea(ViewGroup indicationArea) { mIndicationArea = indicationArea; mTopIndicationView = indicationArea.findViewById(R.id.keyguard_indication_text); mLockScreenIndicationView = indicationArea.findViewById( R.id.keyguard_indication_text_bottom); - mInitialTextColorState = mTopIndicationView != null - ? mTopIndicationView.getTextColors() : ColorStateList.valueOf(Color.WHITE); + if (Flags.indicationTextA11yFix()) { + setIndicationColorToThemeColor(); + } else { + setIndicationTextColor(mTopIndicationView != null + ? mTopIndicationView.getTextColors() : ColorStateList.valueOf(Color.WHITE)); + } if (mRotateTextViewController != null) { mRotateTextViewController.destroy(); } @@ -436,6 +451,12 @@ public class KeyguardIndicationController { mIsLogoutEnabledCallback); } + @NonNull + private ColorStateList wallpaperTextColor() { + return ColorStateList.valueOf( + Utils.getColorAttrDefaultColor(mContext, R.attr.wallpaperTextColor)); + } + /** * Cleanup */ @@ -513,7 +534,7 @@ public class KeyguardIndicationController { .setMessage(mContext.getResources().getString( com.android.systemui.res.R.string.dismissible_keyguard_swipe) ) - .setTextColor(mInitialTextColorState) + .setTextColor(getInitialTextColorState()) .build(), /* updateImmediately */ true); } else { @@ -533,7 +554,7 @@ public class KeyguardIndicationController { INDICATION_TYPE_DISCLOSURE, new KeyguardIndication.Builder() .setMessage(disclosure) - .setTextColor(mInitialTextColorState) + .setTextColor(getInitialTextColorState()) .build(), /* updateImmediately */ false); } @@ -602,7 +623,7 @@ public class KeyguardIndicationController { INDICATION_TYPE_OWNER_INFO, new KeyguardIndication.Builder() .setMessage(finalInfo) - .setTextColor(mInitialTextColorState) + .setTextColor(getInitialTextColorState()) .build(), false); } else { @@ -624,7 +645,7 @@ public class KeyguardIndicationController { INDICATION_TYPE_BATTERY, new KeyguardIndication.Builder() .setMessage(powerIndication) - .setTextColor(mInitialTextColorState) + .setTextColor(getInitialTextColorState()) .build(), animate); } else { @@ -645,7 +666,7 @@ public class KeyguardIndicationController { new KeyguardIndication.Builder() .setMessage(mContext.getResources().getText( com.android.internal.R.string.lockscreen_storage_locked)) - .setTextColor(mInitialTextColorState) + .setTextColor(getInitialTextColorState()) .build(), false); } else { @@ -666,7 +687,7 @@ public class KeyguardIndicationController { .setMessage(mBiometricMessage) .setForceAccessibilityLiveRegionAssertive() .setMinVisibilityMillis(IMPORTANT_MSG_MIN_DURATION) - .setTextColor(mInitialTextColorState) + .setTextColor(getInitialTextColorState()) .build(), true ); @@ -680,7 +701,7 @@ public class KeyguardIndicationController { new KeyguardIndication.Builder() .setMessage(mBiometricMessageFollowUp) .setMinVisibilityMillis(IMPORTANT_MSG_MIN_DURATION) - .setTextColor(mInitialTextColorState) + .setTextColor(getInitialTextColorState()) .build(), true ); @@ -711,7 +732,7 @@ public class KeyguardIndicationController { INDICATION_TYPE_TRUST, new KeyguardIndication.Builder() .setMessage(trustGrantedIndication) - .setTextColor(mInitialTextColorState) + .setTextColor(getInitialTextColorState()) .build(), true); hideBiometricMessage(); @@ -722,7 +743,7 @@ public class KeyguardIndicationController { INDICATION_TYPE_TRUST, new KeyguardIndication.Builder() .setMessage(trustManagedIndication) - .setTextColor(mInitialTextColorState) + .setTextColor(getInitialTextColorState()) .build(), false); } else { @@ -751,7 +772,7 @@ public class KeyguardIndicationController { INDICATION_TYPE_PERSISTENT_UNLOCK_MESSAGE, new KeyguardIndication.Builder() .setMessage(mPersistentUnlockMessage) - .setTextColor(mInitialTextColorState) + .setTextColor(getInitialTextColorState()) .build(), true); } else { @@ -792,7 +813,7 @@ public class KeyguardIndicationController { new KeyguardIndication.Builder() .setMessage(mContext.getString( R.string.keyguard_indication_after_adaptive_auth_lock)) - .setTextColor(mInitialTextColorState) + .setTextColor(getInitialTextColorState()) .build(), true); } else { @@ -1179,7 +1200,8 @@ public class KeyguardIndicationController { } else { message = mContext.getString(R.string.keyguard_retry); } - mStatusBarKeyguardViewManager.setKeyguardMessage(message, mInitialTextColorState, + mStatusBarKeyguardViewManager.setKeyguardMessage(message, + getInitialTextColorState(), null); } } else { @@ -1232,7 +1254,7 @@ public class KeyguardIndicationController { public void dump(PrintWriter pw, String[] args) { pw.println("KeyguardIndicationController:"); - pw.println(" mInitialTextColorState: " + mInitialTextColorState); + pw.println(" mInitialTextColorState: " + getInitialTextColorState()); pw.println(" mPowerPluggedInWired: " + mPowerPluggedInWired); pw.println(" mPowerPluggedIn: " + mPowerPluggedIn); pw.println(" mPowerCharged: " + mPowerCharged); @@ -1253,6 +1275,22 @@ public class KeyguardIndicationController { mRotateTextViewController.dump(pw, args); } + protected ColorStateList getInitialTextColorState() { + return mInitialTextColorState; + } + + private void setIndicationColorToThemeColor() { + mInitialTextColorState = wallpaperTextColor(); + } + + /** + * @deprecated Use {@link #setIndicationColorToThemeColor} + */ + @Deprecated + private void setIndicationTextColor(ColorStateList color) { + mInitialTextColorState = color; + } + protected class BaseKeyguardCallback extends KeyguardUpdateMonitorCallback { @Override public void onTimeChanged() { @@ -1358,7 +1396,7 @@ public class KeyguardIndicationController { mBouncerMessageInteractor.setFaceAcquisitionMessage(helpString); } mStatusBarKeyguardViewManager.setKeyguardMessage(helpString, - mInitialTextColorState, biometricSourceType); + getInitialTextColorState(), biometricSourceType); } else if (mScreenLifecycle.getScreenState() == SCREEN_ON) { if (isCoExFaceAcquisitionMessage && msgId == FACE_ACQUIRED_TOO_DARK) { showBiometricMessage( @@ -1655,7 +1693,7 @@ public class KeyguardIndicationController { private void showErrorMessageNowOrLater(String errString, @Nullable String followUpMsg, BiometricSourceType biometricSourceType) { if (mStatusBarKeyguardViewManager.isBouncerShowing()) { - mStatusBarKeyguardViewManager.setKeyguardMessage(errString, mInitialTextColorState, + mStatusBarKeyguardViewManager.setKeyguardMessage(errString, getInitialTextColorState(), biometricSourceType); } else if (mScreenLifecycle.getScreenState() == SCREEN_ON) { showBiometricMessage(errString, followUpMsg, biometricSourceType); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java index ccea254defaa..4c6fa4839e5c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java @@ -284,7 +284,8 @@ public class NotificationListener extends NotificationListenerWithPlugins implem /* rankingAdjustment= */ 0, /* isBubble= */ false, /* proposedImportance= */ 0, - /* sensitiveContent= */ false + /* sensitiveContent= */ false, + /* summarization = */ null ); } return ranking; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java index 10b726b90894..c8f972774ab0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java @@ -121,7 +121,6 @@ public class NotificationLockscreenUserManagerImpl implements private static final long LOCK_TIME_FOR_SENSITIVE_REDACTION_MS = TimeUnit.MINUTES.toMillis(10); - private final Lazy<NotificationVisibilityProvider> mVisibilityProviderLazy; private final Lazy<CommonNotifCollection> mCommonNotifCollectionLazy; private final DevicePolicyManager mDevicePolicyManager; @@ -751,7 +750,7 @@ public class NotificationLockscreenUserManagerImpl implements } long lastLockedTime = mLastLockTime.get(); - if (ent.getSbn().getPostTime() < lastLockedTime) { + if (ent.getSbn().getNotification().getWhen() < lastLockedTime) { return false; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt index 6ce3228531d2..a682f9674e2e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt @@ -30,6 +30,7 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -47,6 +48,7 @@ import com.android.systemui.res.R import com.android.systemui.statusbar.chips.ui.compose.modifiers.neverDecreaseWidth 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.viewmodel.rememberChronometerState @Composable fun OngoingActivityChip(model: OngoingActivityChipModel.Shown, modifier: Modifier = Modifier) { @@ -195,11 +197,12 @@ private fun ChipContent(viewModel: OngoingActivityChipModel.Shown, modifier: Mod val context = LocalContext.current when (viewModel) { is OngoingActivityChipModel.Shown.Timer -> { - ChronometerText( - startTimeMillis = viewModel.startTimeMs, + val timerState = rememberChronometerState(startTimeMillis = viewModel.startTimeMs) + Text( + text = timerState.currentTimeText, style = MaterialTheme.typography.labelLarge, color = Color(viewModel.colors.text(context)), - modifier = modifier, + modifier = modifier.neverDecreaseWidth(), ) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChronometerText.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChronometerState.kt index 1c14d3349027..62789782d0a9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChronometerText.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChronometerState.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 The Android Open Source Project + * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,12 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.systemui.statusbar.chips.ui.compose +package com.android.systemui.statusbar.chips.ui.viewmodel import android.os.SystemClock import android.text.format.DateUtils.formatElapsedTime -import androidx.compose.material3.LocalTextStyle -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf @@ -26,13 +24,9 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableLongStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.text.TextStyle import androidx.lifecycle.Lifecycle import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.repeatOnLifecycle -import com.android.systemui.statusbar.chips.ui.compose.modifiers.neverDecreaseWidth import kotlinx.coroutines.delay /** Platform-optimized interface for getting current time */ @@ -59,7 +53,10 @@ class ChronometerState(private val timeSource: TimeSource, private val startTime /** Remember and manage the ChronometerState */ @Composable -fun rememberChronometerState(timeSource: TimeSource, startTimeMillis: Long): ChronometerState { +fun rememberChronometerState( + startTimeMillis: Long, + timeSource: TimeSource = remember { TimeSource { SystemClock.elapsedRealtime() } }, +): ChronometerState { val state = remember(timeSource, startTimeMillis) { ChronometerState(timeSource, startTimeMillis) } val lifecycleOwner = LocalLifecycleOwner.current @@ -69,25 +66,3 @@ fun rememberChronometerState(timeSource: TimeSource, startTimeMillis: Long): Chr return state } - -/** - * A composable chronometer that displays elapsed time with constrained width. The width of the text - * is only allowed to increase. This ensures there is no visual jitter when individual digits in the - * text change due to the timer ticking. - */ -@Composable -fun ChronometerText( - startTimeMillis: Long, - modifier: Modifier = Modifier, - color: Color = Color.Unspecified, - style: TextStyle = LocalTextStyle.current, - timeSource: TimeSource = remember { TimeSource { SystemClock.elapsedRealtime() } }, -) { - val state = rememberChronometerState(timeSource, startTimeMillis) - Text( - text = state.currentTimeText, - style = style, - color = color, - modifier = modifier.neverDecreaseWidth(), - ) -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt index 70632b33f532..c748e28e60b4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt @@ -188,7 +188,11 @@ constructor( finish.addListener( object : AnimatorListenerAdapter() { override fun onAnimationEnd(animation: Animator) { - animationWindowView.removeView(currentAnimatedView!!.view) + if (!::animationWindowView.isInitialized) { + return + } + val animatedView = currentAnimatedView ?: return + animationWindowView.removeView(animatedView.view) } } ) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/headsup/shared/StatusBarNoHunBehavior.kt b/packages/SystemUI/src/com/android/systemui/statusbar/headsup/shared/StatusBarNoHunBehavior.kt index 2ae54d7c6c83..c9024d934068 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/headsup/shared/StatusBarNoHunBehavior.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/headsup/shared/StatusBarNoHunBehavior.kt @@ -33,7 +33,7 @@ object StatusBarNoHunBehavior { /** Is the refactor enabled */ @JvmStatic inline val isEnabled - get() = Flags.statusBarNoHunBehavior() && android.app.Flags.notificationsRedesignAppIcons() + get() = Flags.statusBarNoHunBehavior() /** * Called to ensure code is only run when the flag is enabled. This protects users from the diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/layout/StatusBarContentInsetsProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/layout/StatusBarContentInsetsProvider.kt index f7a9094e0337..7358c513eaff 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/layout/StatusBarContentInsetsProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/layout/StatusBarContentInsetsProvider.kt @@ -53,6 +53,7 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import java.io.PrintWriter import java.lang.Math.max +import java.util.concurrent.CopyOnWriteArraySet /** * Encapsulates logic that can solve for the left/right insets required for the status bar contents. @@ -163,7 +164,7 @@ constructor( // Limit cache size as potentially we may connect large number of displays // (e.g. network displays) private val insetsCache = LruCache<CacheKey, Rect>(MAX_CACHE_SIZE) - private val listeners = mutableSetOf<StatusBarContentInsetsChangedListener>() + private val listeners = CopyOnWriteArraySet<StatusBarContentInsetsChangedListener>() private val isPrivacyDotEnabled: Boolean by lazy(LazyThreadSafetyMode.PUBLICATION) { context.resources.getBoolean(R.bool.config_enablePrivacyDot) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java index 417e57d2205f..5cc79df9130a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java @@ -836,6 +836,14 @@ public final class NotificationEntry extends ListEntry { } /** + * Returns whether the NotificationEntry is promoted ongoing. + */ + @FlaggedApi(Flags.FLAG_API_RICH_ONGOING) + public boolean isOngoingPromoted() { + return mSbn.getNotification().isPromotedOngoing(); + } + + /** * Returns whether this row is considered blockable (i.e. it's not a system notif * or is not in an allowList). */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StabilizeHeadsUpGroup.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StabilizeHeadsUpGroup.kt index 629cb831f17a..a0e44bfd7620 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StabilizeHeadsUpGroup.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StabilizeHeadsUpGroup.kt @@ -24,7 +24,7 @@ import com.android.systemui.flags.RefactorFlagUtils @Suppress("NOTHING_TO_INLINE") object StabilizeHeadsUpGroup { /** The aconfig flag name */ - const val FLAG_NAME: String = Flags.FLAG_STABILIZE_HEADS_UP_GROUP + const val FLAG_NAME: String = Flags.FLAG_STABILIZE_HEADS_UP_GROUP_V2 /** A token used for dependency declaration */ val token: FlagToken @@ -33,7 +33,7 @@ object StabilizeHeadsUpGroup { /** Is the refactor enabled */ @JvmStatic inline val isEnabled - get() = Flags.stabilizeHeadsUpGroup() + get() = Flags.stabilizeHeadsUpGroupV2() /** * Called to ensure code is only run when the flag is enabled. This protects users from the diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationStackModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationStackModule.kt new file mode 100644 index 000000000000..6ceeb6aae7a5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationStackModule.kt @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.dagger + +import com.android.systemui.statusbar.notification.stack.NotificationStackRebindingHider +import com.android.systemui.statusbar.notification.stack.NotificationStackRebindingHiderImpl +import dagger.Binds +import dagger.BindsOptionalOf +import dagger.Module + +/** + * This is meant to be bound in SystemUI variants with [NotificationStackScrollLayoutController]. + */ +@Module +interface NotificationStackGoogleModule { + @Binds + fun bindNotificationStackRebindingHider( + impl: NotificationStackRebindingHiderImpl + ): NotificationStackRebindingHider +} + +/** This is meant to be used by all SystemUI variants, also those without NSSL. */ +@Module +interface NotificationStackModule { + @BindsOptionalOf + fun bindOptionalOfNotificationStackRebindingHider(): NotificationStackRebindingHider +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java index 086c32cbae5d..0346108856a2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java @@ -117,6 +117,7 @@ import javax.inject.Provider; NotificationMemoryModule.class, NotificationStatsLoggerModule.class, NotificationsLogModule.class, + NotificationStackModule.class, }) public interface NotificationsModule { @Binds diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java index d401283aa84e..96192b1ea315 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.footer.ui.view; import static android.graphics.PorterDuff.Mode.SRC_ATOP; import static com.android.systemui.Flags.notificationFooterBackgroundTintOptimization; +import static com.android.systemui.Flags.notificationShadeBlur; import static com.android.systemui.util.ColorUtilKt.hexColorString; import android.annotation.ColorInt; @@ -29,6 +30,7 @@ import android.content.Context; import android.content.res.ColorStateList; import android.content.res.Configuration; import android.content.res.Resources; +import android.graphics.Color; import android.graphics.ColorFilter; import android.graphics.PorterDuffColorFilter; import android.graphics.drawable.Drawable; @@ -39,6 +41,8 @@ import android.widget.TextView; import androidx.annotation.NonNull; +import com.android.internal.graphics.ColorUtils; +import com.android.systemui.common.shared.colors.SurfaceEffectColors; import com.android.systemui.res.R; import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.statusbar.notification.ColorUpdateLogger; @@ -383,9 +387,23 @@ public class FooterView extends StackScrollerDecorView { final Drawable historyBg = NotifRedesignFooter.isEnabled() ? theme.getDrawable(R.drawable.notif_footer_btn_background) : null; final @ColorInt int scHigh; + if (!notificationFooterBackgroundTintOptimization()) { - scHigh = mContext.getColor( - com.android.internal.R.color.materialColorSurfaceContainerHigh); + if (notificationShadeBlur()) { + Color backgroundColor = Color.valueOf( + SurfaceEffectColors.surfaceEffect0(getResources())); + scHigh = ColorUtils.setAlphaComponent(backgroundColor.toArgb(), 0xFF); + // Apply alpha on background drawables. + int backgroundAlpha = (int) (backgroundColor.alpha() * 0xFF); + clearAllBg.setAlpha(backgroundAlpha); + settingsBg.setAlpha(backgroundAlpha); + if (historyBg != null) { + historyBg.setAlpha(backgroundAlpha); + } + } else { + scHigh = mContext.getColor( + com.android.internal.R.color.materialColorSurfaceContainerHigh); + } if (scHigh != 0) { final ColorFilter bgColorFilter = new PorterDuffColorFilter(scHigh, SRC_ATOP); clearAllBg.setColorFilter(bgColorFilter); 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 0171fb72e158..be61dc95fe20 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 @@ -706,7 +706,7 @@ public class HeadsUpManagerImpl } private void updateHeadsUpFlow() { - mHeadsUpNotificationRows.setValue(new HashSet<>(getHeadsUpEntryPhoneMap().values())); + mHeadsUpNotificationRows.setValue(new HashSet<>(mHeadsUpEntryMap.values())); } @Override @@ -732,11 +732,6 @@ public class HeadsUpManagerImpl return mHeadsUpAnimatingAway.getValue(); } - @NonNull - private ArrayMap<String, HeadsUpEntry> getHeadsUpEntryPhoneMap() { - return mHeadsUpEntryMap; - } - /** * Called to notify the listeners that the HUN animating away animation has ended. */ @@ -1014,7 +1009,7 @@ public class HeadsUpManagerImpl @Override public void setRemoteInputActive( @NonNull NotificationEntry entry, boolean remoteInputActive) { - HeadsUpEntry headsUpEntry = getHeadsUpEntryPhone(entry.getKey()); + HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(entry.getKey()); if (headsUpEntry != null && headsUpEntry.mRemoteInputActive != remoteInputActive) { headsUpEntry.mRemoteInputActive = remoteInputActive; if (ExpandHeadsUpOnInlineReply.isEnabled() && remoteInputActive) { @@ -1029,11 +1024,6 @@ public class HeadsUpManagerImpl } } - @Nullable - private HeadsUpEntry getHeadsUpEntryPhone(@NonNull String key) { - return mHeadsUpEntryMap.get(key); - } - @Override public void setGutsShown(@NonNull NotificationEntry entry, boolean gutsShown) { HeadsUpEntry headsUpEntry = getHeadsUpEntry(entry.getKey()); @@ -1125,7 +1115,7 @@ public class HeadsUpManagerImpl return true; } - HeadsUpEntry headsUpEntry = getHeadsUpEntryPhone(key); + HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key); HeadsUpEntry topEntry = getTopHeadsUpEntryPhone(); if (headsUpEntry == null || headsUpEntry != topEntry) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt new file mode 100644 index 000000000000..664b2afe90b4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt @@ -0,0 +1,320 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.promoted + +import android.app.Flags +import android.view.LayoutInflater +import android.view.View +import android.view.View.GONE +import android.view.View.VISIBLE +import android.view.ViewStub +import android.widget.Chronometer +import android.widget.DateTimeView +import android.widget.ImageView +import android.widget.ProgressBar +import android.widget.TextView +import androidx.compose.runtime.Composable +import androidx.compose.runtime.key +import androidx.compose.ui.viewinterop.AndroidView +import androidx.core.view.isVisible +import com.android.internal.R +import com.android.internal.widget.BigPictureNotificationImageView +import com.android.internal.widget.CachingIconView +import com.android.internal.widget.ImageFloatingTextView +import com.android.internal.widget.NotificationExpandButton +import com.android.internal.widget.NotificationProgressBar +import com.android.internal.widget.NotificationRowIconView +import com.android.systemui.lifecycle.rememberViewModel +import com.android.systemui.res.R as systemuiR +import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel +import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel.Style +import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel.When +import com.android.systemui.statusbar.notification.promoted.ui.viewmodel.AODPromotedNotificationViewModel + +@Composable +fun AODPromotedNotification(viewModelFactory: AODPromotedNotificationViewModel.Factory) { + if (!PromotedNotificationUiAod.isEnabled) { + return + } + + val viewModel = + rememberViewModel(traceName = "AODPromotedNotification") { viewModelFactory.create() } + + val content = viewModel.content ?: return + + key(content.identity) { + AndroidView( + factory = { context -> + LayoutInflater.from(context) + .inflate(content.layoutResource, /* root= */ null) + .apply { setTag(viewUpdaterTagId, AODPromotedNotificationViewUpdater(this)) } + }, + update = { view -> + (view.getTag(viewUpdaterTagId) as AODPromotedNotificationViewUpdater).update( + content + ) + }, + ) + } +} + +private val PromotedNotificationContentModel.layoutResource: Int + get() { + return if (Flags.notificationsRedesignTemplates()) { + when (style) { + Style.BigPicture -> R.layout.notification_2025_template_expanded_big_picture + Style.BigText -> R.layout.notification_2025_template_expanded_big_text + Style.Call -> R.layout.notification_2025_template_expanded_call + Style.Progress -> R.layout.notification_2025_template_expanded_progress + Style.Ineligible -> 0 + } + } else { + when (style) { + Style.BigPicture -> R.layout.notification_template_material_big_picture + Style.BigText -> R.layout.notification_template_material_big_text + Style.Call -> R.layout.notification_template_material_big_call + Style.Progress -> R.layout.notification_template_material_progress + Style.Ineligible -> 0 + } + } + } + +private class AODPromotedNotificationViewUpdater(root: View) { + private val alertedIcon: View? = root.findViewById(R.id.alerted_icon) + private val alternateExpandTarget: View? = root.findViewById(R.id.alternate_expand_target) + private val appNameDivider: View? = root.findViewById(R.id.app_name_divider) + private val appNameText: TextView? = root.findViewById(R.id.app_name_text) + private val bigPicture: BigPictureNotificationImageView? = root.findViewById(R.id.big_picture) + private val bigText: ImageFloatingTextView? = root.findViewById(R.id.big_text) + private var chronometerStub: ViewStub? = root.findViewById(R.id.chronometer) + private var chronometer: Chronometer? = null + private val closeButton: View? = root.findViewById(R.id.close_button) + private val conversationText: TextView? = root.findViewById(R.id.conversation_text) + private val expandButton: NotificationExpandButton? = root.findViewById(R.id.expand_button) + private val headerText: TextView? = root.findViewById(R.id.header_text) + private val headerTextDivider: View? = root.findViewById(R.id.header_text_divider) + private val headerTextSecondary: TextView? = root.findViewById(R.id.header_text_secondary) + private val headerTextSecondaryDivider: View? = + root.findViewById(R.id.header_text_secondary_divider) + private val icon: NotificationRowIconView? = root.findViewById(R.id.icon) + private val leftIcon: ImageView? = root.findViewById(R.id.left_icon) + private val notificationProgressEndIcon: CachingIconView? = + root.findViewById(R.id.notification_progress_end_icon) + private val notificationProgressStartIcon: CachingIconView? = + root.findViewById(R.id.notification_progress_start_icon) + private val profileBadge: ImageView? = root.findViewById(R.id.profile_badge) + private val text: ImageFloatingTextView? = root.findViewById(R.id.text) + private val time: DateTimeView? = root.findViewById(R.id.time) + private val timeDivider: View? = root.findViewById(R.id.time_divider) + private val title: TextView? = root.findViewById(R.id.title) + private val verificationDivider: View? = root.findViewById(R.id.verification_divider) + private val verificationIcon: ImageView? = root.findViewById(R.id.verification_icon) + private val verificationText: TextView? = root.findViewById(R.id.verification_text) + + private var oldProgressStub = root.findViewById<View>(R.id.progress) as? ViewStub + private var oldProgress: ProgressBar? = null + private val newProgress = root.findViewById<View>(R.id.progress) as? NotificationProgressBar + + fun update(content: PromotedNotificationContentModel) { + when (content.style) { + Style.BigPicture -> updateBigPicture(content) + Style.BigText -> updateBigText(content) + Style.Call -> updateCall(content) + Style.Progress -> updateProgress(content) + Style.Ineligible -> {} + } + } + + private fun updateBigPicture(content: PromotedNotificationContentModel) { + updateHeader(content) + + updateTitle(title, content) + updateText(text, content) + + bigPicture?.visibility = GONE + } + + private fun updateBigText(content: PromotedNotificationContentModel) { + updateHeader(content) + + updateTitle(title, content) + updateText(bigText, content) + } + + private fun updateCall(content: PromotedNotificationContentModel) { + updateConversationHeader(content) + + updateText(text, content) + } + + private fun updateProgress(content: PromotedNotificationContentModel) { + updateHeader(content) + + updateTitle(title, content) + updateText(text, content) + + updateNewProgressBar(content) + } + + private fun updateNewProgressBar(content: PromotedNotificationContentModel) { + notificationProgressStartIcon?.visibility = GONE + notificationProgressEndIcon?.visibility = GONE + content.progress?.let { + newProgress?.setProgressModel(it.toBundle()) + newProgress?.visibility = VISIBLE + } ?: run { newProgress?.visibility = GONE } + } + + private fun updateHeader(content: PromotedNotificationContentModel) { + updateAppName(content) + updateTextView(headerTextSecondary, content.subText) + updateTitle(headerText, content) + updateTimeAndChronometer(content) + + updateHeaderDividers(content) + + leftIcon?.visibility = GONE + alternateExpandTarget?.visibility = GONE + expandButton?.visibility = GONE + closeButton?.visibility = GONE + } + + private fun updateHeaderDividers(content: PromotedNotificationContentModel) { + val hasAppName = content.appName != null && content.appName.isNotEmpty() + val hasSubText = content.subText != null && content.subText.isNotEmpty() + val hasHeader = content.title != null && content.title.isNotEmpty() + val hasTimeOrChronometer = content.time != null + + val hasTextBeforeSubText = hasAppName + val hasTextBeforeHeader = hasAppName || hasSubText + val hasTextBeforeTime = hasAppName || hasSubText || hasHeader + + val showDividerBeforeSubText = hasTextBeforeSubText && hasSubText + val showDividerBeforeHeader = hasTextBeforeHeader && hasHeader + val showDividerBeforeTime = hasTextBeforeTime && hasTimeOrChronometer + + headerTextSecondaryDivider?.isVisible = showDividerBeforeSubText + headerTextDivider?.isVisible = showDividerBeforeHeader + timeDivider?.isVisible = showDividerBeforeTime + } + + private fun updateConversationHeader(content: PromotedNotificationContentModel) { + updateTitle(conversationText, content) + updateAppName(content) + updateTimeAndChronometer(content) + + updateConversationHeaderDividers(content) + + updateTextView(verificationText, content.verificationText) + } + + private fun updateConversationHeaderDividers(content: PromotedNotificationContentModel) { + val hasTitle = content.title != null + val hasAppName = content.appName != null + val hasTimeOrChronometer = content.time != null + val hasVerification = content.verificationIcon != null || content.verificationText != null + + val hasTextBeforeAppName = hasTitle + val hasTextBeforeTime = hasTitle || hasAppName + val hasTextBeforeVerification = hasTitle || hasAppName || hasTimeOrChronometer + + val showDividerBeforeAppName = hasTextBeforeAppName && hasAppName + val showDividerBeforeTime = hasTextBeforeTime && hasTimeOrChronometer + val showDividerBeforeVerification = hasTextBeforeVerification && hasVerification + + appNameDivider?.isVisible = showDividerBeforeAppName + timeDivider?.isVisible = showDividerBeforeTime + verificationDivider?.isVisible = showDividerBeforeVerification + } + + private fun updateAppName(content: PromotedNotificationContentModel) { + updateTextView(appNameText, content.appName) + } + + private fun updateTitle(titleView: TextView?, content: PromotedNotificationContentModel) { + updateTextView(titleView, content.title, color = Color.PrimaryText) + } + + private fun updateTimeAndChronometer(content: PromotedNotificationContentModel) { + setTextViewColor(time, Color.SecondaryText) + setTextViewColor(chronometer, Color.SecondaryText) + + val timeValue = content.time + + if (timeValue == null) { + time?.visibility = GONE + chronometer?.visibility = GONE + } else if (timeValue.mode == When.Mode.BasicTime) { + time?.visibility = VISIBLE + time?.setTime(timeValue.time) + chronometer?.visibility = GONE + } else { + inflateChronometer() + + time?.visibility = GONE + chronometer?.visibility = VISIBLE + chronometer?.base = timeValue.time + chronometer?.isCountDown = (timeValue.mode == When.Mode.CountDown) + chronometer?.setStarted(true) + } + } + + private fun inflateChronometer() { + if (chronometer != null) { + return + } + + chronometer = chronometerStub?.inflate() as Chronometer + chronometerStub = null + } + + private fun updateText( + view: ImageFloatingTextView?, + content: PromotedNotificationContentModel, + ) { + view?.setHasImage(false) + updateTextView(view, content.text) + } + + private fun updateTextView( + view: TextView?, + text: CharSequence?, + color: Color = Color.SecondaryText, + ) { + setTextViewColor(view, color) + + if (text != null && text.isNotEmpty()) { + view?.text = text + view?.visibility = VISIBLE + } else { + view?.text = "" + view?.visibility = GONE + } + } + + private fun setTextViewColor(view: TextView?, color: Color) { + view?.setTextColor(color.color.toInt()) + } + + private enum class Color(val color: UInt) { + Background(0x00000000u), + PrimaryText(0xFFFFFFFFu), + SecondaryText(0xFFCCCCCCu), + } +} + +private val viewUpdaterTagId = systemuiR.id.aod_promoted_notification_view_updater_tag diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLogger.kt index a43f8dbc1b5d..4ccdc65ba91a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLogger.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.notification.promoted +import androidx.constraintlayout.widget.ConstraintSet import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.LogLevel.ERROR import com.android.systemui.log.core.LogLevel.INFO @@ -65,6 +66,58 @@ constructor(@PromotedNotificationLog private val buffer: LogBuffer) { { "extraction succeeded: $str2 for $str1" }, ) } + + fun logBinderBindSkipped(reason: String) { + buffer.log( + AOD_VIEW_BINDER_TAG, + INFO, + { str1 = reason }, + { "binder skipped binding: $str1" }, + ) + } + + fun logBinderAttached() { + buffer.log(AOD_VIEW_BINDER_TAG, INFO, "binder attached") + } + + fun logBinderDetached() { + buffer.log(AOD_VIEW_BINDER_TAG, INFO, "binder detached") + } + + fun logBinderBoundNotification() { + buffer.log(AOD_VIEW_BINDER_TAG, INFO, "binder bound notification") + } + + fun logBinderUnboundNotification() { + buffer.log(AOD_VIEW_BINDER_TAG, INFO, "binder unbound notification") + } + + fun logSectionAddedViews() { + buffer.log(AOD_SECTION_TAG, INFO, "section added views") + } + + fun logSectionBoundData() { + buffer.log(AOD_SECTION_TAG, INFO, "section bound data") + } + + fun logSectionAppliedConstraints() { + buffer.log(AOD_SECTION_TAG, INFO, "section applied constraints") + } + + fun logSectionRemovedViews() { + buffer.log(AOD_SECTION_TAG, INFO, "section removed views") + } } private const val EXTRACTION_TAG = "PromotedNotificationContentExtractor" +private const val AOD_VIEW_BINDER_TAG = "AODPromotedNotificationViewBinder" +private const val AOD_SECTION_TAG = "AodPromotedNotificationSection" + +private fun visibilityToString(visibility: Int): String { + return when (visibility) { + ConstraintSet.VISIBLE -> "VISIBLE" + ConstraintSet.INVISIBLE -> "INVISIBLE" + ConstraintSet.GONE -> "GONE" + else -> "UNKNOWN($visibility)" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/ui/viewmodel/AODPromotedNotificationViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/ui/viewmodel/AODPromotedNotificationViewModel.kt index adfa6a10814d..3bbd217dcab4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/ui/viewmodel/AODPromotedNotificationViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/ui/viewmodel/AODPromotedNotificationViewModel.kt @@ -16,31 +16,32 @@ package com.android.systemui.statusbar.notification.promoted.ui.viewmodel -import com.android.systemui.dagger.SysUISingleton +import androidx.compose.runtime.getValue +import com.android.systemui.lifecycle.ExclusiveActivatable +import com.android.systemui.lifecycle.Hydrator import com.android.systemui.statusbar.notification.promoted.domain.interactor.AODPromotedNotificationInteractor import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel -import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel.Identity -import javax.inject.Inject -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.filterNotNull -import kotlinx.coroutines.flow.map +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject -@SysUISingleton class AODPromotedNotificationViewModel -@Inject -constructor(interactor: AODPromotedNotificationInteractor) { - private val content: Flow<PromotedNotificationContentModel?> = interactor.content - private val identity: Flow<Identity?> = content.mapNonNullsKeepingNulls { it.identity } +@AssistedInject +constructor(interactor: AODPromotedNotificationInteractor) : ExclusiveActivatable() { + private val hydrator = Hydrator("AODPromotedNotificationViewModel.hydrator") - val notification: Flow<PromotedNotificationViewModel?> = - identity.distinctUntilChanged().mapNonNullsKeepingNulls { identity -> - val updates = interactor.content.filterNotNull().filter { it.identity == identity } - PromotedNotificationViewModel(identity, updates) - } -} + val content: PromotedNotificationContentModel? by + hydrator.hydratedStateOf( + traceName = "content", + initialValue = null, + source = interactor.content, + ) + + override suspend fun onActivated(): Nothing { + hydrator.activate() + } -private fun <T, R> Flow<T?>.mapNonNullsKeepingNulls(block: (T) -> R): Flow<R?> = map { - it?.let(block) + @AssistedFactory + interface Factory { + fun create(): AODPromotedNotificationViewModel + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BundleNotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BundleNotificationInfo.java index aad618d50067..9c6e41c482b6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BundleNotificationInfo.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BundleNotificationInfo.java @@ -64,11 +64,11 @@ public class BundleNotificationInfo extends NotificationInfo { boolean isNonblockable, boolean wasShownHighPriority, AssistantFeedbackController assistantFeedbackController, - MetricsLogger metricsLogger) throws RemoteException { + MetricsLogger metricsLogger, OnClickListener onCloseClick) throws RemoteException { super.bindNotification(pm, iNotificationManager, onUserInteractionCallback, channelEditorDialogController, pkg, notificationChannel, entry, onSettingsClick, onAppSettingsClick, uiEventLogger, isDeviceProvisioned, isNonblockable, - wasShownHighPriority, assistantFeedbackController, metricsLogger); + wasShownHighPriority, assistantFeedbackController, metricsLogger, onCloseClick); // Additionally, bind the feedback button. ComponentName assistant = iNotificationManager.getAllowedNotificationAssistant(); 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 598ff09ba3b0..d986aaebc0f8 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 @@ -21,6 +21,7 @@ import static android.app.Notification.Action.SEMANTIC_ACTION_MARK_CONVERSATION_ import static android.service.notification.NotificationListenerService.REASON_CANCEL; import static com.android.systemui.flags.Flags.ENABLE_NOTIFICATIONS_SIMULATE_SLOW_MEASURE; +import static com.android.systemui.Flags.notificationsPinnedHunInShade; import static com.android.systemui.statusbar.notification.collection.NotificationEntry.DismissState.PARENT_DISMISSED; import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP; import static com.android.systemui.statusbar.policy.RemoteInputView.FOCUS_ANIMATION_MIN_SCALE; @@ -106,6 +107,7 @@ import com.android.systemui.statusbar.notification.headsup.HeadsUpManager; import com.android.systemui.statusbar.notification.headsup.PinnedStatus; import com.android.systemui.statusbar.notification.logging.NotificationCounters; import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; +import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiForceExpanded; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation; import com.android.systemui.statusbar.notification.row.shared.LockscreenOtpRedaction; @@ -163,6 +165,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private boolean mShowSnooze = false; private boolean mIsFaded; + private boolean mIsPromotedOngoing = false; + /** * Listener for when {@link ExpandableNotificationRow} is laid out. */ @@ -196,6 +200,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private int mMaxSmallHeightBeforeS; private int mMaxSmallHeight; private int mMaxExpandedHeight; + private int mMaxExpandedHeightForPromotedOngoing; private int mNotificationLaunchHeight; private boolean mMustStayOnScreen; @@ -252,6 +257,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private NotificationGuts mGuts; private NotificationEntry mEntry; private String mAppName; + private NotificationRebindingTracker mRebindingTracker; private FalsingManager mFalsingManager; /** @@ -330,6 +336,15 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private boolean mSaveSpaceOnLockscreen; /** + * It is added for unit testing purpose. + * Please do not use it for other purposes. + */ + @VisibleForTesting + public void setIgnoreLockscreenConstraints(boolean ignoreLockscreenConstraints) { + mIgnoreLockscreenConstraints = ignoreLockscreenConstraints; + } + + /** * True if we use intrinsic height regardless of vertical space available on lockscreen. */ private boolean mIgnoreLockscreenConstraints; @@ -803,6 +818,13 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } private void updateLimitsForView(NotificationContentView layout) { + final int maxExpandedHeight; + if (isPromotedOngoing()) { + maxExpandedHeight = mMaxExpandedHeightForPromotedOngoing; + } else { + maxExpandedHeight = mMaxExpandedHeight; + } + View contractedView = layout.getContractedChild(); boolean customView = contractedView != null && contractedView.getId() @@ -823,7 +845,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView smallHeight = mMaxSmallHeightBeforeS; } } else if (isCallLayout) { - smallHeight = mMaxExpandedHeight; + smallHeight = maxExpandedHeight; } else { smallHeight = mMaxSmallHeight; } @@ -847,7 +869,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView if (headsUpWrapper != null) { headsUpHeight = Math.max(headsUpHeight, headsUpWrapper.getMinLayoutHeight()); } - layout.setHeights(smallHeight, headsUpHeight, mMaxExpandedHeight); + + layout.setHeights(smallHeight, headsUpHeight, maxExpandedHeight); } @NonNull @@ -1257,6 +1280,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView if (mIsSummaryWithChildren) { return mChildrenContainer.getIntrinsicHeight(); } + if (isPromotedOngoing()) { + return getMaxExpandHeight(); + } if (mExpandedWhenPinned) { return Math.max(getMaxExpandHeight(), getHeadsUpHeight()); } else if (android.app.Flags.compactHeadsUpNotification() @@ -1526,7 +1552,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView // TODO: Move content inflation logic out of this call RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry); params.setNeedsReinflation(true); - mRowContentBindStage.requestRebind(mEntry, null /* callback */); + + var rebindEndCallback = mRebindingTracker.trackRebinding(mEntry.getKey()); + mRowContentBindStage.requestRebind(mEntry, (e) -> rebindEndCallback.onFinished()); Trace.endSection(); } @@ -2015,9 +2043,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView SmartReplyConstants smartReplyConstants, SmartReplyController smartReplyController, IStatusBarService statusBarService, - UiEventLogger uiEventLogger) { + UiEventLogger uiEventLogger, + NotificationRebindingTracker notificationRebindingTracker) { mEntry = entry; mAppName = appName; + mRebindingTracker = notificationRebindingTracker; if (mMenuRow == null) { mMenuRow = new NotificationMenuRow(mContext, peopleNotificationIdentifier); } @@ -2072,6 +2102,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } mMaxExpandedHeight = NotificationUtils.getFontScaledHeight(mContext, R.dimen.notification_max_height); + mMaxExpandedHeightForPromotedOngoing = NotificationUtils.getFontScaledHeight(mContext, + R.dimen.notification_max_height_for_promoted_ongoing); mMaxHeadsUpHeightBeforeN = NotificationUtils.getFontScaledHeight(mContext, R.dimen.notification_max_heads_up_height_legacy); mMaxHeadsUpHeightBeforeP = NotificationUtils.getFontScaledHeight(mContext, @@ -2757,6 +2789,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView if (mIsSummaryWithChildren && !shouldShowPublic()) { return !mChildrenExpanded; } + if (isPromotedOngoing()) { + return false; + } return mEnableNonGroupedNotificationExpand && mExpandable; } @@ -2765,6 +2800,18 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mPrivateLayout.updateExpandButtons(isExpandable()); } + /** + * Set this notification to be promoted ongoing + */ + public void setPromotedOngoing(boolean promotedOngoing) { + if (PromotedNotificationUiForceExpanded.isUnexpectedlyInLegacyMode()) { + return; + } + + mIsPromotedOngoing = promotedOngoing; + setExpandable(!mIsPromotedOngoing); + } + @Override public void setClipToActualHeight(boolean clipToActualHeight) { super.setClipToActualHeight(clipToActualHeight || isUserLocked()); @@ -2834,6 +2881,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } public void setUserLocked(boolean userLocked) { + if (isPromotedOngoing()) return; + mUserLocked = userLocked; mPrivateLayout.setUserExpanding(userLocked); // This is intentionally not guarded with mIsSummaryWithChildren since we might have had @@ -2995,6 +3044,35 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } } + public boolean isPromotedOngoing() { + return PromotedNotificationUiForceExpanded.isEnabled() && mIsPromotedOngoing; + } + + private boolean isPromotedNotificationExpanded(boolean allowOnKeyguard) { + // public view in non group notifications is always collapsed. + if (shouldShowPublic()) { + return false; + } + // RON will always be expanded when it is not on keyguard. + if (!mOnKeyguard) { + return true; + } + // RON will always be expanded when it is allowed on keyguard. + // allowOnKeyguard is used for getting the maximum height by NotificationContentView and + // NotificationChildrenContainer. + if (allowOnKeyguard) { + return true; + } + + // RON will be expanded when it needs to ignore lockscreen constraints. + if (mIgnoreLockscreenConstraints) { + return true; + } + + // RON will need be collapsed when it needs to save space on the lock screen. + return !mSaveSpaceOnLockscreen; + } + /** * Check whether the view state is currently expanded. This is given by the system in {@link * #setSystemExpanded(boolean)} and can be overridden by user expansion or @@ -3008,6 +3086,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } public boolean isExpanded(boolean allowOnKeyguard) { + if (isPromotedOngoing()) { + return isPromotedNotificationExpanded(allowOnKeyguard); + } + return (!shouldShowPublic()) && (!mOnKeyguard || allowOnKeyguard) && (!hasUserChangedExpansion() && (isSystemExpanded() || isSystemChildExpanded()) @@ -3179,7 +3261,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView @Override public boolean mustStayOnScreen() { - return mIsHeadsUp && mMustStayOnScreen; + // Must stay on screen in the open shade regardless how much the stack is scrolled if: + // 1. Is HUN and not marked as seen yet (isHeadsUp && mustStayOnScreen) + // 2. Is an FSI HUN (isPinned) + return mIsHeadsUp && mMustStayOnScreen || notificationsPinnedHunInShade() && isPinned(); } /** @@ -4006,6 +4091,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView + (!shouldShowPublic() && mIsSummaryWithChildren)); pw.print(", mShowNoBackground: " + mShowNoBackground); pw.print(", clipBounds: " + getClipBounds()); + if (PromotedNotificationUiForceExpanded.isEnabled()) { + pw.print(", isPromotedOngoing: " + isPromotedOngoing()); + pw.print(", isExpandable: " + isExpandable()); + pw.print(", mExpandable: " + mExpandable); + } pw.println(); if (NotificationContentView.INCLUDE_HEIGHTS_TO_DUMP) { 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 e06280e36bc8..626230353bd7 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 @@ -106,6 +106,7 @@ public class ExpandableNotificationRowController implements NotifViewController private final NotificationGutsManager mNotificationGutsManager; private final OnUserInteractionCallback mOnUserInteractionCallback; private final FalsingManager mFalsingManager; + private final NotificationRebindingTracker mNotificationRebindingTracker; private final FeatureFlagsClassic mFeatureFlags; private final boolean mAllowLongPress; private final PeopleNotificationIdentifier mPeopleNotificationIdentifier; @@ -275,7 +276,8 @@ public class ExpandableNotificationRowController implements NotifViewController NotificationDismissibilityProvider dismissibilityProvider, IStatusBarService statusBarService, UiEventLogger uiEventLogger, - MSDLPlayer msdlPlayer) { + MSDLPlayer msdlPlayer, + NotificationRebindingTracker notificationRebindingTracker) { mView = view; mListContainer = listContainer; mRemoteInputViewSubcomponentFactory = rivSubcomponentFactory; @@ -295,6 +297,7 @@ public class ExpandableNotificationRowController implements NotifViewController mNotificationGutsManager = notificationGutsManager; mOnUserInteractionCallback = onUserInteractionCallback; mFalsingManager = falsingManager; + mNotificationRebindingTracker = notificationRebindingTracker; mOnFeedbackClickListener = mNotificationGutsManager::openGuts; mAllowLongPress = allowLongPress; mFeatureFlags = featureFlags; @@ -343,7 +346,8 @@ public class ExpandableNotificationRowController implements NotifViewController mSmartReplyConstants, mSmartReplyController, mStatusBarService, - mUiEventLogger + mUiEventLogger, + mNotificationRebindingTracker ); mView.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); if (mAllowLongPress) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionBackgroundDrawable.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionBackgroundDrawable.kt index 793b3b8b1e42..6aa5e405f29c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionBackgroundDrawable.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/MagicActionBackgroundDrawable.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.notification.row +import android.animation.ValueAnimator import android.content.Context import android.graphics.BlendMode import android.graphics.Canvas @@ -38,8 +39,8 @@ import androidx.compose.ui.viewinterop.AndroidView import com.android.internal.graphics.ColorUtils import com.android.systemui.res.R import com.android.systemui.surfaceeffects.shaderutil.ShaderUtilLibrary -import kotlin.math.max -import kotlin.math.roundToInt +import com.android.wm.shell.shared.animation.Interpolators +import kotlin.math.min /** * A background style for smarter-smart-actions. The style is composed by a simplex3d noise, @@ -48,7 +49,7 @@ import kotlin.math.roundToInt class MagicActionBackgroundDrawable( context: Context, primaryContainer: Int? = null, - private val seed: Float = 0f, + seed: Float = 0f, ) : Drawable() { private val pixelDensity = context.resources.displayMetrics.density @@ -60,17 +61,15 @@ class MagicActionBackgroundDrawable( .toFloat() private val buttonShape = Path() private val paddingVertical = - context.resources - .getDimensionPixelSize(R.dimen.smart_reply_button_padding_vertical) - .toFloat() + context.resources.getDimensionPixelSize(R.dimen.smart_action_button_icon_padding).toFloat() /** The color of the button background. */ private val mainColor = primaryContainer ?: context.getColor(com.android.internal.R.color.materialColorPrimaryContainer) - /** Slightly dimmed down version of [mainColor] used on the simplex noise. */ - private val dimColor: Int + /** Slightly brighter version of [mainColor] used on the simplex noise. */ + private val effectColor: Int get() { val labColor = arrayOf(0.0, 0.0, 0.0).toDoubleArray() ColorUtils.colorToLAB(mainColor, labColor) @@ -78,56 +77,97 @@ class MagicActionBackgroundDrawable( return ColorUtils.CAMToColor( camColor.hue, camColor.chroma, - max(0f, (labColor[0] - 20).toFloat()), + min(100f, (labColor[0] + 10).toFloat()), ) } private val bgShader = MagicActionBackgroundShader() private val bgPaint = Paint() private val outlinePaint = Paint() + private val gradientAnimator = + ValueAnimator.ofFloat(0f, 1f).apply { + duration = 2500 + interpolator = Interpolators.LINEAR + addUpdateListener { invalidateSelf() } + } + private val turbulenceAnimator = + ValueAnimator.ofFloat(seed, seed + TURBULENCE_MOVEMENT).apply { + duration = ANIMATION_DURATION + interpolator = Interpolators.LINEAR + addUpdateListener { invalidateSelf() } + start() + } + private val effectFadeAnimation = + ValueAnimator.ofFloat(0f, 1f).apply { + duration = 1000 + startDelay = ANIMATION_DURATION - 1000L + interpolator = Interpolators.STANDARD_DECELERATE + addUpdateListener { invalidateSelf() } + } init { bgShader.setColorUniform("in_color", mainColor) - bgShader.setColorUniform("in_dimColor", dimColor) + bgShader.setColorUniform("in_effectColor", effectColor) bgPaint.shader = bgShader outlinePaint.style = Paint.Style.STROKE // Stroke is doubled in width and then clipped, to avoid anti-aliasing artifacts at the edge // of the rectangle. outlinePaint.strokeWidth = outlineStrokeWidth * 2 outlinePaint.blendMode = BlendMode.SCREEN - outlinePaint.alpha = (255 * 0.32f).roundToInt() + outlinePaint.alpha = OUTLINE_ALPHA + + animate() + } + + private fun animate() { + turbulenceAnimator.start() + gradientAnimator.start() + effectFadeAnimation.start() } override fun draw(canvas: Canvas) { + updateShaders() + // We clip instead of drawing 2 rounded rects, otherwise there will be artifacts where // around the button background and the outline. + canvas.save() canvas.clipPath(buttonShape) + canvas.drawPath(buttonShape, bgPaint) + canvas.drawPath(buttonShape, outlinePaint) + canvas.restore() + } - canvas.drawRect(bounds, bgPaint) - canvas.drawRoundRect( - bounds.left.toFloat(), - bounds.top + paddingVertical, - bounds.right.toFloat(), - bounds.bottom - paddingVertical, - cornerRadius, - cornerRadius, - outlinePaint, - ) + private fun updateShaders() { + val effectAlpha = 1f - effectFadeAnimation.animatedValue as Float + val turbulenceZ = turbulenceAnimator.animatedValue as Float + bgShader.setFloatUniform("in_sparkleMove", turbulenceZ * 1000) + bgShader.setFloatUniform("in_noiseMove", 0f, 0f, turbulenceZ) + bgShader.setFloatUniform("in_turbulenceAlpha", effectAlpha) + bgShader.setFloatUniform("in_spkarkleAlpha", SPARKLE_ALPHA * effectAlpha) + val gradientOffset = gradientAnimator.animatedValue as Float * bounds.width() + val outlineGradient = + LinearGradient( + gradientOffset + bounds.left.toFloat(), + 0f, + gradientOffset + bounds.right.toFloat(), + 0f, + mainColor, + ColorUtils.setAlphaComponent(mainColor, 0), + Shader.TileMode.MIRROR, + ) + outlinePaint.shader = outlineGradient } override fun onBoundsChange(bounds: Rect) { super.onBoundsChange(bounds) val width = bounds.width().toFloat() - val height = bounds.height() - paddingVertical * 2 + val height = bounds.height().toFloat() if (width == 0f || height == 0f) return bgShader.setFloatUniform("in_gridNum", NOISE_SIZE) - bgShader.setFloatUniform("in_spkarkleAlpha", SPARKLE_ALPHA) - bgShader.setFloatUniform("in_noiseMove", 0f, 0f, 0f) bgShader.setFloatUniform("in_size", width, height) bgShader.setFloatUniform("in_aspectRatio", width / height) - bgShader.setFloatUniform("in_time", seed) bgShader.setFloatUniform("in_pixelDensity", pixelDensity) buttonShape.reset() @@ -140,18 +180,6 @@ class MagicActionBackgroundDrawable( cornerRadius, Path.Direction.CW, ) - - val outlineGradient = - LinearGradient( - bounds.left.toFloat(), - 0f, - bounds.right.toFloat(), - 0f, - mainColor, - ColorUtils.setAlphaComponent(mainColor, 0), - Shader.TileMode.CLAMP, - ) - outlinePaint.shader = outlineGradient } override fun setAlpha(alpha: Int) { @@ -168,10 +196,15 @@ class MagicActionBackgroundDrawable( companion object { /** Smoothness of the turbulence. Larger numbers yield more detail. */ - private const val NOISE_SIZE = 0.7f - + private const val NOISE_SIZE = 0.57f /** Strength of the sparkles overlaid on the turbulence. */ private const val SPARKLE_ALPHA = 0.15f + /** Alpha (0..255) of the button outline */ + private const val OUTLINE_ALPHA = 82 + /** Turbulence grid size */ + private const val TURBULENCE_MOVEMENT = 4.3f + /** Total animation duration in millis */ + private const val ANIMATION_DURATION = 5000L } } @@ -183,24 +216,25 @@ private class MagicActionBackgroundShader : RuntimeShader(SHADER) { """ uniform float in_gridNum; uniform vec3 in_noiseMove; + uniform half in_sparkleMove; uniform vec2 in_size; uniform float in_aspectRatio; - uniform half in_time; uniform half in_pixelDensity; + uniform float in_turbulenceAlpha; uniform float in_spkarkleAlpha; layout(color) uniform vec4 in_color; - layout(color) uniform vec4 in_dimColor; + layout(color) uniform vec4 in_effectColor; """ private const val MAIN_SHADER = """vec4 main(vec2 p) { vec2 uv = p / in_size.xy; uv.x *= in_aspectRatio; vec3 noiseP = vec3(uv + in_noiseMove.xy, in_noiseMove.z) * in_gridNum; - half luma = 1.0 - getLuminosity(half3(simplex3d(noiseP))); - half4 turbulenceColor = mix(in_color, in_dimColor, luma); - float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_time); + half luma = getLuminosity(half3(simplex3d(noiseP))); + half4 turbulenceColor = mix(in_color, in_effectColor, luma * in_turbulenceAlpha); + float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_sparkleMove); sparkle = min(sparkle * in_spkarkleAlpha, in_spkarkleAlpha); - return turbulenceColor + half4(half3(sparkle), 1.0); + return saturate(turbulenceColor + half4(sparkle)); } """ private const val SHADER = UNIFORMS + ShaderUtilLibrary.SHADER_LIB + MAIN_SHADER diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java index 57fe24f40acb..c0dbb37c1b36 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java @@ -57,6 +57,7 @@ import com.android.systemui.statusbar.notification.ConversationNotificationProce import com.android.systemui.statusbar.notification.InflationException; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractor; +import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiForceExpanded; import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel; import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation; import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation; @@ -1111,6 +1112,10 @@ public class NotificationContentInflater implements NotificationRowContentBinder entry.setHeadsUpStatusBarText(result.headsUpStatusBarText); entry.setHeadsUpStatusBarTextPublic(result.headsUpStatusBarTextPublic); + if (PromotedNotificationUiForceExpanded.isEnabled()) { + row.setPromotedOngoing(entry.isOngoingPromoted()); + } + Trace.endAsyncSection(APPLY_TRACE_METHOD, System.identityHashCode(row)); if (endListener != null) { endListener.onAsyncInflationFinished(entry); 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 9712db8a1812..b1e5b22f9b1a 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 @@ -70,10 +70,10 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewListener; import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewManager; +import com.android.systemui.statusbar.notification.headsup.HeadsUpManager; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; import com.android.systemui.statusbar.phone.CentralSurfaces; import com.android.systemui.statusbar.policy.DeviceProvisionedController; -import com.android.systemui.statusbar.notification.headsup.HeadsUpManager; import com.android.systemui.util.kotlin.JavaAdapter; import com.android.systemui.wmshell.BubblesManager; @@ -422,7 +422,8 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta row.getIsNonblockable(), mHighPriorityProvider.isHighPriority(row.getEntry()), mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + row.getCloseButtonOnClickListener(row)); } /** @@ -476,7 +477,8 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta row.getIsNonblockable(), mHighPriorityProvider.isHighPriority(row.getEntry()), mAssistantFeedbackController, - mMetricsLogger); + mMetricsLogger, + row.getCloseButtonOnClickListener(row)); } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java index 20120991b5ac..8d26f94ced21 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java @@ -202,7 +202,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G boolean isNonblockable, boolean wasShownHighPriority, AssistantFeedbackController assistantFeedbackController, - MetricsLogger metricsLogger) + MetricsLogger metricsLogger, OnClickListener onCloseClick) throws RemoteException { mINotificationManager = iNotificationManager; mMetricsLogger = metricsLogger; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java index 6e8ec9576f80..bf738aa1128f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java @@ -42,6 +42,7 @@ import android.widget.FrameLayout.LayoutParams; import com.android.app.animation.Interpolators; import com.android.internal.annotations.VisibleForTesting; +import com.android.systemui.Flags; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.res.R; import com.android.systemui.statusbar.AlphaOptimizedImageView; @@ -270,6 +271,10 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl mInfoItem = createPartialConversationItem(mContext); } else if (personNotifType >= PeopleNotificationIdentifier.TYPE_FULL_PERSON) { mInfoItem = createConversationItem(mContext); + } else if (android.app.Flags.uiRichOngoing() + && Flags.permissionHelperUiRichOngoing() + && entry.getSbn().getNotification().isPromotedOngoing()) { + mInfoItem = createPromotedItem(mContext); } else { mInfoItem = createInfoItem(mContext); } @@ -682,6 +687,16 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl R.drawable.ic_settings); } + static NotificationMenuItem createPromotedItem(Context context) { + Resources res = context.getResources(); + String infoDescription = res.getString(R.string.notification_menu_gear_description); + PromotedNotificationInfo infoContent = + (PromotedNotificationInfo) LayoutInflater.from(context).inflate( + R.layout.promoted_notification_info, null, false); + return new NotificationMenuItem(context, infoDescription, infoContent, + R.drawable.ic_settings); + } + static NotificationMenuItem createBundleItem(Context context) { Resources res = context.getResources(); String infoDescription = res.getString(R.string.notification_menu_gear_description); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRebindingTracker.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRebindingTracker.kt new file mode 100644 index 000000000000..40dbdf27c920 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRebindingTracker.kt @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.app.tracing.FlowTracing.traceEach +import com.android.app.tracing.TraceUtils.traceAsyncClosable +import com.android.app.tracing.TrackGroupUtils.trackGroup +import com.android.systemui.CoreStartable +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch + +/** + * Tracks notification rebindings in progress as a result of a configuration change (such as density + * or font size) + */ +@SysUISingleton +class NotificationRebindingTracker +@Inject +constructor( + activeNotificationsInteractor: ActiveNotificationsInteractor, + @Background private val bgScope: CoroutineScope, + @Application private val appScope: CoroutineScope, +) : CoreStartable { + + private val rebindingKeys = MutableStateFlow(emptySet<String>()) + private val activeKeys: Flow<Set<String>> = + activeNotificationsInteractor.allRepresentativeNotifications + .map { notifications: Map<String, *> -> + notifications.map { (notifKey, _) -> notifKey }.toSet() + } + .traceEach(trackGroup("shade", "activeKeys")) + + /** + * Emits the current number of active notification rebinding in progress. + * + * Note the usaged of the [appScope] instead of the bg one is intentional, as we need the value + * immediately also in the same frame if it changes. + */ + val rebindingInProgressCount: StateFlow<Int> = + rebindingKeys + .map { it.size } + .traceEach(trackGroup("shade", "rebindingInProgressCount"), traceEmissionCount = true) + .stateIn(appScope, started = SharingStarted.Eagerly, initialValue = 0) + + override fun start() { + syncRebindingKeysWithActiveKeys() + } + + private fun syncRebindingKeysWithActiveKeys() { + // Let's make sure that the "rebindingKeys" set doesn't contain entries that are not active + // anymore. + bgScope.launch { + activeKeys.collect { activeKeys -> + rebindingKeys.update { currentlyBeingInflated -> + currentlyBeingInflated.intersect(activeKeys) + } + } + } + } + + /** Should be called when the inflation begins */ + fun trackRebinding(key: String): RebindFinishedCallback { + val endTrace = + traceAsyncClosable( + trackGroupName = "Notifications", + trackName = "Rebinding", + sliceName = "Rebinding in progress for $key", + ) + rebindingKeys.value += key + return RebindFinishedCallback { + endTrace() + rebindingKeys.value -= key + } + } + + /** + * Callback to notify the end of a rebiding. Views are expected to be in the hierarchy when this + * is called. + */ + fun interface RebindFinishedCallback { + fun onFinished() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt index 49e38def98a6..0b299d965b09 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt @@ -799,6 +799,7 @@ constructor( } redacted.setLargeIcon(original.getLargeIcon()) redacted.setSmallIcon(original.smallIcon) + redacted.setWhen(original.getWhen()) return redacted } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfo.java new file mode 100644 index 000000000000..e4a0fa5b534e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfo.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 android.app.INotificationManager; +import android.app.NotificationChannel; +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.RemoteException; +import android.service.notification.StatusBarNotification; +import android.util.AttributeSet; +import android.util.Log; +import android.view.View; + +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.UiEventLogger; +import com.android.systemui.res.R; +import com.android.systemui.statusbar.notification.AssistantFeedbackController; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; + +/** + * The guts of a notification revealed when performing a long press, specifically + * for notifications that are shown as promoted. Contains extra controls to allow user to revoke + * app permissions for sending promoted notifications. + */ +public class PromotedNotificationInfo extends NotificationInfo { + private static final String TAG = "PromotedNotifInfoGuts"; + private INotificationManager mNotificationManager; + + public PromotedNotificationInfo(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + public void bindNotification( + PackageManager pm, + INotificationManager iNotificationManager, + OnUserInteractionCallback onUserInteractionCallback, + ChannelEditorDialogController channelEditorDialogController, + String pkg, + NotificationChannel notificationChannel, + NotificationEntry entry, + OnSettingsClickListener onSettingsClick, + OnAppSettingsClickListener onAppSettingsClick, + UiEventLogger uiEventLogger, + boolean isDeviceProvisioned, + boolean isNonblockable, + boolean wasShownHighPriority, + AssistantFeedbackController assistantFeedbackController, + MetricsLogger metricsLogger, OnClickListener onCloseClick) throws RemoteException { + super.bindNotification(pm, iNotificationManager, onUserInteractionCallback, + channelEditorDialogController, pkg, notificationChannel, entry, onSettingsClick, + onAppSettingsClick, uiEventLogger, isDeviceProvisioned, isNonblockable, + wasShownHighPriority, assistantFeedbackController, metricsLogger, onCloseClick); + + mNotificationManager = iNotificationManager; + + bindDismiss(entry.getSbn(), onCloseClick); + bindDemote(entry.getSbn(), pkg); + } + + + protected void bindDismiss(StatusBarNotification sbn, + View.OnClickListener onCloseClick) { + View dismissButton = findViewById(R.id.promoted_dismiss); + + dismissButton.setOnClickListener(onCloseClick); + dismissButton.setVisibility(!sbn.isNonDismissable() + && dismissButton.hasOnClickListeners() ? VISIBLE : GONE); + + } + + protected void bindDemote(StatusBarNotification sbn, String packageName) { + View demoteButton = findViewById(R.id.promoted_demote); + demoteButton.setOnClickListener(getDemoteClickListener(sbn, packageName)); + demoteButton.setVisibility(demoteButton.hasOnClickListeners() ? VISIBLE : GONE); + } + + private OnClickListener getDemoteClickListener(StatusBarNotification sbn, String packageName) { + return ((View unusedView) -> { + try { + // TODO(b/391661009): Signal AutomaticPromotionCoordinator here + mNotificationManager.setCanBePromoted(packageName, sbn.getUid(), false, true); + } catch (RemoteException e) { + Log.e(TAG, "Couldn't revoke live update permission", e); + } + }); + } +} + diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackRebindingHider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackRebindingHider.kt new file mode 100644 index 000000000000..dba2308ea34b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackRebindingHider.kt @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.stack + +import com.android.systemui.dagger.SysUISingleton +import javax.inject.Inject + +/** + * Allows to hide the content of the notification stack during notification rebinding. + * + * Note that this is mainly created to allow SystemUI variants without NSSL to provide their own + * implementations. + */ +interface NotificationStackRebindingHider { + /** Sets the Notification stack visibility. */ + fun setVisible(visible: Boolean, animated: Boolean) +} + +@SysUISingleton +class NotificationStackRebindingHiderImpl +@Inject +constructor(private val nsslController: NotificationStackScrollLayoutController) : + NotificationStackRebindingHider { + override fun setVisible(visible: Boolean, animated: Boolean) { + nsslController.updateContainerVisibilityForRebind(visible, animated) + } +} 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 f57107141f61..bf24c873c693 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 @@ -21,6 +21,7 @@ import static android.os.Trace.TRACE_TAG_APP; import static android.view.MotionEvent.ACTION_CANCEL; import static android.view.MotionEvent.ACTION_UP; +import static com.android.app.tracing.TrackGroupUtils.trackGroup; import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING; import static com.android.internal.jank.InteractionJankMonitor.CUJ_SHADE_CLEAR_ALL; import static com.android.systemui.Flags.notificationOverExpansionClippingFix; @@ -1395,6 +1396,16 @@ public class NotificationStackScrollLayout } } + @Override + public void setAlpha(float alpha) { + super.setAlpha(alpha); + if (Trace.isEnabled()) { + Trace.setCounter( + trackGroup(/* groupName= */ "shade", /* trackName= */ "NSSLResultingAlpha"), + (int) (alpha * 100)); + } + } + private boolean isCurrentlyAnimating() { return mStateAnimator.isRunning(); } 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 dc0fae80d041..8eaef3681e5c 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 @@ -249,9 +249,26 @@ public class NotificationStackScrollLayoutController implements Dumpable { } }; + private static final Property<NotificationStackScrollLayoutController, Float> + HIDE_DURING_REBINDING_PROPERTY = new Property<>(Float.class, + "HideNotificationsAlphaDuringRebind") { + @Override + public Float get(NotificationStackScrollLayoutController object) { + return object.mMaxAlphaForRebind; + } + + @Override + public void set(NotificationStackScrollLayoutController object, Float value) { + object.setMaxAlphaForRebind(value); + } + }; + @Nullable private ObjectAnimator mHideAlphaAnimator = null; + @Nullable + private ObjectAnimator mRebindAlphaAnimator = null; + private final Runnable mSensitiveStateChangedListener = new Runnable() { @Override public void run() { @@ -295,6 +312,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { private float mMaxAlphaForKeyguard = 1.0f; private String mMaxAlphaForKeyguardSource = "constructor"; private float mMaxAlphaForUnhide = 1.0f; + private float mMaxAlphaForRebind = 1.0f; private float mMaxAlphaFromView = 1.0f; /** @@ -1244,6 +1262,16 @@ public class NotificationStackScrollLayoutController implements Dumpable { } /** + * Max alpha for rebind. + * + * Used to hide notifications while rebiding is in progress (e.g. after a density change). + */ + public void setMaxAlphaForRebind(float alpha) { + mMaxAlphaForRebind = alpha; + updateAlpha(); + } + + /** * Applies a blur effect to the view. * * @param blurRadius Radius of blur @@ -1261,8 +1289,10 @@ public class NotificationStackScrollLayoutController implements Dumpable { private void updateAlpha() { if (mView != null) { - mView.setAlpha(Math.min(Math.min(mMaxAlphaFromView, mMaxAlphaForKeyguard), - Math.min(mMaxAlphaForUnhide, mMaxAlphaForGlanceableHub))); + float newAlpha = Math.min(mMaxAlphaForRebind, + Math.min(Math.min(mMaxAlphaFromView, mMaxAlphaForKeyguard), + Math.min(mMaxAlphaForUnhide, mMaxAlphaForGlanceableHub))); + mView.setAlpha(newAlpha); } } @@ -1289,6 +1319,26 @@ public class NotificationStackScrollLayoutController implements Dumpable { } } + /** + * Sets whether the nssl should be visible or not. Used during notification rebinding, to hide + * possible flickers that happen when display density changes. (e.g. as a result of the shade + * moving to a different display.) + */ + public void updateContainerVisibilityForRebind(boolean visible, boolean animate) { + if (mRebindAlphaAnimator != null) { + mRebindAlphaAnimator.cancel(); + } + + final float targetAlpha = visible ? 1f : 0f; + + if (animate) { + mRebindAlphaAnimator = createAlphaAnimatorForRebind(targetAlpha); + mRebindAlphaAnimator.start(); + } else { + HIDE_DURING_REBINDING_PROPERTY.set(this, targetAlpha); + } + } + private ObjectAnimator createAlphaAnimator(float targetAlpha) { final ObjectAnimator objectAnimator = ObjectAnimator .ofFloat(this, HIDE_ALPHA_PROPERTY, targetAlpha); @@ -1297,6 +1347,14 @@ public class NotificationStackScrollLayoutController implements Dumpable { return objectAnimator; } + private ObjectAnimator createAlphaAnimatorForRebind(float targetAlpha) { + final ObjectAnimator objectAnimator = ObjectAnimator + .ofFloat(this, HIDE_DURING_REBINDING_PROPERTY, targetAlpha); + objectAnimator.setInterpolator(STANDARD); + objectAnimator.setDuration(ANIMATION_DURATION_STANDARD); + return objectAnimator; + } + public float calculateAppearFraction(float height) { SceneContainerFlag.assertInLegacyMode(); return mView.calculateAppearFraction(height); @@ -1688,6 +1746,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { public void dump(@NonNull PrintWriter pw, @NonNull String[] args) { pw.println("mMaxAlphaFromView=" + mMaxAlphaFromView); pw.println("mMaxAlphaForUnhide=" + mMaxAlphaForUnhide); + pw.println("mMaxAlphaForRebind=" + mMaxAlphaForRebind); pw.println("mMaxAlphaForGlanceableHub=" + mMaxAlphaForGlanceableHub); pw.println("mMaxAlphaForKeyguard=" + mMaxAlphaForKeyguard); pw.println("mMaxAlphaForKeyguardSource=" + mMaxAlphaForKeyguardSource); 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 a96d972af2c4..08bc8f5d5bb9 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 @@ -29,6 +29,7 @@ import com.android.systemui.statusbar.LockscreenShadeTransitionController import com.android.systemui.statusbar.StatusBarState.KEYGUARD import com.android.systemui.statusbar.SysuiStatusBarStateController import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor +import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiForceExpanded import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.ExpandableView import com.android.systemui.statusbar.notification.shared.NotificationMinimalism @@ -463,7 +464,12 @@ constructor( var size = if (onLockscreen) { - if (view is ExpandableNotificationRow && view.entry.isStickyAndNotDemoted) { + if ( + view is ExpandableNotificationRow && + (view.entry.isStickyAndNotDemoted || + (PromotedNotificationUiForceExpanded.isEnabled && + view.isPromotedOngoing)) + ) { height } else { view.getMinHeight(/* ignoreTemporaryStates= */ true).toFloat() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/SharedNotificationContainer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/SharedNotificationContainer.kt index 705845ff984c..5d8f06ff51be 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/SharedNotificationContainer.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/SharedNotificationContainer.kt @@ -60,6 +60,7 @@ class SharedNotificationContainer(context: Context, attrs: AttributeSet?) : marginTop: Int, marginEnd: Int, marginBottom: Int, + nsslAlpha: Float, ) { val constraintSet = ConstraintSet() constraintSet.clone(baseConstraintSet) @@ -83,6 +84,10 @@ class SharedNotificationContainer(context: Context, attrs: AttributeSet?) : } } + // Constraint layout sets the alpha to 1 if it's not set explicitly in the constraint + // set. Let's keep the current nssl alpha instead, otherwise this might interfere with + // animations. + setAlpha(nsslId, nsslAlpha) connect(nsslId, START, startConstraintId, START, marginStart) if ( !SceneContainerFlag.isEnabled || diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt index 3ea4d488357d..f7401440cfcb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt @@ -36,6 +36,8 @@ import com.android.systemui.util.kotlin.DisposableHandles import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.DisposableHandle +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.map /** Binds the shared notification container to its view-model. */ @SysUISingleton @@ -76,6 +78,7 @@ constructor( marginTop = it.marginTop, marginEnd = it.marginEnd, marginBottom = it.marginBottom, + nsslAlpha = controller.alpha, ) controller.setOverExpansion(0f) @@ -142,21 +145,25 @@ constructor( if (!SceneContainerFlag.isEnabled) { if (Flags.magicPortraitWallpapers()) { launch { - viewModel - .getNotificationStackAbsoluteBottom( - calculateMaxNotifications = calculateMaxNotifications, - calculateHeight = { maxNotifications -> - notificationStackSizeCalculator.computeHeight( - maxNotifs = maxNotifications, - shelfHeight = controller.getShelfHeight().toFloat(), - stack = controller.view, - ) - }, - controller.getShelfHeight().toFloat(), + combine( + viewModel.getNotificationStackAbsoluteBottom( + calculateMaxNotifications = calculateMaxNotifications, + calculateHeight = { maxNotifications -> + notificationStackSizeCalculator.computeHeight( + maxNotifs = maxNotifications, + shelfHeight = + controller.getShelfHeight().toFloat(), + stack = controller.view, + ) + }, + controller.getShelfHeight().toFloat(), + ), + viewModel.configurationBasedDimensions.map { it.marginTop }, + ::Pair, ) - .collect { bottom -> + .collect { (bottom: Float, marginTop: Int) -> keyguardInteractor.setNotificationStackAbsoluteBottom( - bottom + marginTop + bottom ) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java index 16e9c717935c..a2f1ded042f4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java @@ -26,24 +26,31 @@ import android.graphics.drawable.Drawable; import android.text.TextUtils; import android.util.AttributeSet; import android.view.View; -import android.widget.TextView; import androidx.annotation.StyleRes; +import androidx.core.graphics.ColorUtils; import com.android.app.animation.Interpolators; import com.android.internal.annotations.VisibleForTesting; +import com.android.systemui.Flags; import com.android.systemui.keyguard.KeyguardIndication; import com.android.systemui.res.R; +import com.android.systemui.shared.shadow.DoubleShadowTextView; /** * A view to show hints on Keyguard ("Swipe up to unlock", "Tap again to open"). */ -public class KeyguardIndicationTextView extends TextView { +public class KeyguardIndicationTextView extends DoubleShadowTextView { + // Minimum luminance for texts to receive shadows. + private static final float MIN_TEXT_SHADOW_LUMINANCE = 0.5f; public static final long Y_IN_DURATION = 600L; @StyleRes private static int sStyleId = R.style.TextAppearance_Keyguard_BottomArea; @StyleRes + private static int sStyleWithDoubleShadowTextId = + R.style.TextAppearance_Keyguard_BottomArea_DoubleShadow; + @StyleRes private static int sButtonStyleId = R.style.TextAppearance_Keyguard_BottomArea_Button; private boolean mAnimationsEnabled = true; @@ -226,7 +233,14 @@ public class KeyguardIndicationTextView extends TextView { if (mKeyguardIndicationInfo.getBackground() != null) { setTextAppearance(sButtonStyleId); } else { - setTextAppearance(sStyleId); + // If text is transparent or dark color, don't draw any shadow + if (Flags.indicationTextA11yFix() && ColorUtils.calculateLuminance( + mKeyguardIndicationInfo.getTextColor().getDefaultColor()) + > MIN_TEXT_SHADOW_LUMINANCE) { + setTextAppearance(sStyleWithDoubleShadowTextId); + } else { + setTextAppearance(sStyleId); + } } setBackground(mKeyguardIndicationInfo.getBackground()); setTextColor(mKeyguardIndicationInfo.getTextColor()); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java index 4c2bfe5ca257..40245aef4f67 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java @@ -23,6 +23,7 @@ import static com.android.systemui.Flags.updateUserSwitcherBackground; import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow; +import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; import android.database.ContentObserver; @@ -56,6 +57,7 @@ import com.android.systemui.log.core.LogLevel; 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.ShadeViewStateProvider; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.StatusBarState; @@ -114,6 +116,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat R.id.keyguard_hun_animator_start_tag); private final CoroutineDispatcher mCoroutineDispatcher; + private final Context mContext; private final CarrierTextController mCarrierTextController; private final ConfigurationController mConfigurationController; private final SystemStatusAnimationScheduler mAnimationScheduler; @@ -129,7 +132,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat private final KeyguardStatusBarViewModel mKeyguardStatusBarViewModel; private final BiometricUnlockController mBiometricUnlockController; private final SysuiStatusBarStateController mStatusBarStateController; - private final StatusBarContentInsetsProvider mInsetsProvider; + private final StatusBarContentInsetsProviderStore mInsetsProviderStore; private final UserManager mUserManager; private final StatusBarUserChipViewModel mStatusBarUserChipViewModel; private final SecureSettings mSecureSettings; @@ -314,6 +317,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat @Inject public KeyguardStatusBarViewController( @Main CoroutineDispatcher dispatcher, + @ShadeDisplayAware Context context, KeyguardStatusBarView view, CarrierTextController carrierTextController, ConfigurationController configurationController, @@ -347,6 +351,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat ) { super(view); mCoroutineDispatcher = dispatcher; + mContext = context; mCarrierTextController = carrierTextController; mConfigurationController = configurationController; mAnimationScheduler = animationScheduler; @@ -362,7 +367,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat mKeyguardStatusBarViewModel = keyguardStatusBarViewModel; mBiometricUnlockController = biometricUnlockController; mStatusBarStateController = statusBarStateController; - mInsetsProvider = statusBarContentInsetsProviderStore.getDefaultDisplay(); + mInsetsProviderStore = statusBarContentInsetsProviderStore; mUserManager = userManager; mStatusBarUserChipViewModel = userChipViewModel; mSecureSettings = secureSettings; @@ -404,6 +409,10 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat mStatusOverlayHoverListenerFactory = statusOverlayHoverListenerFactory; } + private StatusBarContentInsetsProvider insetsProvider() { + return mInsetsProviderStore.forDisplay(mContext.getDisplayId()); + } + @Override protected void onInit() { super.onInit(); @@ -446,7 +455,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat .createDarkAwareListener(mSystemIconsContainer, mView.darkChangeFlow()); mSystemIconsContainer.setOnHoverListener(hoverListener); mView.setOnApplyWindowInsetsListener( - (view, windowInsets) -> mView.updateWindowInsets(windowInsets, mInsetsProvider)); + (view, windowInsets) -> mView.updateWindowInsets(windowInsets, insetsProvider())); mSecureSettings.registerContentObserverForUserSync( Settings.Secure.STATUS_BAR_SHOW_VIBRATE_ICON, false, @@ -645,7 +654,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat * {@code OnApplyWindowInsetsListener}s. */ public void setDisplayCutout(@Nullable DisplayCutout displayCutout) { - mView.setDisplayCutout(displayCutout, mInsetsProvider); + mView.setDisplayCutout(displayCutout, insetsProvider()); } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java index 1a97ab635028..4825a10e901b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java @@ -144,6 +144,7 @@ public class NotificationIconContainer extends ViewGroup { private int mMaxIcons = Integer.MAX_VALUE; private boolean mOverrideIconColor; + private boolean mUseInverseOverrideIconColor; private boolean mIsStaticLayout = true; private final HashMap<View, IconState> mIconStates = new HashMap<>(); private int mDotPadding; @@ -169,6 +170,7 @@ public class NotificationIconContainer extends ViewGroup { private final int[] mAbsolutePosition = new int[2]; @Nullable private View mIsolatedIconForAnimation; private int mThemedTextColorPrimary; + private int mThemedTextColorPrimaryInverse; @Nullable private Runnable mIsolatedIconAnimationEndRunnable; private boolean mUseIncreasedIconScale; @@ -191,6 +193,8 @@ public class NotificationIconContainer extends ViewGroup { com.android.internal.R.style.Theme_DeviceDefault_DayNight); mThemedTextColorPrimary = Utils.getColorAttr(themedContext, com.android.internal.R.attr.textColorPrimary).getDefaultColor(); + mThemedTextColorPrimaryInverse = Utils.getColorAttr(themedContext, + com.android.internal.R.attr.textColorPrimaryInverse).getDefaultColor(); } @Override @@ -713,6 +717,10 @@ public class NotificationIconContainer extends ViewGroup { mOverrideIconColor = override; } + public void setUseInverseOverrideIconColor(boolean override) { + mUseInverseOverrideIconColor = override; + } + public class IconState extends ViewState { public float iconAppearAmount = 1.0f; public float clampedAppearAmount = 1.0f; @@ -821,7 +829,9 @@ public class NotificationIconContainer extends ViewGroup { } icon.setVisibleState(visibleState, animationsAllowed); if (mOverrideIconColor) { - icon.setIconColor(mThemedTextColorPrimary, + int overrideIconColor = mUseInverseOverrideIconColor + ? mThemedTextColorPrimaryInverse : mThemedTextColorPrimary; + icon.setIconColor(overrideIconColor, /* animate= */ needsCannedAnimation && animationsAllowed); } if (animate) { 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 3f44f7bdef90..caf8a43b2aaf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt @@ -46,7 +46,6 @@ import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator import com.android.systemui.statusbar.core.StatusBarConnectedDisplays import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStore -import com.android.systemui.statusbar.layout.StatusBarContentInsetsProvider import com.android.systemui.statusbar.policy.Clock import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.window.StatusBarWindowStateController @@ -84,7 +83,7 @@ private constructor( private val configurationController: ConfigurationController, private val statusOverlayHoverListenerFactory: StatusOverlayHoverListenerFactory, private val darkIconDispatcher: DarkIconDispatcher, - private val statusBarContentInsetsProvider: StatusBarContentInsetsProvider, + private val statusBarContentInsetsProviderStore: StatusBarContentInsetsProviderStore, private val lazyStatusBarShadeDisplayPolicy: Lazy<StatusBarTouchShadeDisplayPolicy>, ) : ViewController<PhoneStatusBarView>(view) { @@ -92,6 +91,8 @@ private constructor( private lateinit var clock: Clock private lateinit var startSideContainer: View private lateinit var endSideContainer: View + private val statusBarContentInsetsProvider + get() = statusBarContentInsetsProviderStore.forDisplay(context.displayId) private val iconsOnTouchListener = object : View.OnTouchListener { @@ -189,11 +190,9 @@ private constructor( init { // These should likely be done in `onInit`, not `init`. mView.setTouchEventHandler(PhoneStatusBarViewTouchHandler()) - mView.setHasCornerCutoutFetcher { - statusBarContentInsetsProvider.currentRotationHasCornerCutout() - } - mView.setInsetsFetcher { - statusBarContentInsetsProvider.getStatusBarContentInsetsForCurrentRotation() + statusBarContentInsetsProvider?.let { + mView.setHasCornerCutoutFetcher { it.currentRotationHasCornerCutout() } + mView.setInsetsFetcher { it.getStatusBarContentInsetsForCurrentRotation() } } mView.init(userChipViewModel) } @@ -393,7 +392,7 @@ private constructor( configurationController, statusOverlayHoverListenerFactory, darkIconDispatcher, - statusBarContentInsetsProviderStore.defaultDisplay, + statusBarContentInsetsProviderStore, lazyStatusBarShadeDisplayPolicy, ) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index 2cd8eafcdb54..be48c3d928f4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -66,6 +66,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardState; import com.android.systemui.keyguard.shared.model.ScrimAlpha; import com.android.systemui.keyguard.shared.model.TransitionState; import com.android.systemui.keyguard.shared.model.TransitionStep; +import com.android.systemui.keyguard.ui.transitions.BlurConfig; import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToGoneTransitionViewModel; import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel; import com.android.systemui.res.R; @@ -258,6 +259,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump private int mScrimsVisibility; private final TriConsumer<ScrimState, Float, GradientColors> mScrimStateListener; private final LargeScreenShadeInterpolator mLargeScreenShadeInterpolator; + private final BlurConfig mBlurConfig; private Consumer<Integer> mScrimVisibleListener; private boolean mBlankScreen; private boolean mScreenBlankingCallbackCalled; @@ -339,9 +341,11 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump KeyguardTransitionInteractor keyguardTransitionInteractor, KeyguardInteractor keyguardInteractor, @Main CoroutineDispatcher mainDispatcher, - LargeScreenShadeInterpolator largeScreenShadeInterpolator) { + LargeScreenShadeInterpolator largeScreenShadeInterpolator, + BlurConfig blurConfig) { mScrimStateListener = lightBarController::setScrimState; mLargeScreenShadeInterpolator = largeScreenShadeInterpolator; + mBlurConfig = blurConfig; // All scrims default alpha need to match bouncer background alpha to make sure the // transitions involving the bouncer are smooth and don't overshoot the bouncer alpha. mDefaultScrimAlpha = @@ -406,7 +410,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump final ScrimState[] states = ScrimState.values(); for (int i = 0; i < states.length; i++) { - states[i].init(mScrimInFront, mScrimBehind, mDozeParameters, mDockManager); + states[i].init(mScrimInFront, mScrimBehind, mDozeParameters, mDockManager, mBlurConfig); states[i].setScrimBehindAlphaKeyguard(mScrimBehindAlphaKeyguard); states[i].setDefaultScrimAlpha(mDefaultScrimAlpha); } @@ -868,6 +872,12 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump * bounds instead. */ public void setClipsQsScrim(boolean clipScrim) { + if (Flags.notificationShadeBlur() || Flags.bouncerUiRevamp()) { + // Never clip scrims when blur is enabled, colors of UI elements are supposed to "add" + // up across the scrims. + mClipsQsScrim = false; + return; + } if (clipScrim == mClipsQsScrim) { return; } @@ -957,11 +967,20 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump mBehindAlpha = 1; mNotificationsAlpha = behindFraction * mDefaultScrimAlpha; } else { - mBehindAlpha = mLargeScreenShadeInterpolator.getBehindScrimAlpha( - mPanelExpansionFraction * mDefaultScrimAlpha); - mNotificationsAlpha = - mLargeScreenShadeInterpolator.getNotificationScrimAlpha( - mPanelExpansionFraction); + if (Flags.notificationShadeBlur()) { + // TODO (b/390730594): match any spec for controlling alpha based on shade + // expansion fraction. + mBehindAlpha = mState.getBehindAlpha() * mPanelExpansionFraction; + mBehindTint = mState.getBehindTint(); + mNotificationsAlpha = mState.getNotifAlpha() * mPanelExpansionFraction; + mNotificationsTint = mState.getNotifTint(); + } else { + mBehindAlpha = mLargeScreenShadeInterpolator.getBehindScrimAlpha( + mPanelExpansionFraction * mDefaultScrimAlpha); + mNotificationsAlpha = + mLargeScreenShadeInterpolator.getNotificationScrimAlpha( + mPanelExpansionFraction); + } } mBehindTint = mState.getBehindTint(); mInFrontAlpha = 0; @@ -1015,7 +1034,11 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump .saturate(mTransitionToLockScreenFullShadeNotificationsProgress); } else if (mState == ScrimState.SHADE_LOCKED) { // going from KEYGUARD to SHADE_LOCKED state - mNotificationsAlpha = getInterpolatedFraction(); + if (Flags.notificationShadeBlur()) { + mNotificationsAlpha = mState.getNotifAlpha() * getInterpolatedFraction(); + } else { + mNotificationsAlpha = getInterpolatedFraction(); + } } else if (mState == ScrimState.GLANCEABLE_HUB && mTransitionToFullShadeProgress == 0.0f) { // Notification scrim should not be visible on the glanceable hub unless the @@ -1191,6 +1214,12 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump dispatchBackScrimState(mScrimBehind.getViewAlpha()); } + if (Flags.bouncerUiRevamp()) { + // Blur the notification scrim as needed. The blur is needed only when we show the + // expanded shade behind the bouncer. Without it, the notification scrim outline is + // visible behind the bouncer. + mNotificationsScrim.setBlurRadius(mState.getNotifBlurRadius()); + } // We also want to hide FLAG_SHOW_WHEN_LOCKED activities under the scrim. boolean hideFlagShowWhenLockedActivities = diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java index 14937295051d..5f423cf35edd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java @@ -24,8 +24,10 @@ import android.graphics.Color; import com.android.app.tracing.coroutines.TrackTracer; import com.android.systemui.Flags; import com.android.systemui.dock.DockManager; +import com.android.systemui.keyguard.ui.transitions.BlurConfig; import com.android.systemui.res.R; import com.android.systemui.scrim.ScrimView; +import com.android.systemui.shade.ui.ShadeColors; import com.android.systemui.statusbar.notification.stack.StackStateAnimator; import kotlinx.coroutines.ExperimentalCoroutinesApi; @@ -86,16 +88,24 @@ public enum ScrimState { } else { mAnimationDuration = ScrimController.ANIMATION_DURATION; } - mFrontTint = mBackgroundColor; - mBehindTint = mBackgroundColor; - mNotifTint = mClipQsScrim ? mBackgroundColor : Color.TRANSPARENT; - - mFrontAlpha = 0; - mBehindAlpha = mClipQsScrim ? 1 : mScrimBehindAlphaKeyguard; - mNotifAlpha = mClipQsScrim ? mScrimBehindAlphaKeyguard : 0; - if (mClipQsScrim) { - updateScrimColor(mScrimBehind, 1f /* alpha */, mBackgroundColor); + if (Flags.notificationShadeBlur()) { + mBehindTint = Color.TRANSPARENT; + mNotifTint = ShadeColors.notificationScrim(mScrimBehind.getResources()); + mBehindAlpha = 0.0f; + mNotifAlpha = 0.0f; + mFrontAlpha = 0.0f; + } else { + mFrontTint = mBackgroundColor; + mBehindTint = mBackgroundColor; + mNotifTint = mClipQsScrim ? mBackgroundColor : Color.TRANSPARENT; + mFrontAlpha = 0; + mBehindAlpha = mClipQsScrim ? 1 : mScrimBehindAlphaKeyguard; + mNotifAlpha = mClipQsScrim ? mScrimBehindAlphaKeyguard : 0; + if (mClipQsScrim) { + updateScrimColor(mScrimBehind, 1f /* alpha */, mBackgroundColor); + } } + } }, @@ -140,7 +150,14 @@ public enum ScrimState { @Override public void prepare(ScrimState previousState) { if (Flags.bouncerUiRevamp()) { - mBehindAlpha = 0f; + if (previousState == SHADE_LOCKED) { + mBehindAlpha = previousState.getBehindAlpha(); + mNotifAlpha = previousState.getNotifAlpha(); + mNotifBlurRadius = mBlurConfig.getMaxBlurRadiusPx(); + } else { + mNotifAlpha = 0f; + mBehindAlpha = 0f; + } mFrontAlpha = TRANSPARENT_BOUNCER_SCRIM_ALPHA; mFrontTint = mSurfaceColor; return; @@ -169,13 +186,21 @@ public enum ScrimState { @Override public void prepare(ScrimState previousState) { - mBehindAlpha = mClipQsScrim ? 1 : mDefaultScrimAlpha; - mNotifAlpha = 1f; - mFrontAlpha = 0f; - mBehindTint = mClipQsScrim ? Color.TRANSPARENT : mBackgroundColor; + if (Flags.notificationShadeBlur()) { + mBehindTint = ShadeColors.shadePanel(mScrimBehind.getResources()); + mBehindAlpha = Color.alpha(mBehindTint) / 255.0f; + mNotifTint = ShadeColors.notificationScrim(mScrimBehind.getResources()); + mNotifAlpha = Color.alpha(mNotifTint) / 255.0f; + mFrontAlpha = 0.0f; + } else { + mBehindAlpha = mClipQsScrim ? 1 : mDefaultScrimAlpha; + mNotifAlpha = 1f; + mFrontAlpha = 0f; + mBehindTint = mClipQsScrim ? Color.TRANSPARENT : mBackgroundColor; - if (mClipQsScrim) { - updateScrimColor(mScrimBehind, 1f /* alpha */, mBackgroundColor); + if (mClipQsScrim) { + updateScrimColor(mScrimBehind, 1f /* alpha */, mBackgroundColor); + } } } }, @@ -282,6 +307,13 @@ public enum ScrimState { mFrontTint = mBackgroundColor; mBehindTint = mBackgroundColor; mBlankScreen = true; + } else if (Flags.notificationShadeBlur()) { + mBehindTint = ShadeColors.shadePanel(mScrimBehind.getResources()); + mBehindAlpha = Color.alpha(mBehindTint) / 255.0f; + mNotifTint = ShadeColors.notificationScrim(mScrimBehind.getResources()); + mNotifAlpha = Color.alpha(mNotifTint) / 255.0f; + mFrontAlpha = 0.0f; + return; } if (mClipQsScrim) { @@ -371,6 +403,7 @@ public enum ScrimState { DozeParameters mDozeParameters; DockManager mDockManager; boolean mDisplayRequiresBlanking; + protected BlurConfig mBlurConfig; boolean mLaunchingAffordanceWithPreview; boolean mOccludeAnimationPlaying; boolean mWakeLockScreenSensorActive; @@ -379,8 +412,12 @@ public enum ScrimState { boolean mClipQsScrim; int mBackgroundColor; + // This is needed to blur the scrim behind the scrimmed bouncer to avoid showing + // the notification section border + protected float mNotifBlurRadius = 0.0f; + public void init(ScrimView scrimInFront, ScrimView scrimBehind, DozeParameters dozeParameters, - DockManager dockManager) { + DockManager dockManager, BlurConfig blurConfig) { mBackgroundColor = scrimBehind.getContext().getColor(R.color.shade_scrim_background_dark); mScrimInFront = scrimInFront; mScrimBehind = scrimBehind; @@ -388,6 +425,7 @@ public enum ScrimState { mDozeParameters = dozeParameters; mDockManager = dockManager; mDisplayRequiresBlanking = dozeParameters.getDisplayNeedsBlanking(); + mBlurConfig = blurConfig; } /** Prepare state for transition. */ @@ -494,4 +532,8 @@ public enum ScrimState { public void setClipQsScrim(boolean clipsQsScrim) { mClipQsScrim = clipsQsScrim; } + + public float getNotifBlurRadius() { + return mNotifBlurRadius; + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTransitionAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTransitionAnimatorController.kt index 705a11df83fc..e12b21eb2c4a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTransitionAnimatorController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTransitionAnimatorController.kt @@ -1,6 +1,7 @@ package com.android.systemui.statusbar.phone import android.view.View +import com.android.systemui.Flags import com.android.systemui.animation.ActivityTransitionAnimator import com.android.systemui.animation.TransitionAnimator import com.android.systemui.animation.TransitionAnimator.Companion.getProgress @@ -22,7 +23,7 @@ class StatusBarTransitionAnimatorController( private val notificationShadeWindowController: NotificationShadeWindowController, private val commandQueue: CommandQueue, @DisplayId private val displayId: Int, - private val isLaunchForActivity: Boolean = true + private val isLaunchForActivity: Boolean = true, ) : ActivityTransitionAnimator.Controller by delegate { private var hideIconsDuringLaunchAnimation: Boolean = true @@ -41,8 +42,16 @@ class StatusBarTransitionAnimatorController( } override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) { - delegate.onTransitionAnimationStart(isExpandingFullyAbove) - shadeAnimationInteractor.setIsLaunchingActivity(true) + if (Flags.shadeLaunchAccessibility()) { + // We set this before calling the delegate to make sure that accessibility is disabled + // for the whole duration of the transition, so that we don't have stray TalkBack events + // once the animating view becomes invisible. + shadeAnimationInteractor.setIsLaunchingActivity(true) + delegate.onTransitionAnimationStart(isExpandingFullyAbove) + } else { + delegate.onTransitionAnimationStart(isExpandingFullyAbove) + shadeAnimationInteractor.setIsLaunchingActivity(true) + } if (!isExpandingFullyAbove) { shadeController.collapseWithDuration( ActivityTransitionAnimator.TIMINGS.totalDuration.toInt() @@ -59,7 +68,7 @@ class StatusBarTransitionAnimatorController( override fun onTransitionAnimationProgress( state: TransitionAnimator.State, progress: Float, - linearProgress: Float + linearProgress: Float, ) { delegate.onTransitionAnimationProgress(state, progress, linearProgress) val hideIcons = @@ -67,7 +76,7 @@ class StatusBarTransitionAnimatorController( ActivityTransitionAnimator.TIMINGS, linearProgress, ANIMATION_DELAY_ICON_FADE_IN, - 100 + 100, ) == 0.0f if (hideIcons != hideIconsDuringLaunchAnimation) { hideIconsDuringLaunchAnimation = hideIcons 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 5fa15b831299..a28d14fd908d 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 @@ -30,6 +30,7 @@ import com.android.settingslib.notification.modes.ZenIcon import com.android.settingslib.notification.modes.ZenIconLoader import com.android.settingslib.notification.modes.ZenMode import com.android.settingslib.volume.shared.model.AudioStream +import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.modes.shared.ModesUi import com.android.systemui.shared.notifications.data.repository.NotificationSettingsRepository @@ -57,6 +58,7 @@ import kotlinx.coroutines.flow.stateIn * An interactor that performs business logic related to the status and configuration of Zen Mode * (or Do Not Disturb/DND Mode). */ + @SysUISingleton class ZenModeInteractor @Inject constructor( diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/SwitchAppsGestureRecognizer.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/SwitchAppsGestureRecognizer.kt index cc382d0a6148..6732287fe256 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/SwitchAppsGestureRecognizer.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/SwitchAppsGestureRecognizer.kt @@ -18,12 +18,9 @@ package com.android.systemui.touchpad.tutorial.ui.gesture import android.util.MathUtils import android.view.MotionEvent -import com.android.systemui.touchpad.tutorial.ui.gesture.GestureDirection.LEFT -import com.android.systemui.touchpad.tutorial.ui.gesture.GestureDirection.RIGHT import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.InProgress -import kotlin.math.abs -/** Recognizes Quickswitch gesture i.e. using four fingers on touchpad, swiping right. */ +/** Recognizes Quickswitch gesture i.e. using four fingers on touchpad, swiping to right. */ class SwitchAppsGestureRecognizer(private val gestureDistanceThresholdPx: Int) : GestureRecognizer { private val distanceTracker = DistanceTracker() @@ -50,13 +47,7 @@ class SwitchAppsGestureRecognizer(private val gestureDistanceThresholdPx: Int) : gestureStateChangedCallback, gestureState, isFinished = { it.deltaX >= gestureDistanceThresholdPx }, - progress = ::getProgress, + progress = { InProgress(MathUtils.saturate(it.deltaX / gestureDistanceThresholdPx)) }, ) } - - private fun getProgress(it: Moving): InProgress { - val direction = if (it.deltaX > 0) RIGHT else LEFT - val value = MathUtils.saturate(abs(it.deltaX / gestureDistanceThresholdPx)) - return InProgress(value, direction) - } } diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/SwitchAppsGestureRecognizerProvider.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/SwitchAppsGestureRecognizerProvider.kt index b1e163b55377..8b8606a119d4 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/SwitchAppsGestureRecognizerProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/SwitchAppsGestureRecognizerProvider.kt @@ -18,15 +18,13 @@ package com.android.systemui.touchpad.tutorial.ui.viewmodel import com.android.systemui.touchpad.tutorial.ui.gesture.GestureRecognizer import com.android.systemui.touchpad.tutorial.ui.gesture.SwitchAppsGestureRecognizer -import com.android.systemui.touchpad.tutorial.ui.gesture.VelocityTracker import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map class SwitchAppsGestureRecognizerProvider @Inject -constructor(val resources: TouchpadGestureResources, val velocityTracker: VelocityTracker) : - GestureRecognizerProvider { +constructor(val resources: TouchpadGestureResources) : GestureRecognizerProvider { override val recognizer: Flow<GestureRecognizer> = resources.distanceThreshold().map { diff --git a/packages/SystemUI/src/com/android/systemui/utils/SafeIconLoader.kt b/packages/SystemUI/src/com/android/systemui/utils/SafeIconLoader.kt new file mode 100644 index 000000000000..41bef7f94fa2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/utils/SafeIconLoader.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.utils + +import android.app.IUriGrantsManager +import android.content.Context +import android.graphics.drawable.Drawable +import android.graphics.drawable.Icon +import android.os.UserHandle +import com.android.systemui.dagger.qualifiers.Application +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject + +/** + * Use to load an icon (from another app) safely. It will prevent cross user icon loading if there + * are no permissions. + */ +class SafeIconLoader +@AssistedInject +constructor( + @Assisted("serviceUid") private val serviceUid: Int, + @Assisted private val packageName: String, + @Assisted("userId") private val userId: Int, + @Application private val applicationContext: Context, + private val iUriGrantsManager: IUriGrantsManager, +) { + + private val serviceContext = + applicationContext.createPackageContextAsUser(packageName, 0, UserHandle.of(userId)) + + /** + * Tries to load the icon. If it fails in any way (for example, cross user permissions), it will + * return `null`. Prefer calling this in a background thread. + */ + fun load(icon: Icon): Drawable? { + return icon.loadDrawableCheckingUriGrant( + serviceContext, + iUriGrantsManager, + serviceUid, + packageName, + ) + } + + @AssistedFactory + interface Factory { + + fun create( + @Assisted("serviceUid") serviceUid: Int, + packageName: String, + @Assisted("userId") userId: Int, + ): SafeIconLoader + } +} 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 39b434ad65f1..83b7c1818341 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt @@ -16,7 +16,6 @@ package com.android.systemui.volume.dialog -import android.app.Dialog import android.content.Context import android.graphics.PixelFormat import android.os.Bundle @@ -24,6 +23,7 @@ import android.view.MotionEvent import android.view.View import android.view.ViewGroup import android.view.WindowManager +import androidx.activity.ComponentDialog import com.android.app.tracing.coroutines.coroutineScopeTraced import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.lifecycle.repeatWhenAttached @@ -40,7 +40,7 @@ constructor( @Application context: Context, private val componentFactory: VolumeDialogComponent.Factory, private val visibilityInteractor: VolumeDialogVisibilityInteractor, -) : Dialog(context, R.style.Theme_SystemUI_Dialog_Volume) { +) : ComponentDialog(context, R.style.Theme_SystemUI_Dialog_Volume) { init { with(window!!) { diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractor.kt index 8c018606ebda..e261ceebf33e 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractor.kt @@ -17,6 +17,8 @@ package com.android.systemui.volume.dialog.domain.interactor import android.annotation.SuppressLint +import android.media.AudioManager.RINGER_MODE_NORMAL +import android.media.AudioManager.RINGER_MODE_SILENT import android.os.Handler import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.plugins.VolumeDialogController @@ -60,10 +62,10 @@ constructor( awaitClose { volumeDialogController.removeCallback(producer) } } .buffer(capacity = BUFFER_CAPACITY, onBufferOverflow = BufferOverflow.DROP_OLDEST) - .shareIn(replay = 0, scope = coroutineScope, started = SharingStarted.WhileSubscribed()) + .shareIn(replay = 0, scope = coroutineScope, started = SharingStarted.Eagerly) .onStart { emit(VolumeDialogEventModel.SubscribedToEvents) } - private class VolumeDialogEventModelProducer( + private inner class VolumeDialogEventModelProducer( private val scope: ProducerScope<VolumeDialogEventModel> ) : VolumeDialogController.Callbacks { override fun onShowRequested(reason: Int, keyguardLocked: Boolean, lockTaskModeState: Int) { @@ -93,14 +95,6 @@ constructor( // Configuration change is never emitted by the VolumeDialogControllerImpl now. override fun onConfigurationChanged() = Unit - override fun onShowVibrateHint() { - scope.trySend(VolumeDialogEventModel.ShowVibrateHint) - } - - override fun onShowSilentHint() { - scope.trySend(VolumeDialogEventModel.ShowSilentHint) - } - override fun onScreenOff() { scope.trySend(VolumeDialogEventModel.ScreenOff) } @@ -113,16 +107,6 @@ constructor( scope.trySend(VolumeDialogEventModel.AccessibilityModeChanged(showA11yStream == true)) } - // Captions button is remove from the Volume Dialog - override fun onCaptionComponentStateChanged( - isComponentEnabled: Boolean, - fromTooltip: Boolean, - ) = Unit - - // Captions button is remove from the Volume Dialog - override fun onCaptionEnabledStateChanged(isEnabled: Boolean, checkBeforeSwitch: Boolean) = - Unit - override fun onShowCsdWarning(csdWarning: Int, durationMs: Int) { scope.trySend( VolumeDialogEventModel.ShowCsdWarning( @@ -135,5 +119,25 @@ constructor( override fun onVolumeChangedFromKey() { scope.trySend(VolumeDialogEventModel.VolumeChangedFromKey) } + + // This should've been handled in side the controller itself. + override fun onShowVibrateHint() { + volumeDialogController.setRingerMode(RINGER_MODE_SILENT, false) + } + + // This should've been handled in side the controller itself. + override fun onShowSilentHint() { + volumeDialogController.setRingerMode(RINGER_MODE_NORMAL, false) + } + + // Captions button is remove from the Volume Dialog + override fun onCaptionComponentStateChanged( + isComponentEnabled: Boolean, + fromTooltip: Boolean, + ) = Unit + + // Captions button is remove from the Volume Dialog + override fun onCaptionEnabledStateChanged(isEnabled: Boolean, checkBeforeSwitch: Boolean) = + Unit } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogEventModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogEventModel.kt index 9793d2be6b98..a0214dc957a4 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogEventModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogEventModel.kt @@ -38,10 +38,6 @@ sealed interface VolumeDialogEventModel { data class LayoutDirectionChanged(val layoutDirection: Int) : VolumeDialogEventModel - data object ShowVibrateHint : VolumeDialogEventModel - - data object ShowSilentHint : VolumeDialogEventModel - data object ScreenOff : VolumeDialogEventModel data class ShowSafetyWarning(val flags: Int) : VolumeDialogEventModel 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 40719185e290..73f6236393b2 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 @@ -47,7 +47,6 @@ constructor( private val audioSystemRepository: AudioSystemRepository, private val ringerFeedbackRepository: VolumeDialogRingerFeedbackRepository, ) { - val ringerModel: Flow<VolumeDialogRingerModel> = volumeDialogStateInteractor.volumeDialogState .mapNotNull { toRingerModel(it) } 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 940c79c78d76..577e47bb3b83 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 @@ -18,7 +18,6 @@ package com.android.systemui.volume.dialog.sliders.dagger import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSliderType import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogOverscrollViewBinder -import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSliderHapticsViewBinder import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSliderViewBinder import dagger.BindsInstance import dagger.Subcomponent @@ -33,8 +32,6 @@ interface VolumeDialogSliderComponent { fun sliderViewBinder(): VolumeDialogSliderViewBinder - fun sliderHapticsViewBinder(): VolumeDialogSliderHapticsViewBinder - fun overscrollViewBinder(): VolumeDialogOverscrollViewBinder @Subcomponent.Factory diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/data/repository/VolumeDialogSliderTouchEventsRepository.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/data/repository/VolumeDialogSliderTouchEventsRepository.kt index 82885d65c513..07954f850286 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/data/repository/VolumeDialogSliderTouchEventsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/data/repository/VolumeDialogSliderTouchEventsRepository.kt @@ -16,8 +16,8 @@ package com.android.systemui.volume.dialog.sliders.data.repository -import android.view.MotionEvent import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope +import com.android.systemui.volume.dialog.sliders.shared.model.SliderInputEvent import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow @@ -26,10 +26,11 @@ import kotlinx.coroutines.flow.filterNotNull @VolumeDialogSliderScope class VolumeDialogSliderTouchEventsRepository @Inject constructor() { - private val mutableSliderTouchEvents: MutableStateFlow<MotionEvent?> = MutableStateFlow(null) - val sliderTouchEvent: Flow<MotionEvent> = mutableSliderTouchEvents.filterNotNull() + private val mutableSliderTouchEvents: MutableStateFlow<SliderInputEvent.Touch?> = + MutableStateFlow(null) + val sliderTouchEvent: Flow<SliderInputEvent.Touch> = mutableSliderTouchEvents.filterNotNull() - fun update(event: MotionEvent) { - mutableSliderTouchEvents.tryEmit(MotionEvent.obtain(event)) + fun update(touch: SliderInputEvent.Touch) { + mutableSliderTouchEvents.value = touch } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInputEventsInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInputEventsInteractor.kt index c7b4184a9f2f..351832bd275a 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInputEventsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInputEventsInteractor.kt @@ -16,7 +16,6 @@ package com.android.systemui.volume.dialog.sliders.domain.interactor -import android.view.MotionEvent import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogCallbacksInteractor import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogVisibilityInteractor @@ -45,7 +44,7 @@ constructor( val event: Flow<SliderInputEvent> = merge( - repository.sliderTouchEvent.map { SliderInputEvent.Touch(it) }, + repository.sliderTouchEvent, volumeDialogCallbacksInteractor.event .filterIsInstance(VolumeDialogEventModel.VolumeChangedFromKey::class) .map { SliderInputEvent.Button }, @@ -55,7 +54,7 @@ constructor( event.onEach { visibilityInteractor.resetDismissTimeout() }.launchIn(coroutineScope) } - fun onTouchEvent(newEvent: MotionEvent) { - repository.update(newEvent) + fun onTouchEvent(pointerEvent: SliderInputEvent.Touch) { + repository.update(pointerEvent) } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/shared/model/SliderInputEvent.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/shared/model/SliderInputEvent.kt index 37dbb4b3a81d..841730857d71 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/shared/model/SliderInputEvent.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/shared/model/SliderInputEvent.kt @@ -16,12 +16,20 @@ package com.android.systemui.volume.dialog.sliders.shared.model -import android.view.MotionEvent - /** Models input event happened on the Volume Slider */ sealed interface SliderInputEvent { - data class Touch(val event: MotionEvent) : SliderInputEvent + interface Touch : SliderInputEvent { + + val x: Float + val y: Float + + data class Start(override val x: Float, override val y: Float) : Touch + + data class Move(override val x: Float, override val y: Float) : Touch + + data class End(override val x: Float, override val y: Float) : Touch + } data object Button : SliderInputEvent } diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogOverscrollViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogOverscrollViewBinder.kt index 8109b50aa34a..38feb69aad7b 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogOverscrollViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogOverscrollViewBinder.kt @@ -20,11 +20,9 @@ import android.view.View import androidx.dynamicanimation.animation.FloatValueHolder import androidx.dynamicanimation.animation.SpringAnimation import androidx.dynamicanimation.animation.SpringForce -import com.android.systemui.res.R import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogOverscrollViewModel import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogOverscrollViewModel.OverscrollEventModel -import com.google.android.material.slider.Slider import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.launchIn @@ -51,10 +49,6 @@ constructor(private val viewModel: VolumeDialogOverscrollViewModel) { ) .addUpdateListener { _, value, _ -> viewsToAnimate.setTranslationY(value) } - view.requireViewById<Slider>(R.id.volume_dialog_slider).addOnChangeListener { s, value, _ -> - viewModel.setSlider(value = value, min = s.valueFrom, max = s.valueTo) - } - viewModel.overscrollEvent .onEach { event -> when (event) { 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 deleted file mode 100644 index 5a7fbc6341f2..000000000000 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderHapticsViewBinder.kt +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.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/VolumeDialogSliderViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt index d40302408dd6..21a392776235 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 @@ -16,96 +16,211 @@ package com.android.systemui.volume.dialog.sliders.ui -import android.annotation.SuppressLint +import android.graphics.drawable.Drawable import android.view.View -import androidx.dynamicanimation.animation.FloatPropertyCompat -import androidx.dynamicanimation.animation.SpringAnimation -import androidx.dynamicanimation.animation.SpringForce +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.foundation.gestures.Orientation +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.BoxScope +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.SliderDefaults +import androidx.compose.material3.SliderState +import androidx.compose.material3.VerticalSlider +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.runtime.snapshotFlow +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.android.compose.ui.graphics.painter.DrawablePainter +import com.android.systemui.haptics.slider.compose.ui.SliderHapticsViewModel +import com.android.systemui.lifecycle.rememberViewModel import com.android.systemui.res.R import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope -import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderInputEventsViewModel -import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderStateModel +import com.android.systemui.volume.dialog.sliders.ui.compose.VolumeDialogSliderTrack +import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogOverscrollViewModel import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderViewModel -import com.google.android.material.slider.Slider -import com.google.android.material.slider.Slider.OnSliderTouchListener +import com.android.systemui.volume.haptics.ui.VolumeHapticsConfigsProvider import javax.inject.Inject +import kotlin.math.round import kotlin.math.roundToInt -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.currentCoroutineContext +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.isActive @VolumeDialogSliderScope class VolumeDialogSliderViewBinder @Inject constructor( private val viewModel: VolumeDialogSliderViewModel, - private val inputViewModel: VolumeDialogSliderInputEventsViewModel, + private val overscrollViewModel: VolumeDialogOverscrollViewModel, + private val hapticsViewModelFactory: SliderHapticsViewModel.Factory, ) { + fun bind(view: View) { + val sliderComposeView: ComposeView = view.requireViewById(R.id.volume_dialog_slider) + sliderComposeView.setContent { + VolumeDialogSlider( + viewModel = viewModel, + overscrollViewModel = overscrollViewModel, + hapticsViewModelFactory = + if (com.android.systemui.Flags.hapticsForComposeSliders()) { + hapticsViewModelFactory + } else { + null + }, + ) + } + } +} - private val sliderValueProperty = - object : FloatPropertyCompat<Slider>("value") { - override fun getValue(slider: Slider): Float = slider.value +@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) +@Composable +private fun VolumeDialogSlider( + viewModel: VolumeDialogSliderViewModel, + overscrollViewModel: VolumeDialogOverscrollViewModel, + hapticsViewModelFactory: SliderHapticsViewModel.Factory?, + modifier: Modifier = Modifier, +) { + + val colors = + SliderDefaults.colors( + thumbColor = MaterialTheme.colorScheme.primary, + activeTickColor = MaterialTheme.colorScheme.surfaceContainerHighest, + inactiveTickColor = MaterialTheme.colorScheme.primary, + activeTrackColor = MaterialTheme.colorScheme.primary, + inactiveTrackColor = MaterialTheme.colorScheme.surfaceContainerHighest, + ) + val collectedSliderState by viewModel.state.collectAsStateWithLifecycle(null) + val sliderState = collectedSliderState ?: return - override fun setValue(slider: Slider, value: Float) { - slider.value = value + val interactionSource = remember { MutableInteractionSource() } + val hapticsViewModel: SliderHapticsViewModel? = + hapticsViewModelFactory?.let { + rememberViewModel(traceName = "SliderHapticsViewModel") { + it.create( + interactionSource, + sliderState.valueRange, + Orientation.Vertical, + VolumeHapticsConfigsProvider.sliderHapticFeedbackConfig(sliderState.valueRange), + VolumeHapticsConfigsProvider.seekableSliderTrackerConfig, + ) } } - private val springForce = - SpringForce().apply { - stiffness = SpringForce.STIFFNESS_MEDIUM - dampingRatio = SpringForce.DAMPING_RATIO_NO_BOUNCY - } - @SuppressLint("ClickableViewAccessibility") - fun CoroutineScope.bind(view: View) { - var isInitialUpdate = true - val sliderView: Slider = view.requireViewById(R.id.volume_dialog_slider) - val animation = SpringAnimation(sliderView, sliderValueProperty) - animation.spring = springForce - sliderView.setOnTouchListener { _, event -> - inputViewModel.onTouchEvent(event) - false - } - sliderView.addOnChangeListener { _, value, fromUser -> - viewModel.setStreamVolume(value.roundToInt(), fromUser) + val state = + remember(sliderState.valueRange) { + SliderState( + value = sliderState.value, + valueRange = sliderState.valueRange, + steps = + (sliderState.valueRange.endInclusive - sliderState.valueRange.start - 1) + .toInt(), + ) + .apply { + onValueChangeFinished = { + viewModel.onStreamChangeFinished(value.roundToInt()) + hapticsViewModel?.onValueChangeEnded() + } + setOnValueChangeListener { + value = it + hapticsViewModel?.addVelocityDataPoint(it) + overscrollViewModel.setSlider( + value = value, + min = valueRange.start, + max = valueRange.endInclusive, + ) + viewModel.setStreamVolume(it, true) + } + } } - sliderView.addOnSliderTouchListener( - object : OnSliderTouchListener { - override fun onStartTrackingTouch(slider: Slider) {} + var lastDiscreteStep by remember { mutableFloatStateOf(round(sliderState.value)) } + LaunchedEffect(sliderState.value) { + state.value = sliderState.value + snapshotFlow { sliderState.value } + .map { round(it) } + .filter { it != lastDiscreteStep } + .distinctUntilChanged() + .collect { discreteStep -> + lastDiscreteStep = discreteStep + hapticsViewModel?.onValueChange(discreteStep) + } + } - override fun onStopTrackingTouch(slider: Slider) { - viewModel.onStreamChangeFinished(slider.value.roundToInt()) + VerticalSlider( + state = state, + enabled = !sliderState.isDisabled, + reverseDirection = true, + colors = colors, + interactionSource = interactionSource, + modifier = + modifier.pointerInput(Unit) { + coroutineScope { + val currentContext = currentCoroutineContext() + awaitPointerEventScope { + while (currentContext.isActive) { + viewModel.onTouchEvent(awaitPointerEvent()) + } + } } - } - ) + }, + track = { + VolumeDialogSliderTrack( + state, + colors = colors, + isEnabled = !sliderState.isDisabled, + activeTrackEndIcon = { iconsState -> + VolumeIcon(sliderState.icon, iconsState.isActiveTrackEndIconVisible) + }, + inactiveTrackEndIcon = { iconsState -> + VolumeIcon(sliderState.icon, !iconsState.isActiveTrackEndIconVisible) + }, + ) + }, + ) +} - viewModel.isDisabledByZenMode.onEach { sliderView.isEnabled = !it }.launchIn(this) - viewModel.state - .onEach { - sliderView.setModel(it, animation, isInitialUpdate) - isInitialUpdate = false - } - .launchIn(this) +@Composable +private fun BoxScope.VolumeIcon( + drawable: Drawable, + isVisible: Boolean, + modifier: Modifier = Modifier, +) { + AnimatedVisibility( + visible = isVisible, + enter = fadeIn(animationSpec = tween(durationMillis = 50)), + exit = fadeOut(animationSpec = tween(durationMillis = 50)), + modifier = modifier.align(Alignment.Center).size(40.dp).padding(10.dp), + ) { + Icon(painter = DrawablePainter(drawable), contentDescription = null) } +} - @SuppressLint("UseCompatLoadingForDrawables") - private fun Slider.setModel( - model: VolumeDialogSliderStateModel, - animation: SpringAnimation, - isInitialUpdate: Boolean, - ) { - valueFrom = model.minValue - animation.setMinValue(model.minValue) - valueTo = model.maxValue - animation.setMaxValue(model.maxValue) - // coerce the current value to the new value range before animating it. This prevents - // animating from the value that is outside of current [valueFrom, valueTo]. - value = value.coerceIn(valueFrom, valueTo) - trackIconActiveStart = model.icon - if (isInitialUpdate) { - value = model.value - } else { - animation.animateToFinalPosition(model.value) - } +@OptIn(ExperimentalMaterial3Api::class) +fun SliderState.setOnValueChangeListener(onValueChange: ((Float) -> Unit)?) { + with(javaClass.getDeclaredField("onValueChange")) { + val oldIsAccessible = isAccessible + AutoCloseable { isAccessible = oldIsAccessible } + .use { + isAccessible = true + set(this@setOnValueChangeListener, onValueChange) + } } } 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 75d427acc05b..c66955a0c187 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 @@ -71,7 +71,6 @@ constructor(private val viewModel: VolumeDialogSlidersViewModel) { viewsToAnimate: Array<View>, ) { with(component.sliderViewBinder()) { bind(sliderContainer) } - with(component.sliderHapticsViewBinder()) { bind(sliderContainer) } with(component.overscrollViewBinder()) { bind(sliderContainer, viewsToAnimate) } } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/compose/VolumeDialogSliderTrack.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/compose/VolumeDialogSliderTrack.kt new file mode 100644 index 000000000000..1dd9ddac79be --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/compose/VolumeDialogSliderTrack.kt @@ -0,0 +1,347 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.compose + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxScope +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.width +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi +import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.SliderColors +import androidx.compose.material3.SliderDefaults +import androidx.compose.material3.SliderState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.Layout +import androidx.compose.ui.layout.Measurable +import androidx.compose.ui.layout.MeasurePolicy +import androidx.compose.ui.layout.MeasureResult +import androidx.compose.ui.layout.MeasureScope +import androidx.compose.ui.layout.Placeable +import androidx.compose.ui.layout.layoutId +import androidx.compose.ui.unit.Constraints +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.util.fastFilter +import androidx.compose.ui.util.fastFirst +import kotlin.math.min + +@Composable +@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) +fun VolumeDialogSliderTrack( + sliderState: SliderState, + colors: SliderColors, + isEnabled: Boolean, + modifier: Modifier = Modifier, + thumbTrackGapSize: Dp = 6.dp, + trackCornerSize: Dp = 12.dp, + trackInsideCornerSize: Dp = 2.dp, + trackSize: Dp = 40.dp, + activeTrackStartIcon: (@Composable BoxScope.(iconsState: SliderIconsState) -> Unit)? = null, + activeTrackEndIcon: (@Composable BoxScope.(iconsState: SliderIconsState) -> Unit)? = null, + inactiveTrackStartIcon: (@Composable BoxScope.(iconsState: SliderIconsState) -> Unit)? = null, + inactiveTrackEndIcon: (@Composable BoxScope.(iconsState: SliderIconsState) -> Unit)? = null, +) { + val measurePolicy = remember(sliderState) { TrackMeasurePolicy(sliderState) } + Layout( + measurePolicy = measurePolicy, + content = { + SliderDefaults.Track( + sliderState = sliderState, + colors = colors, + enabled = isEnabled, + trackCornerSize = trackCornerSize, + trackInsideCornerSize = trackInsideCornerSize, + drawStopIndicator = null, + thumbTrackGapSize = thumbTrackGapSize, + drawTick = { _, _ -> }, + modifier = Modifier.width(trackSize).layoutId(Contents.Track), + ) + + TrackIcon( + icon = activeTrackStartIcon, + contentsId = Contents.Active.TrackStartIcon, + isEnabled = isEnabled, + colors = colors, + state = measurePolicy, + ) + TrackIcon( + icon = activeTrackEndIcon, + contentsId = Contents.Active.TrackEndIcon, + isEnabled = isEnabled, + colors = colors, + state = measurePolicy, + ) + TrackIcon( + icon = inactiveTrackStartIcon, + contentsId = Contents.Inactive.TrackStartIcon, + isEnabled = isEnabled, + colors = colors, + state = measurePolicy, + ) + TrackIcon( + icon = inactiveTrackEndIcon, + contentsId = Contents.Inactive.TrackEndIcon, + isEnabled = isEnabled, + colors = colors, + state = measurePolicy, + ) + }, + modifier = modifier, + ) +} + +@Composable +private fun TrackIcon( + icon: (@Composable BoxScope.(sliderIconsState: SliderIconsState) -> Unit)?, + isEnabled: Boolean, + contentsId: Contents, + state: SliderIconsState, + colors: SliderColors, + modifier: Modifier = Modifier, +) { + icon ?: return + Box(modifier = modifier.layoutId(contentsId).fillMaxSize()) { + CompositionLocalProvider( + LocalContentColor provides contentsId.getColor(colors, isEnabled) + ) { + icon(state) + } + } +} + +@OptIn(ExperimentalMaterial3Api::class) +private class TrackMeasurePolicy(private val sliderState: SliderState) : + MeasurePolicy, SliderIconsState { + + private val isVisible: Map<Contents, MutableState<Boolean>> = + mutableMapOf( + Contents.Active.TrackStartIcon to mutableStateOf(false), + Contents.Active.TrackEndIcon to mutableStateOf(false), + Contents.Inactive.TrackStartIcon to mutableStateOf(false), + Contents.Inactive.TrackEndIcon to mutableStateOf(false), + ) + + override val isActiveTrackStartIconVisible: Boolean + get() = isVisible.getValue(Contents.Active.TrackStartIcon).value + + override val isActiveTrackEndIconVisible: Boolean + get() = isVisible.getValue(Contents.Active.TrackEndIcon).value + + override val isInactiveTrackStartIconVisible: Boolean + get() = isVisible.getValue(Contents.Inactive.TrackStartIcon).value + + override val isInactiveTrackEndIconVisible: Boolean + get() = isVisible.getValue(Contents.Inactive.TrackEndIcon).value + + override fun MeasureScope.measure( + measurables: List<Measurable>, + constraints: Constraints, + ): MeasureResult { + val track = measurables.fastFirst { it.layoutId == Contents.Track }.measure(constraints) + + val iconSize = min(track.width, track.height) + val iconConstraints = constraints.copy(maxWidth = iconSize, maxHeight = iconSize) + + val icons = + measurables + .fastFilter { it.layoutId != Contents.Track } + .associateBy( + keySelector = { it.layoutId as Contents }, + valueTransform = { it.measure(iconConstraints) }, + ) + + return layout(track.width, track.height) { + with(Contents.Track) { + performPlacing( + placeable = track, + width = track.width, + height = track.height, + sliderState = sliderState, + ) + } + + for (iconLayoutId in icons.keys) { + with(iconLayoutId) { + performPlacing( + placeable = icons.getValue(iconLayoutId), + width = track.width, + height = track.height, + sliderState = sliderState, + ) + + isVisible.getValue(iconLayoutId).value = + isVisible( + placeable = icons.getValue(iconLayoutId), + width = track.width, + height = track.height, + sliderState = sliderState, + ) + } + } + } + } +} + +@OptIn(ExperimentalMaterial3Api::class) +private sealed interface Contents { + + data object Track : Contents { + override fun Placeable.PlacementScope.performPlacing( + placeable: Placeable, + width: Int, + height: Int, + sliderState: SliderState, + ) = placeable.place(x = 0, y = 0) + + override fun isVisible( + placeable: Placeable, + width: Int, + height: Int, + sliderState: SliderState, + ) = true + + override fun getColor(sliderColors: SliderColors, isEnabled: Boolean): Color = + error("Unsupported") + } + + interface Active : Contents { + override fun getColor(sliderColors: SliderColors, isEnabled: Boolean): Color { + return if (isEnabled) { + sliderColors.activeTickColor + } else { + sliderColors.disabledActiveTickColor + } + } + + data object TrackStartIcon : Active { + override fun Placeable.PlacementScope.performPlacing( + placeable: Placeable, + width: Int, + height: Int, + sliderState: SliderState, + ) = + placeable.place( + x = 0, + y = (height * (1 - sliderState.coercedValueAsFraction)).toInt(), + ) + + override fun isVisible( + placeable: Placeable, + width: Int, + height: Int, + sliderState: SliderState, + ): Boolean = (height * (sliderState.coercedValueAsFraction)).toInt() > placeable.height + } + + data object TrackEndIcon : Active { + override fun Placeable.PlacementScope.performPlacing( + placeable: Placeable, + width: Int, + height: Int, + sliderState: SliderState, + ) = placeable.place(x = 0, y = (height - placeable.height)) + + override fun isVisible( + placeable: Placeable, + width: Int, + height: Int, + sliderState: SliderState, + ): Boolean = (height * (sliderState.coercedValueAsFraction)).toInt() > placeable.height + } + } + + interface Inactive : Contents { + + override fun getColor(sliderColors: SliderColors, isEnabled: Boolean): Color { + return if (isEnabled) { + sliderColors.inactiveTickColor + } else { + sliderColors.disabledInactiveTickColor + } + } + + data object TrackStartIcon : Inactive { + override fun Placeable.PlacementScope.performPlacing( + placeable: Placeable, + width: Int, + height: Int, + sliderState: SliderState, + ) { + placeable.place(x = 0, y = 0) + } + + override fun isVisible( + placeable: Placeable, + width: Int, + height: Int, + sliderState: SliderState, + ): Boolean = + (height * (1 - sliderState.coercedValueAsFraction)).toInt() > placeable.height + } + + data object TrackEndIcon : Inactive { + override fun Placeable.PlacementScope.performPlacing( + placeable: Placeable, + width: Int, + height: Int, + sliderState: SliderState, + ) { + placeable.place( + x = 0, + y = + (height * (1 - sliderState.coercedValueAsFraction)).toInt() - + placeable.height, + ) + } + + override fun isVisible( + placeable: Placeable, + width: Int, + height: Int, + sliderState: SliderState, + ): Boolean = + (height * (1 - sliderState.coercedValueAsFraction)).toInt() > placeable.height + } + } + + fun Placeable.PlacementScope.performPlacing( + placeable: Placeable, + width: Int, + height: Int, + sliderState: SliderState, + ) + + fun isVisible(placeable: Placeable, width: Int, height: Int, sliderState: SliderState): Boolean + + fun getColor(sliderColors: SliderColors, isEnabled: Boolean): Color +} + +/** Provides visibility state for each of the Slider's icons. */ +interface SliderIconsState { + val isActiveTrackStartIconVisible: Boolean + val isActiveTrackEndIconVisible: Boolean + val isInactiveTrackStartIconVisible: Boolean + val isInactiveTrackEndIconVisible: Boolean +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogOverscrollViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogOverscrollViewModel.kt index 0d41860d9f57..0fdf5d6266d0 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogOverscrollViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogOverscrollViewModel.kt @@ -95,18 +95,17 @@ constructor( private fun overscrollEvents(direction: Float): Flow<OverscrollEventModel> { var startPosition: Float? = null return inputEventsInteractor.event - .mapNotNull { (it as? SliderInputEvent.Touch)?.event } + .mapNotNull { it as? SliderInputEvent.Touch } .transform { touchEvent -> // Skip events from inside the slider bounds for the case when the user adjusts - // slider - // towards max when the slider is already on max value. - if (touchEvent.isFinalEvent()) { + // slider towards max when the slider is already on max value. + if (touchEvent is SliderInputEvent.Touch.End) { startPosition = null emit(OverscrollEventModel.Animate(0f)) return@transform } val currentStartPosition = startPosition - val newPosition: Float = touchEvent.rawY + val newPosition: Float = touchEvent.y if (currentStartPosition == null) { startPosition = newPosition } else { @@ -126,11 +125,6 @@ constructor( } } - /** @return true when the [MotionEvent] indicates the end of the gesture. */ - private fun MotionEvent.isFinalEvent(): Boolean { - return actionMasked == MotionEvent.ACTION_UP || actionMasked == MotionEvent.ACTION_CANCEL - } - /** Models overscroll event */ sealed interface OverscrollEventModel { diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderInputEventsViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderInputEventsViewModel.kt deleted file mode 100644 index 755776ac9723..000000000000 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderInputEventsViewModel.kt +++ /dev/null @@ -1,43 +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.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 javax.inject.Inject -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.filterNotNull -import kotlinx.coroutines.flow.stateIn - -@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) - } -} 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 index 8df9e788905c..b01046b377b0 100644 --- 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 @@ -20,17 +20,20 @@ import android.graphics.drawable.Drawable import com.android.systemui.volume.dialog.shared.model.VolumeDialogStreamModel data class VolumeDialogSliderStateModel( - val minValue: Float, - val maxValue: Float, val value: Float, + val isDisabled: Boolean, + val valueRange: ClosedFloatingPointRange<Float>, val icon: Drawable, ) -fun VolumeDialogStreamModel.toStateModel(icon: Drawable): VolumeDialogSliderStateModel { +fun VolumeDialogStreamModel.toStateModel( + isDisabled: Boolean, + icon: Drawable, +): VolumeDialogSliderStateModel { return VolumeDialogSliderStateModel( - minValue = levelMin.toFloat(), value = level.toFloat(), - maxValue = levelMax.toFloat(), + isDisabled = isDisabled, + valueRange = levelMin.toFloat()..levelMax.toFloat(), icon = icon, ) } 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 a752f1f78e74..e89d5ab53560 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 @@ -16,6 +16,9 @@ package com.android.systemui.volume.dialog.sliders.ui.viewmodel +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.input.pointer.PointerEvent +import androidx.compose.ui.input.pointer.PointerEventType import com.android.systemui.util.time.SystemClock import com.android.systemui.volume.Events import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog @@ -23,20 +26,23 @@ import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogVisibili import com.android.systemui.volume.dialog.shared.VolumeDialogLogger 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.VolumeDialogSliderInputEventsInteractor import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSliderInteractor import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSliderType +import com.android.systemui.volume.dialog.sliders.shared.model.SliderInputEvent import javax.inject.Inject +import kotlin.math.roundToInt import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.combine 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 @@ -62,6 +68,7 @@ constructor( private val visibilityInteractor: VolumeDialogVisibilityInteractor, @VolumeDialog private val coroutineScope: CoroutineScope, private val volumeDialogSliderIconProvider: VolumeDialogSliderIconProvider, + private val inputEventsInteractor: VolumeDialogSliderInputEventsInteractor, private val systemClock: SystemClock, private val logger: VolumeDialogLogger, ) { @@ -77,11 +84,12 @@ constructor( .stateIn(coroutineScope, SharingStarted.Eagerly, null) .filterNotNull() - val isDisabledByZenMode: Flow<Boolean> = interactor.isDisabledByZenMode val state: Flow<VolumeDialogSliderStateModel> = - model - .flatMapLatest { streamModel -> - with(streamModel) { + combine( + interactor.isDisabledByZenMode, + model, + model.flatMapLatest { streamModel -> + with(streamModel) { val isMuted = muteSupported && muted when (sliderType) { is VolumeDialogSliderType.Stream -> @@ -101,7 +109,9 @@ constructor( } } } - .map { icon -> streamModel.toStateModel(icon) } + }, + ) { isDisabledByZenMode, model, icon -> + model.toStateModel(icon = icon, isDisabled = isDisabledByZenMode) } .stateIn(coroutineScope, SharingStarted.Eagerly, null) .filterNotNull() @@ -116,11 +126,14 @@ constructor( .launchIn(coroutineScope) } - fun setStreamVolume(volume: Int, fromUser: Boolean) { + fun setStreamVolume(volume: Float, fromUser: Boolean) { if (fromUser) { visibilityInteractor.resetDismissTimeout() userVolumeUpdates.value = - VolumeUpdate(newVolumeLevel = volume, timestampMillis = getTimestampMillis()) + VolumeUpdate( + newVolumeLevel = volume.roundToInt(), + timestampMillis = getTimestampMillis(), + ) } } @@ -128,6 +141,28 @@ constructor( logger.onVolumeSliderAdjustmentFinished(volume = volume, stream = sliderType.audioStream) } + fun onTouchEvent(pointerEvent: PointerEvent) { + val position: Offset = pointerEvent.changes.first().position + when (pointerEvent.type) { + PointerEventType.Press -> + inputEventsInteractor.onTouchEvent( + SliderInputEvent.Touch.Start(position.x, position.y) + ) + PointerEventType.Move -> + inputEventsInteractor.onTouchEvent( + SliderInputEvent.Touch.Move(position.x, position.y) + ) + PointerEventType.Scroll -> + inputEventsInteractor.onTouchEvent( + SliderInputEvent.Touch.Move(position.x, position.y) + ) + PointerEventType.Release -> + inputEventsInteractor.onTouchEvent( + SliderInputEvent.Touch.End(position.x, position.y) + ) + } + } + private fun getTimestampMillis(): Long = systemClock.uptimeMillis() private data class VolumeUpdate(val newVolumeLevel: Int, val timestampMillis: Long) diff --git a/packages/SystemUI/src/com/android/systemui/volume/haptics/ui/VolumeHapticsConfigsProvider.kt b/packages/SystemUI/src/com/android/systemui/volume/haptics/ui/VolumeHapticsConfigsProvider.kt new file mode 100644 index 000000000000..92e9bf2d1ffc --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/haptics/ui/VolumeHapticsConfigsProvider.kt @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume.haptics.ui + +import com.android.systemui.haptics.slider.SeekableSliderTrackerConfig +import com.android.systemui.haptics.slider.SliderHapticFeedbackConfig + +object VolumeHapticsConfigsProvider { + + fun sliderHapticFeedbackConfig( + valueRange: ClosedFloatingPointRange<Float> + ): SliderHapticFeedbackConfig { + val sliderStepSize = 1f / (valueRange.endInclusive - valueRange.start) + return SliderHapticFeedbackConfig( + lowerBookendScale = 0.2f, + progressBasedDragMinScale = 0.2f, + progressBasedDragMaxScale = 0.5f, + deltaProgressForDragThreshold = 0f, + additionalVelocityMaxBump = 0.2f, + maxVelocityToScale = 0.1f, /* slider progress(from 0 to 1) per sec */ + sliderStepSize = sliderStepSize, + ) + } + + val seekableSliderTrackerConfig = + SeekableSliderTrackerConfig(lowerBookendThreshold = 0f, upperBookendThreshold = 1f) +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/MediaOutputAvailabilityCriteria.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/MediaOutputAvailabilityCriteria.kt new file mode 100644 index 000000000000..bee45645bfdb --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/MediaOutputAvailabilityCriteria.kt @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.panel.component.mediaoutput.domain + +import android.content.Context +import com.android.settingslib.media.PhoneMediaDevice.isDesktop +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope +import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.stateIn + +@VolumePanelScope +class MediaOutputAvailabilityCriteria +@Inject +constructor( + @Application private val context: Context, + @VolumePanelScope private val scope: CoroutineScope, +) : ComponentAvailabilityCriteria { + + private val availability = + flow { emit(!isDesktop(context)) }.stateIn(scope, SharingStarted.WhileSubscribed(), false) + + override fun isAvailable(): Flow<Boolean> = availability +} diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/GradientColorWallpaper.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/GradientColorWallpaper.kt index c1fb0e80cafc..760e94c72f19 100644 --- a/packages/SystemUI/src/com/android/systemui/wallpapers/GradientColorWallpaper.kt +++ b/packages/SystemUI/src/com/android/systemui/wallpapers/GradientColorWallpaper.kt @@ -16,6 +16,7 @@ package com.android.systemui.wallpapers +import android.app.Flags import android.graphics.Canvas import android.graphics.Paint import android.service.wallpaper.WallpaperService @@ -26,7 +27,15 @@ import androidx.core.graphics.toRectF /** A wallpaper that shows a static gradient color image wallpaper. */ class GradientColorWallpaper : WallpaperService() { - override fun onCreateEngine(): Engine = GradientColorWallpaperEngine() + override fun onCreateEngine(): Engine = + if (Flags.enableConnectedDisplaysWallpaper()) { + GradientColorWallpaperEngine() + } else { + EmptyWallpaperEngine() + } + + /** Empty engine used when the feature flag is disabled. */ + inner class EmptyWallpaperEngine : Engine() inner class GradientColorWallpaperEngine : Engine() { init { diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/NoopWallpaperRepository.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/NoopWallpaperRepository.kt index 9055d18a9f55..ec74f4f47bc9 100644 --- a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/NoopWallpaperRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/NoopWallpaperRepository.kt @@ -23,6 +23,7 @@ import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.flowOf /** * A no-op implementation of [WallpaperRepository]. @@ -33,6 +34,7 @@ import kotlinx.coroutines.flow.asStateFlow @SysUISingleton class NoopWallpaperRepository @Inject constructor() : WallpaperRepository { override val wallpaperInfo: StateFlow<WallpaperInfo?> = MutableStateFlow(null).asStateFlow() - override val wallpaperSupportsAmbientMode = MutableStateFlow(false).asStateFlow() + override val wallpaperSupportsAmbientMode = flowOf(false) override var rootView: View? = null + override val shouldSendFocalArea: StateFlow<Boolean> = MutableStateFlow(false).asStateFlow() } diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt index 015b480eddc8..9794c619041e 100644 --- a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt @@ -25,11 +25,12 @@ import android.os.Bundle import android.os.UserHandle import android.view.View import androidx.annotation.VisibleForTesting +import com.android.app.tracing.coroutines.launchTraced as launch +import com.android.internal.R import com.android.systemui.Flags import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background -import com.android.systemui.keyguard.data.repository.KeyguardClockRepository import com.android.systemui.keyguard.data.repository.KeyguardRepository import com.android.systemui.shared.Flags.ambientAod import com.android.systemui.user.data.model.SelectedUserModel @@ -43,14 +44,15 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.SharingStarted.Companion.WhileSubscribed import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn -import com.android.app.tracing.coroutines.launchTraced as launch import kotlinx.coroutines.withContext /** A repository storing information about the current wallpaper. */ @@ -59,10 +61,13 @@ interface WallpaperRepository { val wallpaperInfo: StateFlow<WallpaperInfo?> /** Emits true if the current user's current wallpaper supports ambient mode. */ - val wallpaperSupportsAmbientMode: StateFlow<Boolean> + val wallpaperSupportsAmbientMode: Flow<Boolean> /** Set rootView to get its windowToken afterwards */ var rootView: View? + + /** when we use magic portrait wallpapers, we should always get its bounds from keyguard */ + val shouldSendFocalArea: StateFlow<Boolean> } @SysUISingleton @@ -74,13 +79,9 @@ constructor( broadcastDispatcher: BroadcastDispatcher, userRepository: UserRepository, keyguardRepository: KeyguardRepository, - keyguardClockRepository: KeyguardClockRepository, private val wallpaperManager: WallpaperManager, context: Context, ) : WallpaperRepository { - private val deviceSupportsAodWallpaper = - context.resources.getBoolean(com.android.internal.R.bool.config_dozeSupportsAodWallpaper) - private val wallpaperChanged: Flow<Unit> = broadcastDispatcher .broadcastFlow(IntentFilter(Intent.ACTION_WALLPAPER_CHANGED), user = UserHandle.ALL) @@ -98,30 +99,10 @@ constructor( // Only update the wallpaper status once the user selection has finished. .filter { it.selectionStatus == SelectionStatus.SELECTION_COMPLETE } - /** The bottom of notification stack respect to the top of screen. */ - private val notificationStackAbsoluteBottom: StateFlow<Float> = - keyguardRepository.notificationStackAbsoluteBottom - - /** The top of shortcut respect to the top of screen. */ - private val shortcutAbsoluteTop: StateFlow<Float> = keyguardRepository.shortcutAbsoluteTop - - /** - * The top of notification stack to give a default state of lockscreen remaining space for - * states with notifications to compare with. It's the bottom of smartspace date and weather - * smartspace in small clock state, plus proper bottom margin. - */ - private val notificationStackDefaultTop = keyguardClockRepository.notificationDefaultTop @VisibleForTesting var sendLockscreenLayoutJob: Job? = null - private val lockscreenRemainingSpaceWithNotification: Flow<Triple<Float, Float, Float>> = - combine( - notificationStackAbsoluteBottom, - notificationStackDefaultTop, - shortcutAbsoluteTop, - ::Triple, - ) override val wallpaperInfo: StateFlow<WallpaperInfo?> = - if (!wallpaperManager.isWallpaperSupported || !deviceSupportsAodWallpaper) { + if (!wallpaperManager.isWallpaperSupported) { MutableStateFlow(null).asStateFlow() } else { combine(wallpaperChanged, selectedUser, ::Pair) @@ -136,37 +117,21 @@ constructor( ) } - override val wallpaperSupportsAmbientMode: StateFlow<Boolean> = - wallpaperInfo - .map { - if (ambientAod()) { - // Force this mode for now, until ImageWallpaper supports it directly - // TODO(b/371236225) - true - } else { - // If WallpaperInfo is null, it's ImageWallpaper which never supports ambient - // mode. - it?.supportsAmbientMode() == true - } - } - .stateIn( - scope, - // Always be listening for wallpaper changes. - SharingStarted.Eagerly, - initialValue = if (ambientAod()) true else false, - ) + override val wallpaperSupportsAmbientMode: Flow<Boolean> = + flowOf(context.resources.getBoolean(R.bool.config_dozeSupportsAodWallpaper) && ambientAod()) override var rootView: View? = null - val shouldSendNotificationLayout = + override val shouldSendFocalArea = wallpaperInfo .map { - val shouldSendNotificationLayout = shouldSendNotificationLayout(it) + val shouldSendNotificationLayout = + it?.component?.className == MAGIC_PORTRAIT_CLASSNAME if (shouldSendNotificationLayout) { sendLockscreenLayoutJob = scope.launch { - lockscreenRemainingSpaceWithNotification.collect { - (notificationBottom, notificationDefaultTop, shortcutTop) -> + keyguardRepository.wallpaperFocalAreaBounds.collect { + wallpaperFocalAreaBounds -> wallpaperManager.sendWallpaperCommand( /* windowToken = */ rootView?.windowToken, /* action = */ WallpaperManager @@ -175,14 +140,22 @@ constructor( /* y = */ 0, /* z = */ 0, /* extras = */ Bundle().apply { - putFloat("screenLeft", 0F) - putFloat("smartspaceBottom", notificationDefaultTop) - putFloat("notificationBottom", notificationBottom) putFloat( - "screenRight", - context.resources.displayMetrics.widthPixels.toFloat(), + "wallpaperFocalAreaLeft", + wallpaperFocalAreaBounds.left, + ) + putFloat( + "wallpaperFocalAreaRight", + wallpaperFocalAreaBounds.right, + ) + putFloat( + "wallpaperFocalAreaTop", + wallpaperFocalAreaBounds.top, + ) + putFloat( + "wallpaperFocalAreaBottom", + wallpaperFocalAreaBounds.bottom, ) - putFloat("shortCutTop", shortcutTop) }, ) } @@ -194,10 +167,9 @@ constructor( } .stateIn( scope, - // Always be listening for wallpaper changes. - if (Flags.magicPortraitWallpapers()) SharingStarted.Eagerly - else SharingStarted.Lazily, - initialValue = false, + // Always be listening for wallpaper changes when magic portrait flag is on + if (Flags.magicPortraitWallpapers()) SharingStarted.Eagerly else WhileSubscribed(), + initialValue = Flags.magicPortraitWallpapers(), ) private suspend fun getWallpaper(selectedUser: SelectedUserModel): WallpaperInfo? { @@ -206,14 +178,6 @@ constructor( } } - private fun shouldSendNotificationLayout(wallpaperInfo: WallpaperInfo?): Boolean { - return if (wallpaperInfo != null && wallpaperInfo.component != null) { - wallpaperInfo.component!!.className == MAGIC_PORTRAIT_CLASSNAME - } else { - false - } - } - companion object { const val MAGIC_PORTRAIT_CLASSNAME = "com.google.android.apps.magicportrait.service.MagicPortraitWallpaperService" diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/domain/interactor/WallpaperInteractor.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/domain/interactor/WallpaperInteractor.kt index 88795cada716..1b15832d4913 100644 --- a/packages/SystemUI/src/com/android/systemui/wallpapers/domain/interactor/WallpaperInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/wallpapers/domain/interactor/WallpaperInteractor.kt @@ -18,9 +18,9 @@ package com.android.systemui.wallpapers.domain.interactor import com.android.systemui.wallpapers.data.repository.WallpaperRepository import javax.inject.Inject -import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.Flow class WallpaperInteractor @Inject constructor(val wallpaperRepository: WallpaperRepository) { - val wallpaperSupportsAmbientMode: StateFlow<Boolean> = + val wallpaperSupportsAmbientMode: Flow<Boolean> = wallpaperRepository.wallpaperSupportsAmbientMode } diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/ui/viewmodel/WallpaperViewModel.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/ui/viewmodel/WallpaperViewModel.kt index a51acf66432a..197f2b010a97 100644 --- a/packages/SystemUI/src/com/android/systemui/wallpapers/ui/viewmodel/WallpaperViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/wallpapers/ui/viewmodel/WallpaperViewModel.kt @@ -18,8 +18,8 @@ package com.android.systemui.wallpapers.ui.viewmodel import com.android.systemui.wallpapers.domain.interactor.WallpaperInteractor import javax.inject.Inject -import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.Flow class WallpaperViewModel @Inject constructor(interactor: WallpaperInteractor) { - val wallpaperSupportsAmbientMode: StateFlow<Boolean> = interactor.wallpaperSupportsAmbientMode + val wallpaperSupportsAmbientMode: Flow<Boolean> = interactor.wallpaperSupportsAmbientMode } diff --git a/packages/SystemUI/src/com/android/systemui/window/domain/interactor/WindowRootViewBlurInteractor.kt b/packages/SystemUI/src/com/android/systemui/window/domain/interactor/WindowRootViewBlurInteractor.kt index fee32b5e7e78..8e0616c00196 100644 --- a/packages/SystemUI/src/com/android/systemui/window/domain/interactor/WindowRootViewBlurInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/window/domain/interactor/WindowRootViewBlurInteractor.kt @@ -17,13 +17,23 @@ package com.android.systemui.window.domain.interactor import android.util.Log +import com.android.systemui.Flags import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER import com.android.systemui.window.data.repository.WindowRootViewBlurRepository import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn /** * Interactor that provides the blur state for the window root view @@ -33,9 +43,21 @@ import kotlinx.coroutines.flow.asStateFlow class WindowRootViewBlurInteractor @Inject constructor( + @Application private val applicationScope: CoroutineScope, private val keyguardInteractor: KeyguardInteractor, + keyguardTransitionInteractor: KeyguardTransitionInteractor, private val repository: WindowRootViewBlurRepository, ) { + private var isBouncerTransitionInProgress: StateFlow<Boolean> = + if (Flags.bouncerUiRevamp()) { + keyguardTransitionInteractor + .transitionValue(PRIMARY_BOUNCER) + .map { it > 0f } + .distinctUntilChanged() + .stateIn(applicationScope, SharingStarted.Eagerly, false) + } else { + MutableStateFlow(false) + } /** * Invoked by the view after blur of [appliedBlurRadius] was successfully applied on the window @@ -57,8 +79,7 @@ constructor( val onBlurAppliedEvent: Flow<Int> = repository.onBlurApplied /** - * Request to apply blur while on bouncer, this takes precedence over other blurs (from - * shade). + * Request to apply blur while on bouncer, this takes precedence over other blurs (from shade). */ fun requestBlurForBouncer(blurRadius: Int) { repository.isBlurOpaque.value = false @@ -76,7 +97,10 @@ constructor( * @return whether the request for blur was processed or not. */ fun requestBlurForShade(blurRadius: Int, opaque: Boolean): Boolean { - if (keyguardInteractor.primaryBouncerShowing.value) { + // We need to check either of these because they are two different sources of truth, + // primaryBouncerShowing changes early to true/false, but blur is + // coordinated by transition value. + if (keyguardInteractor.primaryBouncerShowing.value || isBouncerTransitionInProgress.value) { return false } Log.d(TAG, "requestingBlurForShade for $blurRadius $opaque") diff --git a/packages/SystemUI/src/com/android/systemui/window/ui/WindowRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/window/ui/WindowRootViewBinder.kt index d2069cfdfdc6..dbccc1d8cca4 100644 --- a/packages/SystemUI/src/com/android/systemui/window/ui/WindowRootViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/window/ui/WindowRootViewBinder.kt @@ -49,7 +49,6 @@ object WindowRootViewBinder { view.repeatWhenAttached { Log.d(TAG, "Binding root view") var frameCallbackPendingExecution: FrameCallback? = null - val viewRootImpl = view.rootView.viewRootImpl view.viewModel( minWindowLifecycleState = WindowLifecycleState.ATTACHED, factory = { viewModelFactory.create() }, @@ -64,13 +63,13 @@ object WindowRootViewBinder { val newFrameCallback = FrameCallback { frameCallbackPendingExecution = null blurUtils.applyBlur( - viewRootImpl, + view.rootView?.viewRootImpl, blurState.radius, blurState.isOpaque, ) viewModel.onBlurApplied(blurState.radius) } - blurUtils.prepareBlur(viewRootImpl, blurState.radius) + blurUtils.prepareBlur(view.rootView?.viewRootImpl, blurState.radius) if (frameCallbackPendingExecution != null) { choreographer.removeFrameCallback(frameCallbackPendingExecution) } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt index 1729a4dfa945..bac2c47f51c7 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt @@ -55,7 +55,6 @@ import com.android.systemui.plugins.clocks.ClockTickRate import com.android.systemui.plugins.clocks.ThemeConfig import com.android.systemui.plugins.clocks.ZenData import com.android.systemui.plugins.clocks.ZenData.ZenMode -import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.res.R import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.policy.BatteryController @@ -132,7 +131,6 @@ class ClockEventControllerTest : SysuiTestCase() { @Mock private lateinit var parentView: View @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor @Mock private lateinit var userTracker: UserTracker - @Mock private lateinit var powerInteractor: PowerInteractor @Mock private lateinit var zenModeController: ZenModeController private var zenModeControllerCallback: ZenModeController.Callback? = null @@ -180,7 +178,6 @@ class ClockEventControllerTest : SysuiTestCase() { zenModeController, zenModeInteractor, userTracker, - powerInteractor, ) underTest.clock = clock diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 2645811fa4ad..312d2ffd74e4 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -38,6 +38,7 @@ import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_STATE_CANCELL import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_STATE_STOPPED; import static com.android.keyguard.KeyguardUpdateMonitor.DEFAULT_CANCEL_SIGNAL_TIMEOUT; import static com.android.keyguard.KeyguardUpdateMonitor.HAL_POWER_PRESS_TIMEOUT; +import static com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2; import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED; import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN; @@ -139,6 +140,7 @@ import com.android.systemui.biometrics.AuthController; import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider; import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor; import com.android.systemui.deviceentry.data.repository.FaceWakeUpTriggersConfig; import com.android.systemui.deviceentry.data.repository.FaceWakeUpTriggersConfigImpl; import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor; @@ -180,6 +182,9 @@ import org.mockito.MockitoAnnotations; import org.mockito.MockitoSession; import org.mockito.quality.Strictness; +import platform.test.runner.parameterized.ParameterizedAndroidJunit4; +import platform.test.runner.parameterized.Parameters; + import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -189,9 +194,6 @@ import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; -import platform.test.runner.parameterized.ParameterizedAndroidJunit4; -import platform.test.runner.parameterized.Parameters; - @SmallTest @RunWith(ParameterizedAndroidJunit4.class) @TestableLooper.RunWithLooper @@ -304,6 +306,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { private JavaAdapter mJavaAdapter; @Mock private SceneInteractor mSceneInteractor; + @Mock + private CommunalSceneInteractor mCommunalSceneInteractor; @Captor private ArgumentCaptor<FaceAuthenticationListener> mFaceAuthenticationListener; @@ -1084,6 +1088,49 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } @Test + @EnableFlags(FLAG_GLANCEABLE_HUB_V2) + public void udfpsStopsListeningWhenCommunalShowing() { + // GIVEN keyguard showing + mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON); + mKeyguardUpdateMonitor.setKeyguardShowing(true, false); + + assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(true)).isTrue(); + + // WHEN communal is shown + mKeyguardUpdateMonitor.onCommunalShowingChanged(true); + + // THEN shouldn't listen for fingerprint + assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(true)).isFalse(); + + // WHEN alternate bouncer shows on top of communal, we should listen for fingerprint + mKeyguardUpdateMonitor.setAlternateBouncerVisibility(true); + assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(true)).isTrue(); + + // WHEN communal is hidden + mKeyguardUpdateMonitor.onCommunalShowingChanged(false); + mKeyguardUpdateMonitor.setAlternateBouncerVisibility(false); + + // THEN listen for fingerprint + assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(true)).isTrue(); + } + + @Test + @EnableFlags(FLAG_GLANCEABLE_HUB_V2) + public void sfpsNotAffectedByCommunalShowing() { + // GIVEN keyguard showing + mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON); + mKeyguardUpdateMonitor.setKeyguardShowing(true, false); + + assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isTrue(); + + // WHEN communal is shown + mKeyguardUpdateMonitor.onCommunalShowingChanged(true); + + // THEN we should still listen for fingerprint if not UDFPS + assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isTrue(); + } + + @Test public void testFingerprintPowerPressed_restartsFingerprintListeningStateWithDelay() { mKeyguardUpdateMonitor.mFingerprintAuthenticationCallback .onAuthenticationError(FingerprintManager.BIOMETRIC_ERROR_POWER_PRESSED, ""); @@ -2669,7 +2716,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mTaskStackChangeListeners, mSelectedUserInteractor, mActivityTaskManager, () -> mAlternateBouncerInteractor, () -> mJavaAdapter, - () -> mSceneInteractor); + () -> mSceneInteractor, + mCommunalSceneInteractor); setAlternateBouncerVisibility(false); setPrimaryBouncerVisibility(false); setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker); diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt index 39e1e1d8bb57..b1d0f86c364c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt @@ -2,6 +2,9 @@ package com.android.systemui.controls.management import android.content.ComponentName import android.content.Intent +import android.content.pm.ApplicationInfo +import android.content.pm.ServiceInfo +import android.graphics.drawable.Drawable import android.os.Bundle import android.testing.TestableLooper import android.view.View @@ -11,25 +14,31 @@ import android.window.OnBackInvokedDispatcher import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import androidx.test.rule.ActivityTestRule -import com.android.systemui.res.R import com.android.systemui.SysuiTestCase import com.android.systemui.activity.SingleActivityFactory +import com.android.systemui.controls.ControlsServiceInfo import com.android.systemui.controls.CustomIconCache import com.android.systemui.controls.controller.ControlsControllerImpl +import com.android.systemui.res.R import com.android.systemui.settings.UserTracker import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.time.FakeSystemClock +import com.android.systemui.utils.SafeIconLoader import com.google.common.truth.Truth.assertThat import java.util.concurrent.CountDownLatch import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith +import org.mockito.Answers import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatchers.eq import org.mockito.Captor import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.Mockito.mock import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @SmallTest @@ -41,6 +50,7 @@ class ControlsEditingActivityTest : SysuiTestCase() { val TEST_COMPONENT = ComponentName("TestPackageName", "TestClassName") val TEST_STRUCTURE: CharSequence = "TestStructure" val TEST_APP: CharSequence = "TestApp" + val TEST_UID = 12345 } private val uiExecutor = FakeExecutor(FakeSystemClock()) @@ -51,6 +61,10 @@ class ControlsEditingActivityTest : SysuiTestCase() { @Mock lateinit var customIconCache: CustomIconCache + @Mock lateinit var controlsListingController: ControlsListingController + + @Mock(answer = Answers.RETURNS_MOCKS) lateinit var safeIconLoaderFactory: SafeIconLoader.Factory + private var latch: CountDownLatch = CountDownLatch(1) @Mock private lateinit var mockDispatcher: OnBackInvokedDispatcher @@ -66,8 +80,10 @@ class ControlsEditingActivityTest : SysuiTestCase() { controller, userTracker, customIconCache, + controlsListingController, + safeIconLoaderFactory, mockDispatcher, - latch + latch, ) }, /* initialTouchMode= */ false, @@ -77,6 +93,9 @@ class ControlsEditingActivityTest : SysuiTestCase() { @Before fun setUp() { MockitoAnnotations.initMocks(this) + + val serviceInfo = ControlsServiceInfo(TEST_COMPONENT, "", TEST_UID) + `when`(controlsListingController.getCurrentServices()).thenReturn(listOf(serviceInfo)) } @Test @@ -86,7 +105,7 @@ class ControlsEditingActivityTest : SysuiTestCase() { verify(mockDispatcher) .registerOnBackInvokedCallback( eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT), - captureCallback.capture() + captureCallback.capture(), ) activityRule.finishActivity() latch.await() // ensure activity is finished @@ -158,19 +177,40 @@ class ControlsEditingActivityTest : SysuiTestCase() { } ) + private fun ControlsServiceInfo( + componentName: ComponentName, + label: CharSequence, + uid: Int, + ): ControlsServiceInfo { + val serviceInfo = + ServiceInfo().apply { + applicationInfo = ApplicationInfo().apply { this.uid = uid } + packageName = componentName.packageName + name = componentName.className + } + return Mockito.spy(ControlsServiceInfo(mContext, serviceInfo)).apply { + Mockito.doReturn(label).`when`(this).loadLabel() + Mockito.doReturn(mock(Drawable::class.java)).`when`(this).loadIcon() + } + } + class TestableControlsEditingActivity( executor: FakeExecutor, controller: ControlsControllerImpl, userTracker: UserTracker, customIconCache: CustomIconCache, + controlsListingController: ControlsListingController, + safeIconLoaderFactory: SafeIconLoader.Factory, private val mockDispatcher: OnBackInvokedDispatcher, - private val latch: CountDownLatch + private val latch: CountDownLatch, ) : ControlsEditingActivity( executor, controller, userTracker, customIconCache, + controlsListingController, + safeIconLoaderFactory, ) { var startActivityData: StartActivityData? = null diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt index 7fb74b3439bc..5eb93721e735 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt @@ -2,6 +2,9 @@ package com.android.systemui.controls.management import android.content.ComponentName import android.content.Intent +import android.content.pm.ApplicationInfo +import android.content.pm.ServiceInfo +import android.graphics.drawable.Drawable import android.os.Bundle import android.service.controls.Control import android.testing.TestableLooper @@ -13,19 +16,21 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.FlakyTest import androidx.test.filters.SmallTest import androidx.test.rule.ActivityTestRule -import com.android.systemui.res.R import com.android.systemui.SysuiTestCase import com.android.systemui.activity.SingleActivityFactory import com.android.systemui.controls.ControlStatus +import com.android.systemui.controls.ControlsServiceInfo import com.android.systemui.controls.controller.ControlsController import com.android.systemui.controls.controller.ControlsControllerImpl import com.android.systemui.controls.controller.createLoadDataObject import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.flags.FakeFeatureFlags +import com.android.systemui.res.R import com.android.systemui.settings.UserTracker import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.capture import com.android.systemui.util.mockito.whenever +import com.android.systemui.utils.SafeIconLoader import com.google.common.truth.Truth.assertThat import com.google.common.util.concurrent.MoreExecutors import java.util.concurrent.CountDownLatch @@ -39,6 +44,7 @@ import org.mockito.Answers import org.mockito.ArgumentCaptor import org.mockito.Captor import org.mockito.Mock +import org.mockito.Mockito import org.mockito.Mockito.eq import org.mockito.Mockito.mock import org.mockito.Mockito.verify @@ -57,6 +63,7 @@ class ControlsFavoritingActivityTest : SysuiTestCase() { whenever(structure).thenReturn(TEST_STRUCTURE) } val TEST_APP: CharSequence = "TestApp" + val TEST_UID = 12345 private fun View.waitForPost() { val latch = CountDownLatch(1) @@ -72,6 +79,10 @@ class ControlsFavoritingActivityTest : SysuiTestCase() { @Mock lateinit var userTracker: UserTracker + @Mock lateinit var controlsListingController: ControlsListingController + + @Mock(answer = Answers.RETURNS_MOCKS) lateinit var safeIconLoaderFactory: SafeIconLoader.Factory + private var latch: CountDownLatch = CountDownLatch(1) @Mock private lateinit var mockDispatcher: OnBackInvokedDispatcher @@ -88,8 +99,10 @@ class ControlsFavoritingActivityTest : SysuiTestCase() { executor, controller, userTracker, + controlsListingController, + safeIconLoaderFactory, mockDispatcher, - latch + latch, ) }, /* initialTouchMode= */ false, @@ -99,6 +112,10 @@ class ControlsFavoritingActivityTest : SysuiTestCase() { @Before fun setUp() { MockitoAnnotations.initMocks(this) + + val serviceInfo = ControlsServiceInfo(TEST_COMPONENT, "", TEST_UID) + Mockito.`when`(controlsListingController.getCurrentServices()) + .thenReturn(listOf(serviceInfo)) } // b/259549854 to root-cause and fix @@ -110,7 +127,7 @@ class ControlsFavoritingActivityTest : SysuiTestCase() { verify(mockDispatcher) .registerOnBackInvokedCallback( eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT), - captureCallback.capture() + captureCallback.capture(), ) activityRule.finishActivity() latch.await() // ensure activity is finished @@ -178,17 +195,38 @@ class ControlsFavoritingActivityTest : SysuiTestCase() { } ) + private fun ControlsServiceInfo( + componentName: ComponentName, + label: CharSequence, + uid: Int, + ): ControlsServiceInfo { + val serviceInfo = + ServiceInfo().apply { + applicationInfo = ApplicationInfo().apply { this.uid = uid } + packageName = componentName.packageName + name = componentName.className + } + return Mockito.spy(ControlsServiceInfo(mContext, serviceInfo)).apply { + Mockito.doReturn(label).`when`(this).loadLabel() + Mockito.doReturn(mock(Drawable::class.java)).`when`(this).loadIcon() + } + } + class TestableControlsFavoritingActivity( executor: Executor, controller: ControlsControllerImpl, userTracker: UserTracker, + controlsListingController: ControlsListingController, + safeIconLoaderFactory: SafeIconLoader.Factory, private val mockDispatcher: OnBackInvokedDispatcher, - private val latch: CountDownLatch + private val latch: CountDownLatch, ) : ControlsFavoritingActivity( executor, controller, userTracker, + safeIconLoaderFactory, + controlsListingController, ) { var triedToFinish = false diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlViewHolderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlViewHolderTest.kt index 4b30fa5dd161..c1c90bd41fea 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlViewHolderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlViewHolderTest.kt @@ -21,6 +21,7 @@ import android.content.ComponentName import android.content.res.ColorStateList import android.graphics.drawable.GradientDrawable import android.graphics.drawable.Icon +import android.graphics.drawable.ShapeDrawable import android.service.controls.Control import android.service.controls.DeviceTypes import android.service.controls.templates.ControlTemplate @@ -30,18 +31,21 @@ import android.view.View import android.view.ViewGroup 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.controls.ControlsMetricsLogger import com.android.systemui.controls.controller.ControlInfo import com.android.systemui.controls.controller.ControlsController +import com.android.systemui.res.R import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.mockito.any import com.android.systemui.util.time.FakeSystemClock +import com.android.systemui.utils.SafeIconLoader import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.mock +import org.mockito.Mockito.`when` @SmallTest @RunWith(AndroidJUnit4::class) @@ -52,14 +56,20 @@ class ControlViewHolderTest : SysuiTestCase() { private lateinit var cvh: ControlViewHolder private lateinit var baseLayout: ViewGroup + private lateinit var safeIconLoader: SafeIconLoader @Before fun setUp() { TestableLooper.get(this).runWithLooper { - baseLayout = LayoutInflater.from(mContext).inflate( - R.layout.controls_base_item, null, false) as ViewGroup + baseLayout = + LayoutInflater.from(mContext).inflate(R.layout.controls_base_item, null, false) + as ViewGroup - cvh = ControlViewHolder( + safeIconLoader = mock(SafeIconLoader::class.java) + `when`(safeIconLoader.load(any())).thenReturn(PLAIN_DRAWABLE) + + cvh = + ControlViewHolder( baseLayout, mock(ControlsController::class.java), FakeExecutor(clock), @@ -68,15 +78,20 @@ class ControlViewHolderTest : SysuiTestCase() { mock(ControlsMetricsLogger::class.java), uid = 100, 0, - ) + safeIconLoader, + ) - val cws = ControlWithState( + val cws = + ControlWithState( ComponentName.createRelative("pkg", "cls"), ControlInfo( - CONTROL_ID, CONTROL_TITLE, "subtitle", DeviceTypes.TYPE_AIR_FRESHENER + CONTROL_ID, + CONTROL_TITLE, + "subtitle", + DeviceTypes.TYPE_AIR_FRESHENER, ), - Control.StatelessBuilder(CONTROL_ID, mock(PendingIntent::class.java)).build() - ) + Control.StatelessBuilder(CONTROL_ID, mock(PendingIntent::class.java)).build(), + ) cvh.bindData(cws, isLocked = false) } @@ -84,10 +99,11 @@ class ControlViewHolderTest : SysuiTestCase() { @Test fun updateStatusRow_customIconWithTint_iconTintRemains() { - val control = Control.StatelessBuilder(DEFAULT_CONTROL) + val control = + Control.StatelessBuilder(DEFAULT_CONTROL) .setCustomIcon( - Icon.createWithResource(mContext.resources, R.drawable.ic_emergency_star) - .setTint(TINT_COLOR) + Icon.createWithResource(mContext.resources, R.drawable.ic_emergency_star) + .setTint(TINT_COLOR) ) .build() @@ -99,10 +115,11 @@ class ControlViewHolderTest : SysuiTestCase() { @Test fun updateStatusRow_customIconWithTintList_iconTintListRemains() { val customIconTintList = ColorStateList.valueOf(TINT_COLOR) - val control = Control.StatelessBuilder(CONTROL_ID, mock(PendingIntent::class.java)) + val control = + Control.StatelessBuilder(CONTROL_ID, mock(PendingIntent::class.java)) .setCustomIcon( - Icon.createWithResource(mContext.resources, R.drawable.ic_emergency_star) - .setTintList(customIconTintList) + Icon.createWithResource(mContext.resources, R.drawable.ic_emergency_star) + .setTintList(customIconTintList) ) .build() @@ -113,22 +130,54 @@ class ControlViewHolderTest : SysuiTestCase() { @Test fun chevronIcon() { - val control = Control.StatefulBuilder(CONTROL_ID, mock(PendingIntent::class.java)) - .setStatus(Control.STATUS_OK) - .setControlTemplate(ControlTemplate.NO_TEMPLATE) - .build() - val cws = ControlWithState( - ComponentName.createRelative("pkg", "cls"), - ControlInfo( - CONTROL_ID, CONTROL_TITLE, "subtitle", DeviceTypes.TYPE_AIR_FRESHENER - ), - control - ) + val control = + Control.StatefulBuilder(CONTROL_ID, mock(PendingIntent::class.java)) + .setStatus(Control.STATUS_OK) + .setControlTemplate(ControlTemplate.NO_TEMPLATE) + .build() + val cws = + ControlWithState( + ComponentName.createRelative("pkg", "cls"), + ControlInfo(CONTROL_ID, CONTROL_TITLE, "subtitle", DeviceTypes.TYPE_AIR_FRESHENER), + control, + ) cvh.bindData(cws, false) val chevronIcon = baseLayout.requireViewById<View>(R.id.chevron_icon) assertThat(chevronIcon.visibility).isEqualTo(View.VISIBLE) } + + @Test + fun drawableLoadedSafely_showsPlainDrawableLoaded() { + val control = + Control.StatelessBuilder(DEFAULT_CONTROL) + .setCustomIcon( + Icon.createWithResource(mContext.resources, R.drawable.ic_emergency_star) + .setTint(TINT_COLOR) + ) + .build() + + cvh.updateStatusRow(enabled = true, CONTROL_TITLE, DRAWABLE, COLOR, control) + + assertThat(cvh.icon.drawable).isSameInstanceAs(PLAIN_DRAWABLE) + } + + @Test + fun drawableNotLoadedSafely_showsDefaultDrawable() { + `when`(safeIconLoader.load(any())).thenReturn(null) + + val control = + Control.StatelessBuilder(DEFAULT_CONTROL) + .setCustomIcon( + Icon.createWithResource(mContext.resources, R.drawable.ic_emergency_star) + .setTint(TINT_COLOR) + ) + .build() + + cvh.updateStatusRow(enabled = true, CONTROL_TITLE, DRAWABLE, COLOR, control) + + assertThat(cvh.icon.drawable).isSameInstanceAs(DRAWABLE) + } } private const val CONTROL_ID = "CONTROL_ID" @@ -136,6 +185,7 @@ private const val CONTROL_TITLE = "CONTROL_TITLE" private const val TINT_COLOR = 0x00ff00 // Should be different from [COLOR] private val DRAWABLE = GradientDrawable() +private val PLAIN_DRAWABLE = ShapeDrawable() private val COLOR = ColorStateList.valueOf(0xffff00) -private val DEFAULT_CONTROL = Control.StatelessBuilder( - CONTROL_ID, mock(PendingIntent::class.java)).build() +private val DEFAULT_CONTROL = + Control.StatelessBuilder(CONTROL_ID, mock(PendingIntent::class.java)).build() diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt index 20890a7780c8..3a0bda4b3259 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt @@ -63,6 +63,7 @@ import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.android.systemui.util.time.FakeSystemClock +import com.android.systemui.utils.SafeIconLoader import com.android.wm.shell.taskview.TaskView import com.android.wm.shell.taskview.TaskViewFactory import com.google.common.truth.Truth.assertThat @@ -71,6 +72,7 @@ import java.util.function.Consumer import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.Answers import org.mockito.Mock import org.mockito.Mockito.clearInvocations import org.mockito.Mockito.doAnswer @@ -102,6 +104,7 @@ class ControlsUiControllerImplTest : SysuiTestCase() { @Mock lateinit var featureFlags: FeatureFlags @Mock lateinit var packageManager: PackageManager @Mock lateinit var systemUIDialogFactory: SystemUIDialog.Factory + @Mock(answer = Answers.RETURNS_MOCKS) lateinit var safeIconLoaderFactory: SafeIconLoader.Factory private val preferredPanelRepository = kosmos.selectedComponentRepository private lateinit var fakeDialogController: FakeSystemUIDialogController @@ -130,7 +133,7 @@ class ControlsUiControllerImplTest : SysuiTestCase() { Context.LAYOUT_INFLATER_SERVICE, mContext.baseContext .getSystemService(LayoutInflater::class.java)!! - .cloneInContext(mContext) + .cloneInContext(mContext), ) parent = FrameLayout(mContext) @@ -154,6 +157,7 @@ class ControlsUiControllerImplTest : SysuiTestCase() { authorizedPanelsRepository, preferredPanelRepository, featureFlags, + safeIconLoaderFactory, ControlsDialogsFactory(systemUIDialogFactory), dumpManager, ) @@ -303,7 +307,7 @@ class ControlsUiControllerImplTest : SysuiTestCase() { assertThat( intent.getBooleanExtra( ControlsProviderService.EXTRA_LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS, - false + false, ) ) .isTrue() @@ -341,7 +345,7 @@ class ControlsUiControllerImplTest : SysuiTestCase() { assertThat( intent.getBooleanExtra( ControlsProviderService.EXTRA_LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS, - false + false, ) ) .isTrue() @@ -374,7 +378,7 @@ class ControlsUiControllerImplTest : SysuiTestCase() { assertThat( pendingIntent.intent.getBooleanExtra( ControlsProviderService.EXTRA_LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS, - false + false, ) ) .isTrue() @@ -393,7 +397,7 @@ class ControlsUiControllerImplTest : SysuiTestCase() { assertThat( newPendingIntent.intent.getBooleanExtra( ControlsProviderService.EXTRA_LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS, - false + false, ) ) .isFalse() @@ -416,9 +420,9 @@ class ControlsUiControllerImplTest : SysuiTestCase() { StructureInfo( checkNotNull(ComponentName.unflattenFromString("pkg/.cls1")), "a", - ArrayList() + ArrayList(), ) - ), + ) ) preferredPanelRepository.setSelectedComponent( SelectedComponentRepository.SelectedComponent(selectedItems[0]) @@ -598,7 +602,7 @@ class ControlsUiControllerImplTest : SysuiTestCase() { private fun ControlsServiceInfo( componentName: ComponentName, label: CharSequence, - panelComponentName: ComponentName? = null + panelComponentName: ComponentName? = null, ): ControlsServiceInfo { val serviceInfo = ServiceInfo().apply { @@ -621,7 +625,7 @@ class ControlsUiControllerImplTest : SysuiTestCase() { view: View?, name: String, context: Context, - attrs: AttributeSet + attrs: AttributeSet, ): View? { return onCreateView(name, context, attrs) } @@ -629,7 +633,7 @@ class ControlsUiControllerImplTest : SysuiTestCase() { override fun onCreateView( name: String, context: Context, - attrs: AttributeSet + attrs: AttributeSet, ): View? { if (FrameLayout::class.java.simpleName.equals(name)) { val mock: FrameLayout = mock { diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt index 21519b0cb38a..ab691c630f97 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt @@ -304,9 +304,7 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() { testScope = TestScope(testDispatcher) underTest = KeyguardQuickAffordanceInteractor( - keyguardInteractor = - KeyguardInteractorFactory.create(featureFlags = featureFlags) - .keyguardInteractor, + keyguardInteractor = kosmos.keyguardInteractor, shadeInteractor = kosmos.shadeInteractor, lockPatternUtils = lockPatternUtils, keyguardStateController = keyguardStateController, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt index b5a227104900..051aba3d593f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt @@ -44,9 +44,10 @@ import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanc import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository +import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor -import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor +import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState.AOD import com.android.systemui.keyguard.shared.model.KeyguardState.GONE @@ -191,9 +192,8 @@ class KeyguardQuickAffordancesCombinedViewModelTest : SysuiTestCase() { dockManager = DockManagerFake() biometricSettingsRepository = FakeBiometricSettingsRepository() - val withDeps = KeyguardInteractorFactory.create() - keyguardInteractor = withDeps.keyguardInteractor - repository = withDeps.repository + keyguardInteractor = kosmos.keyguardInteractor + repository = kosmos.fakeKeyguardRepository whenever(userTracker.userHandle).thenReturn(mock()) whenever(lockPatternUtils.getStrongAuthForUser(ArgumentMatchers.anyInt())) diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt index 909680866d20..4e1ccfb07220 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt @@ -21,14 +21,11 @@ import android.content.Context import android.content.pm.PackageManager import android.content.pm.ResolveInfo import android.os.PowerManager -import android.os.Process -import android.os.UserHandle import android.os.UserManager import android.testing.TestableContext import android.testing.TestableLooper import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.dx.mockito.inline.extended.ExtendedMockito import com.android.internal.app.AssistUtils import com.android.internal.logging.UiEventLogger import com.android.systemui.SysuiTestCase @@ -42,6 +39,7 @@ import com.android.systemui.model.SysUiState import com.android.systemui.model.sceneContainerPlugin import com.android.systemui.navigationbar.NavigationBarController import com.android.systemui.navigationbar.NavigationModeController +import com.android.systemui.process.ProcessWrapper import com.android.systemui.recents.OverviewProxyService.ACTION_QUICKSTEP import com.android.systemui.settings.FakeDisplayTracker import com.android.systemui.settings.UserTracker @@ -92,6 +90,7 @@ class OverviewProxyServiceTest : SysuiTestCase() { private val kosmos = testKosmos() private lateinit var subject: OverviewProxyService @Mock private val dumpManager = DumpManager() + @Mock private val processWrapper = ProcessWrapper() private val displayTracker = FakeDisplayTracker(mContext) private val fakeSystemClock = FakeSystemClock() private val sysUiState = SysUiState(displayTracker, kosmos.sceneContainerPlugin) @@ -146,6 +145,8 @@ class OverviewProxyServiceTest : SysuiTestCase() { mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR) + // return isSystemUser as true by default. + `when`(processWrapper.isSystemUser).thenReturn(true) subject = createOverviewProxyService(context) } @@ -202,68 +203,44 @@ class OverviewProxyServiceTest : SysuiTestCase() { @Test fun connectToOverviewService_primaryUserNoVisibleBgUsersSupported_expectBindService() { - val mockitoSession = - ExtendedMockito.mockitoSession().spyStatic(Process::class.java).startMocking() - try { - `when`(Process.myUserHandle()).thenReturn(UserHandle.SYSTEM) - `when`(userManager.isVisibleBackgroundUsersSupported()).thenReturn(false) - val spyContext = spy(context) - val ops = createOverviewProxyService(spyContext) - ops.startConnectionToCurrentUser() - verify(spyContext, atLeast(1)).bindServiceAsUser(any(), any(), anyInt(), any()) - } finally { - mockitoSession.finishMocking() - } + `when`(processWrapper.isSystemUser).thenReturn(true) + `when`(userManager.isVisibleBackgroundUsersSupported()).thenReturn(false) + val spyContext = spy(context) + val ops = createOverviewProxyService(spyContext) + ops.startConnectionToCurrentUser() + verify(spyContext, atLeast(1)).bindServiceAsUser(any(), any(), anyInt(), any()) } @Test fun connectToOverviewService_nonPrimaryUserNoVisibleBgUsersSupported_expectNoBindService() { - val mockitoSession = - ExtendedMockito.mockitoSession().spyStatic(Process::class.java).startMocking() - try { - `when`(Process.myUserHandle()).thenReturn(UserHandle.of(12345)) - `when`(userManager.isVisibleBackgroundUsersSupported()).thenReturn(false) - val spyContext = spy(context) - val ops = createOverviewProxyService(spyContext) - ops.startConnectionToCurrentUser() - verify(spyContext, times(0)).bindServiceAsUser(any(), any(), anyInt(), any()) - } finally { - mockitoSession.finishMocking() - } + `when`(processWrapper.isSystemUser).thenReturn(false) + `when`(userManager.isVisibleBackgroundUsersSupported()).thenReturn(false) + val spyContext = spy(context) + val ops = createOverviewProxyService(spyContext) + ops.startConnectionToCurrentUser() + verify(spyContext, times(0)).bindServiceAsUser(any(), any(), anyInt(), any()) } @Test fun connectToOverviewService_nonPrimaryBgUserVisibleBgUsersSupported_expectBindService() { - val mockitoSession = - ExtendedMockito.mockitoSession().spyStatic(Process::class.java).startMocking() - try { - `when`(Process.myUserHandle()).thenReturn(UserHandle.of(12345)) - `when`(userManager.isVisibleBackgroundUsersSupported()).thenReturn(true) - `when`(userManager.isUserForeground()).thenReturn(false) - val spyContext = spy(context) - val ops = createOverviewProxyService(spyContext) - ops.startConnectionToCurrentUser() - verify(spyContext, atLeast(1)).bindServiceAsUser(any(), any(), anyInt(), any()) - } finally { - mockitoSession.finishMocking() - } + `when`(processWrapper.isSystemUser).thenReturn(false) + `when`(userManager.isVisibleBackgroundUsersSupported()).thenReturn(true) + `when`(userManager.isUserForeground()).thenReturn(false) + val spyContext = spy(context) + val ops = createOverviewProxyService(spyContext) + ops.startConnectionToCurrentUser() + verify(spyContext, atLeast(1)).bindServiceAsUser(any(), any(), anyInt(), any()) } @Test fun connectToOverviewService_nonPrimaryFgUserVisibleBgUsersSupported_expectNoBindService() { - val mockitoSession = - ExtendedMockito.mockitoSession().spyStatic(Process::class.java).startMocking() - try { - `when`(Process.myUserHandle()).thenReturn(UserHandle.of(12345)) - `when`(userManager.isVisibleBackgroundUsersSupported()).thenReturn(true) - `when`(userManager.isUserForeground()).thenReturn(true) - val spyContext = spy(context) - val ops = createOverviewProxyService(spyContext) - ops.startConnectionToCurrentUser() - verify(spyContext, times(0)).bindServiceAsUser(any(), any(), anyInt(), any()) - } finally { - mockitoSession.finishMocking() - } + `when`(processWrapper.isSystemUser).thenReturn(false) + `when`(userManager.isVisibleBackgroundUsersSupported()).thenReturn(true) + `when`(userManager.isUserForeground()).thenReturn(true) + val spyContext = spy(context) + val ops = createOverviewProxyService(spyContext) + ops.startConnectionToCurrentUser() + verify(spyContext, times(0)).bindServiceAsUser(any(), any(), anyInt(), any()) } private fun createOverviewProxyService(ctx: Context): OverviewProxyService { @@ -292,6 +269,7 @@ class OverviewProxyServiceTest : SysuiTestCase() { unfoldTransitionProgressForwarder, broadcastDispatcher, backAnimation, + processWrapper, ) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt index e68153ad2606..70450d29c74e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt @@ -57,7 +57,10 @@ import com.android.systemui.res.R import com.android.systemui.settings.brightness.data.repository.BrightnessMirrorShowingRepository import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirrorShowingInteractor import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler +import com.android.systemui.shade.data.repository.ShadeAnimationRepository +import com.android.systemui.shade.data.repository.ShadeRepositoryImpl import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor +import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractorLegacyImpl import com.android.systemui.statusbar.BlurUtils import com.android.systemui.statusbar.DragDownHelper import com.android.systemui.statusbar.LockscreenShadeTransitionController @@ -219,6 +222,10 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : notificationShadeDepthController, view, shadeViewController, + ShadeAnimationInteractorLegacyImpl( + ShadeAnimationRepository(), + ShadeRepositoryImpl(testScope), + ), panelExpansionInteractor, ShadeExpansionStateManager(), stackScrollLayoutController, @@ -521,6 +528,18 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : verify(view).findViewById<ViewGroup>(R.id.keyguard_message_area) } + @EnableFlags(Flags.FLAG_SHADE_LAUNCH_ACCESSIBILITY) + @Test + fun notifiesTheViewWhenLaunchAnimationIsRunning() { + testScope.runTest { + underTest.setExpandAnimationRunning(true) + verify(view).setAnimatingContentLaunch(true) + + underTest.setExpandAnimationRunning(false) + verify(view).setAnimatingContentLaunch(false) + } + } + @Test @DisableSceneContainer fun setsUpCommunalHubLayout_whenFlagEnabled() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt index eae828562223..a5cd81ff3116 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt @@ -53,6 +53,7 @@ import com.android.systemui.shade.ShadeHeaderController.Companion.QQS_HEADER_CON import com.android.systemui.shade.ShadeHeaderController.Companion.QS_HEADER_CONSTRAINT import com.android.systemui.shade.carrier.ShadeCarrierGroup import com.android.systemui.shade.carrier.ShadeCarrierGroupController +import com.android.systemui.shade.data.repository.shadeDisplaysRepository import com.android.systemui.statusbar.data.repository.fakeStatusBarContentInsetsProviderStore import com.android.systemui.statusbar.phone.StatusIconContainer import com.android.systemui.statusbar.phone.StatusOverlayHoverListenerFactory @@ -96,7 +97,7 @@ class ShadeHeaderControllerTest : SysuiTestCase() { private val kosmos = testKosmos() private val insetsProviderStore = kosmos.fakeStatusBarContentInsetsProviderStore - private val insetsProvider = insetsProviderStore.defaultDisplay + private val insetsProvider = insetsProviderStore.forDisplay(context.displayId) @Mock(answer = Answers.RETURNS_MOCKS) private lateinit var view: MotionLayout @Mock private lateinit var statusIcons: StatusIconContainer @@ -196,6 +197,7 @@ class ShadeHeaderControllerTest : SysuiTestCase() { privacyIconsController, insetsProviderStore, configurationController, + kosmos.shadeDisplaysRepository, variableDateViewControllerFactory, batteryMeterViewController, dumpManager, @@ -809,6 +811,43 @@ class ShadeHeaderControllerTest : SysuiTestCase() { } @Test + fun sameInsetsTwice_listenerCallsOnApplyWindowInsetsOnlyOnce() { + val windowInsets = createWindowInsets() + + val captor = ArgumentCaptor.forClass(View.OnApplyWindowInsetsListener::class.java) + verify(view).setOnApplyWindowInsetsListener(capture(captor)) + + val listener = captor.value + + listener.onApplyWindowInsets(view, windowInsets) + + verify(view, times(1)).onApplyWindowInsets(any()) + + listener.onApplyWindowInsets(view, windowInsets) + + verify(view, times(1)).onApplyWindowInsets(any()) + } + + @Test + fun twoDifferentInsets_listenerCallsOnApplyWindowInsetsTwice() { + val windowInsets1 = WindowInsets(Rect(1, 2, 3, 4)) + val windowInsets2 = WindowInsets(Rect(5, 6, 7, 8)) + + val captor = ArgumentCaptor.forClass(View.OnApplyWindowInsetsListener::class.java) + verify(view).setOnApplyWindowInsetsListener(capture(captor)) + + val listener = captor.value + + listener.onApplyWindowInsets(view, windowInsets1) + + verify(view, times(1)).onApplyWindowInsets(any()) + + listener.onApplyWindowInsets(view, windowInsets2) + + verify(view, times(2)).onApplyWindowInsets(any()) + } + + @Test fun alarmIconNotIgnored() { verify(statusIcons, Mockito.never()) .addIgnoredSlot(context.getString(com.android.internal.R.string.status_bar_alarm_clock)) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java index 5aee92939ed5..493468e8f675 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java @@ -69,6 +69,7 @@ import com.android.systemui.statusbar.notification.FeedbackIcon; import com.android.systemui.statusbar.notification.SourceType; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.headsup.PinnedStatus; +import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiForceExpanded; import com.android.systemui.statusbar.notification.row.ExpandableView.OnHeightChangedListener; import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper; import com.android.systemui.statusbar.notification.shared.NotificationContentAlphaOptimization; @@ -878,6 +879,85 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { } @Test + @EnableFlags(PromotedNotificationUiForceExpanded.FLAG_NAME) + public void isExpanded_sensitivePromotedNotification_notExpanded() throws Exception { + // GIVEN + final ExpandableNotificationRow row = mNotificationTestHelper.createRow(); + row.setPromotedOngoing(true); + row.setSensitive(/* sensitive= */true, /* hideSensitive= */false); + row.setHideSensitiveForIntrinsicHeight(/* hideSensitive= */true); + + // THEN + assertThat(row.isExpanded()).isFalse(); + } + + @Test + @EnableFlags(PromotedNotificationUiForceExpanded.FLAG_NAME) + public void isExpanded_promotedNotificationNotOnKeyguard_expanded() throws Exception { + // GIVEN + final ExpandableNotificationRow row = mNotificationTestHelper.createRow(); + row.setPromotedOngoing(true); + row.setOnKeyguard(false); + + // THEN + assertThat(row.isExpanded()).isTrue(); + } + + @Test + @EnableFlags(PromotedNotificationUiForceExpanded.FLAG_NAME) + public void isExpanded_promotedNotificationAllowOnKeyguard_expanded() throws Exception { + // GIVEN + final ExpandableNotificationRow row = mNotificationTestHelper.createRow(); + row.setPromotedOngoing(true); + row.setOnKeyguard(true); + + // THEN + assertThat(row.isExpanded(/* allowOnKeyguard = */ true)).isTrue(); + } + + @Test + @EnableFlags(PromotedNotificationUiForceExpanded.FLAG_NAME) + public void isExpanded_promotedNotificationIgnoreLockscreenConstraints_expanded() + throws Exception { + // GIVEN + final ExpandableNotificationRow row = mNotificationTestHelper.createRow(); + row.setPromotedOngoing(true); + row.setOnKeyguard(true); + row.setIgnoreLockscreenConstraints(true); + + // THEN + assertThat(row.isExpanded()).isTrue(); + } + + @Test + @EnableFlags(PromotedNotificationUiForceExpanded.FLAG_NAME) + public void isExpanded_promotedNotificationSaveSpaceOnLockScreen_notExpanded() + throws Exception { + // GIVEN + final ExpandableNotificationRow row = mNotificationTestHelper.createRow(); + row.setPromotedOngoing(true); + row.setOnKeyguard(true); + row.setSaveSpaceOnLockscreen(true); + + // THEN + assertThat(row.isExpanded()).isFalse(); + } + + @Test + @EnableFlags(PromotedNotificationUiForceExpanded.FLAG_NAME) + public void isExpanded_promotedNotificationNotSaveSpaceOnLockScreen_expanded() + throws Exception { + // GIVEN + final ExpandableNotificationRow row = mNotificationTestHelper.createRow(); + row.setPromotedOngoing(true); + row.setOnKeyguard(true); + row.setSaveSpaceOnLockscreen(false); + + // THEN + assertThat(row.isExpanded()).isTrue(); + } + + @Test public void onDisappearAnimationFinished_shouldSetFalse_headsUpAnimatingAway() throws Exception { final ExpandableNotificationRow row = mNotificationTestHelper.createRow(); @@ -941,6 +1021,57 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { assertThat(row.getImageResolver().getContext()).isSameInstanceAs(userContext); } + @Test + @EnableFlags(com.android.systemui.Flags.FLAG_NOTIFICATIONS_PINNED_HUN_IN_SHADE) + public void mustStayOnScreen_false() throws Exception { + final ExpandableNotificationRow row = mNotificationTestHelper.createRow(); + assertThat(row.mustStayOnScreen()).isFalse(); + } + + @Test + @EnableFlags(com.android.systemui.Flags.FLAG_NOTIFICATIONS_PINNED_HUN_IN_SHADE) + public void mustStayOnScreen_isHeadsUp_markedAsSeen() throws Exception { + final ExpandableNotificationRow row = mNotificationTestHelper.createRow(); + // When the row is a HUN + row.setHeadsUp(true); + //Then it must stay on screen + assertThat(row.mustStayOnScreen()).isTrue(); + // And when the user has seen it + row.markHeadsUpSeen(); + // Then it should NOT stay on screen anymore + assertThat(row.mustStayOnScreen()).isFalse(); + } + + @Test + @EnableFlags(com.android.systemui.Flags.FLAG_NOTIFICATIONS_PINNED_HUN_IN_SHADE) + public void mustStayOnScreen_isPinned_markedAsSeen() throws Exception { + final ExpandableNotificationRow row = mNotificationTestHelper.createRow(); + // When a HUN is pinned + row.setHeadsUp(true); + row.setPinnedStatus(PinnedStatus.PinnedBySystem); + //Then it must stay on screen + assertThat(row.mustStayOnScreen()).isTrue(); + // And when the user has seen it + row.markHeadsUpSeen(); + // Then it should still stay on screen + assertThat(row.mustStayOnScreen()).isTrue(); + } + + @Test + @DisableFlags(com.android.systemui.Flags.FLAG_NOTIFICATIONS_PINNED_HUN_IN_SHADE) + public void mustStayOnScreen_isPinned_markedAsSeen_false() throws Exception { + final ExpandableNotificationRow row = mNotificationTestHelper.createRow(); + // When a HUN is pinned + row.setHeadsUp(true); + row.setPinnedStatus(PinnedStatus.PinnedBySystem); + //Then it must stay on screen + assertThat(row.mustStayOnScreen()).isTrue(); + // And when the user has seen it + row.markHeadsUpSeen(); + // Then it should NOT stay on screen anymore + assertThat(row.mustStayOnScreen()).isFalse(); + } + private void setDrawableIconsInImageView(CachingIconView icon, Drawable iconDrawable, Drawable rightIconDrawable) { ImageView iconView = mock(ImageView.class); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt index 61943f2283e0..8645a40319f4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt @@ -444,6 +444,7 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() { eq(true), /* wasShownHighPriority */ eq(assistantFeedbackController), any<MetricsLogger>(), + any<View.OnClickListener>(), ) } @@ -476,6 +477,7 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() { eq(false), /* wasShownHighPriority */ eq(assistantFeedbackController), any<MetricsLogger>(), + any<View.OnClickListener>(), ) } @@ -508,6 +510,7 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() { eq(false), /* wasShownHighPriority */ eq(assistantFeedbackController), any<MetricsLogger>(), + any<View.OnClickListener>(), ) } 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 437ccb6a9821..68f66611c981 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 @@ -90,6 +90,8 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { private val kosmos = testKosmos() private val statusBarContentInsetsProviderStore = kosmos.fakeStatusBarContentInsetsProviderStore private val statusBarContentInsetsProvider = statusBarContentInsetsProviderStore.defaultDisplay + private val statusBarContentInsetsProviderForSecondaryDisplay = + statusBarContentInsetsProviderStore.forDisplay(SECONDARY_DISPLAY_ID) private val fakeDarkIconDispatcher = kosmos.fakeDarkIconDispatcher @Mock private lateinit var shadeViewController: ShadeViewController @@ -144,6 +146,12 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { controller = createAndInitController(view) } + `when`( + statusBarContentInsetsProviderForSecondaryDisplay + .getStatusBarContentInsetsForCurrentRotation() + ) + .thenReturn(Insets.NONE) + val contextForSecondaryDisplay = SysuiTestableContext( mContext.createDisplayContext( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java index a02d333d1507..a7fe1ba76590 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java @@ -59,6 +59,7 @@ import com.android.internal.colorextraction.ColorExtractor.GradientColors; import com.android.keyguard.BouncerPanelExpansionCalculator; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.DejankUtils; +import com.android.systemui.Flags; import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.ShadeInterpolation; import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants; @@ -71,6 +72,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInterac import com.android.systemui.keyguard.shared.model.KeyguardState; import com.android.systemui.keyguard.shared.model.TransitionState; import com.android.systemui.keyguard.shared.model.TransitionStep; +import com.android.systemui.keyguard.ui.transitions.BlurConfig; import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToGoneTransitionViewModel; import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel; import com.android.systemui.kosmos.KosmosJavaAdapter; @@ -110,6 +112,9 @@ import java.util.Map; @RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest +// TODO(b/381263619) there are more changes and tweaks required to match the new bouncer/shade specs +// Disabling for now but it will be fixed before the flag is fully ramped up. +@DisableFlags(Flags.FLAG_BOUNCER_UI_REVAMP) public class ScrimControllerTest extends SysuiTestCase { @Rule public Expect mExpect = Expect.create(); @@ -286,7 +291,8 @@ public class ScrimControllerTest extends SysuiTestCase { mKeyguardTransitionInteractor, mKeyguardInteractor, mKosmos.getTestDispatcher(), - mLinearLargeScreenShadeInterpolator); + mLinearLargeScreenShadeInterpolator, + new BlurConfig(0.0f, 0.0f)); mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible); mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront); mScrimController.setAnimatorListener(mAnimatorListener); @@ -1251,7 +1257,8 @@ public class ScrimControllerTest extends SysuiTestCase { mKeyguardTransitionInteractor, mKeyguardInteractor, mKosmos.getTestDispatcher(), - mLinearLargeScreenShadeInterpolator); + mLinearLargeScreenShadeInterpolator, + new BlurConfig(0.0f, 0.0f)); mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible); mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront); mScrimController.setAnimatorListener(mAnimatorListener); 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 fab7922f58e7..5d88f72b805b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -98,6 +98,7 @@ import android.view.View; import android.view.ViewTreeObserver; import android.view.WindowManager; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.test.filters.SmallTest; @@ -2894,6 +2895,12 @@ public class BubblesTest extends SysuiTestCase { @Override public void animateBubbleBarLocation(BubbleBarLocation location) { } + + @Override + public void onDragItemOverBubbleBarDragZone(@NonNull BubbleBarLocation location) {} + + @Override + public void onItemDraggedOutsideBubbleBarDropZone() {} } private static class FakeBubbleProperties implements BubbleProperties { diff --git a/packages/SystemUI/tests/utils/src/com/android/app/WallpaperManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/app/WallpaperManagerKosmos.kt index 2850ab7b1e41..f893aba240fc 100644 --- a/packages/SystemUI/tests/utils/src/com/android/app/WallpaperManagerKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/app/WallpaperManagerKosmos.kt @@ -19,9 +19,7 @@ import android.app.WallpaperManager import android.content.applicationContext import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture -import kotlinx.coroutines.ExperimentalCoroutinesApi -@OptIn(ExperimentalCoroutinesApi::class) val Kosmos.wallpaperManager: WallpaperManager by Fixture { WallpaperManager.getInstance(applicationContext) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt index c9458125e762..dfcda222e54f 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt @@ -51,7 +51,6 @@ import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineStart -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent @@ -148,7 +147,6 @@ interface SysUITestComponent<out T> { val underTest: T } -@OptIn(ExperimentalCoroutinesApi::class) fun <T : SysUITestComponent<*>> T.runTest(block: suspend T.() -> Unit): Unit = testScope.runTest { // Access underTest immediately to force Dagger to instantiate it prior to the test running @@ -157,7 +155,6 @@ fun <T : SysUITestComponent<*>> T.runTest(block: suspend T.() -> Unit): Unit = block() } -@OptIn(ExperimentalCoroutinesApi::class) fun SysUITestComponent<*>.runCurrent() = testScope.runCurrent() fun <T> SysUITestComponent<*>.collectLastValue( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt index a7917a0866bb..3c264b9d6a81 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt @@ -28,7 +28,6 @@ import com.android.systemui.dagger.SysUISingleton import dagger.Binds import dagger.Module import dagger.Provides -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow @@ -260,7 +259,6 @@ class FakeAuthenticationRepository(private val currentTime: () -> Long) : Authen } } -@OptIn(ExperimentalCoroutinesApi::class) @Module(includes = [FakeAuthenticationRepositoryModule.Bindings::class]) object FakeAuthenticationRepositoryModule { @Provides diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderKosmos.kt index 79d58a1d4e40..e3bd6fa27d4c 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderKosmos.kt @@ -27,9 +27,7 @@ import com.android.systemui.keyguard.ui.viewmodel.sideFpsProgressBarViewModel import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.kosmos.applicationCoroutineScope -import kotlinx.coroutines.ExperimentalCoroutinesApi -@OptIn(ExperimentalCoroutinesApi::class) val Kosmos.sideFpsOverlayViewBinder by Fixture { SideFpsOverlayViewBinder( applicationScope = applicationCoroutineScope, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelKosmos.kt index e2386a6a42b4..220bb90303b8 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelKosmos.kt @@ -23,9 +23,7 @@ import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.statusbar.phone.systemUIDialogManager import com.android.systemui.util.mockito.mock -import kotlinx.coroutines.ExperimentalCoroutinesApi -@ExperimentalCoroutinesApi val Kosmos.deviceEntryUdfpsTouchOverlayViewModel by Fixture { DeviceEntryUdfpsTouchOverlayViewModel( deviceEntryIconViewModel = deviceEntryIconViewModel, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelKosmos.kt index de038559fc38..e79c089361af 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelKosmos.kt @@ -22,9 +22,7 @@ import com.android.systemui.biometrics.domain.interactor.sideFpsSensorInteractor import com.android.systemui.keyguard.domain.interactor.deviceEntrySideFpsOverlayInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture -import kotlinx.coroutines.ExperimentalCoroutinesApi -@OptIn(ExperimentalCoroutinesApi::class) val Kosmos.sideFpsOverlayViewModel by Fixture { SideFpsOverlayViewModel( applicationContext = applicationContext, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelKosmos.kt index 5c5969d359c3..7de71ff44bb1 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelKosmos.kt @@ -14,8 +14,6 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package com.android.systemui.bouncer.ui.viewmodel import android.content.applicationContext @@ -30,7 +28,6 @@ import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.user.ui.viewmodel.userSwitcherViewModel import com.android.systemui.util.time.systemClock -import kotlinx.coroutines.ExperimentalCoroutinesApi val Kosmos.bouncerMessageViewModel by Fixture { BouncerMessageViewModel( 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 72541540226c..3bfd95816cf0 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 @@ -14,8 +14,6 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package com.android.systemui.bouncer.ui.viewmodel import android.app.admin.devicePolicyManager @@ -34,7 +32,6 @@ import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.user.domain.interactor.selectedUserInteractor import com.android.systemui.user.ui.viewmodel.userSwitcherViewModel -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.StateFlow val Kosmos.bouncerUserActionsViewModel by Fixture { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalPrefsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalPrefsRepository.kt index 5e870b19681b..163625747d85 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalPrefsRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalPrefsRepository.kt @@ -25,6 +25,7 @@ import kotlinx.coroutines.flow.map /** Fake implementation of [CommunalPrefsRepository] */ class FakeCommunalPrefsRepository : CommunalPrefsRepository { private val _isCtaDismissed = MutableStateFlow<Set<UserInfo>>(emptySet()) + private val _isHubOnboardingismissed = MutableStateFlow<Set<UserInfo>>(emptySet()) override fun isCtaDismissed(user: UserInfo): Flow<Boolean> = _isCtaDismissed.map { it.contains(user) } @@ -32,4 +33,12 @@ class FakeCommunalPrefsRepository : CommunalPrefsRepository { override suspend fun setCtaDismissed(user: UserInfo) { _isCtaDismissed.value = _isCtaDismissed.value.toMutableSet().apply { add(user) } } + + override fun isHubOnboardingDismissed(user: UserInfo): Flow<Boolean> = + _isHubOnboardingismissed.map { it.contains(user) } + + override suspend fun setHubOnboardingDismissed(user: UserInfo) { + _isHubOnboardingismissed.value = + _isHubOnboardingismissed.value.toMutableSet().apply { add(user) } + } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalSceneRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalSceneRepository.kt index 82454817ecbb..b3c1411243c1 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalSceneRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalSceneRepository.kt @@ -5,7 +5,6 @@ import com.android.compose.animation.scene.SceneKey import com.android.compose.animation.scene.TransitionKey import com.android.systemui.communal.shared.model.CommunalScenes import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted @@ -16,7 +15,6 @@ import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch /** Fake implementation of [CommunalSceneRepository]. */ -@OptIn(ExperimentalCoroutinesApi::class) class FakeCommunalSceneRepository( private val applicationScope: CoroutineScope, override val currentScene: MutableStateFlow<SceneKey> = diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderInputEventsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/HubOnboardingInteractorKosmos.kt index 2de0e8f76a4b..9db4e4f4b8f2 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderInputEventsViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/HubOnboardingInteractorKosmos.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 The Android Open Source Project + * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,16 +14,16 @@ * limitations under the License. */ -package com.android.systemui.volume.dialog.sliders.ui.viewmodel +package com.android.systemui.communal.domain.interactor +import com.android.systemui.communal.data.repository.communalSettingsRepository import com.android.systemui.kosmos.Kosmos -import com.android.systemui.kosmos.applicationCoroutineScope -import com.android.systemui.volume.dialog.sliders.domain.interactor.volumeDialogSliderInputEventsInteractor -val Kosmos.volumeDialogSliderInputEventsViewModel by +val Kosmos.hubOnboardingInteractor: HubOnboardingInteractor by Kosmos.Fixture { - VolumeDialogSliderInputEventsViewModel( - applicationCoroutineScope, - volumeDialogSliderInputEventsInteractor, + HubOnboardingInteractor( + communalSceneInteractor = communalSceneInteractor, + communalSettingsRepository = communalSettingsRepository, + communalPrefsInteractor = communalPrefsInteractor, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/posturing/data/repository/FakePosturingRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/posturing/data/repository/FakePosturingRepository.kt new file mode 100644 index 000000000000..8a597a61ee78 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/posturing/data/repository/FakePosturingRepository.kt @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.communal.posturing.data.repository + +import com.android.systemui.communal.posturing.shared.model.PosturedState +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow + +class FakePosturingRepository : PosturingRepository { + private val _postured = MutableStateFlow<PosturedState>(PosturedState.Unknown) + + override val posturedState: StateFlow<PosturedState> = _postured.asStateFlow() + + fun setPosturedState(state: PosturedState) { + _postured.value = state + } +} + +val PosturingRepository.fake + get() = this as FakePosturingRepository diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeUserActionsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/posturing/data/repository/PosturingRepositoryKosmos.kt index 6345c4076412..105a3581b787 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeUserActionsViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/posturing/data/repository/PosturingRepositoryKosmos.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 The Android Open Source Project + * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,11 +14,8 @@ * limitations under the License. */ -package com.android.systemui.shade.ui.viewmodel +package com.android.systemui.communal.posturing.data.repository import com.android.systemui.kosmos.Kosmos -import com.android.systemui.kosmos.Kosmos.Fixture -import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeUserActionsViewModel -val Kosmos.notificationsShadeUserActionsViewModel: - NotificationsShadeUserActionsViewModel by Fixture { NotificationsShadeUserActionsViewModel() } +val Kosmos.posturingRepository by Kosmos.Fixture<PosturingRepository> { FakePosturingRepository() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/posturing/domain/interactor/PosturingInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/posturing/domain/interactor/PosturingInteractorKosmos.kt new file mode 100644 index 000000000000..53c9c6440c69 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/posturing/domain/interactor/PosturingInteractorKosmos.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.communal.posturing.domain.interactor + +import com.android.systemui.communal.posturing.data.repository.posturingRepository +import com.android.systemui.kosmos.Kosmos + +val Kosmos.posturingInteractor by + Kosmos.Fixture<PosturingInteractor> { PosturingInteractor(repository = posturingRepository) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelKosmos.kt index 1ae8449d8b4d..7b0c09cd80a6 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelKosmos.kt @@ -26,9 +26,7 @@ import com.android.systemui.keyguard.ui.viewmodel.glanceableHubToLockscreenTrans import com.android.systemui.keyguard.ui.viewmodel.lockscreenToGlanceableHubTransitionViewModel import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope -import kotlinx.coroutines.ExperimentalCoroutinesApi -@OptIn(ExperimentalCoroutinesApi::class) val Kosmos.communalTransitionViewModel by Kosmos.Fixture { CommunalTransitionViewModel( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/HubOnboardingViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/HubOnboardingViewModelKosmos.kt new file mode 100644 index 000000000000..9cdaaf4b88f8 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/HubOnboardingViewModelKosmos.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.communal.ui.viewmodel + +import com.android.systemui.communal.domain.interactor.hubOnboardingInteractor +import com.android.systemui.kosmos.Kosmos + +val Kosmos.hubOnboardingViewModel by + Kosmos.Fixture { HubOnboardingViewModel(hubOnboardingInteractor = hubOnboardingInteractor) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/coroutines/TestCoroutineSchedulerUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/coroutines/TestCoroutineSchedulerUtils.kt index 84e2a5c7d4c2..2fb73264b3d4 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/coroutines/TestCoroutineSchedulerUtils.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/coroutines/TestCoroutineSchedulerUtils.kt @@ -13,12 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package com.android.systemui.coroutines import kotlin.time.Duration -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestCoroutineScheduler /** diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/ui/viewmodel/UdfpsAccessibilityOverlayViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/ui/viewmodel/UdfpsAccessibilityOverlayViewModelKosmos.kt index cdeade1876a7..2a46437ed33e 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/ui/viewmodel/UdfpsAccessibilityOverlayViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/ui/viewmodel/UdfpsAccessibilityOverlayViewModelKosmos.kt @@ -22,9 +22,7 @@ import com.android.systemui.deviceentry.ui.viewmodel.DeviceEntryUdfpsAccessibili import com.android.systemui.keyguard.ui.viewmodel.deviceEntryForegroundIconViewModel import com.android.systemui.keyguard.ui.viewmodel.deviceEntryIconViewModel import com.android.systemui.kosmos.Kosmos -import kotlinx.coroutines.ExperimentalCoroutinesApi -@ExperimentalCoroutinesApi val Kosmos.deviceEntryUdfpsAccessibilityOverlayViewModel by Kosmos.Fixture { DeviceEntryUdfpsAccessibilityOverlayViewModel( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractorKosmos.kt index 3070cf4c06ad..015d4ddcd54e 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractorKosmos.kt @@ -17,9 +17,7 @@ package com.android.systemui.deviceentry.domain.interactor import com.android.systemui.kosmos.Kosmos -import kotlinx.coroutines.ExperimentalCoroutinesApi -@OptIn(ExperimentalCoroutinesApi::class) val Kosmos.authRippleInteractor by Kosmos.Fixture { AuthRippleInteractor( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/BiometricMessageInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/BiometricMessageInteractorKosmos.kt index 77d39f066e08..6b6488122b68 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/BiometricMessageInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/BiometricMessageInteractorKosmos.kt @@ -20,9 +20,7 @@ import android.content.res.mainResources import com.android.systemui.biometrics.domain.interactor.fingerprintPropertyInteractor import com.android.systemui.keyguard.domain.interactor.devicePostureInteractor import com.android.systemui.kosmos.Kosmos -import kotlinx.coroutines.ExperimentalCoroutinesApi -@ExperimentalCoroutinesApi val Kosmos.biometricMessageInteractor by Kosmos.Fixture { BiometricMessageInteractor( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricAuthInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricAuthInteractorKosmos.kt index 1bd105620813..281782ad726a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricAuthInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricAuthInteractorKosmos.kt @@ -14,13 +14,10 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package com.android.systemui.deviceentry.domain.interactor import com.android.systemui.keyguard.data.repository.biometricSettingsRepository import com.android.systemui.kosmos.Kosmos -import kotlinx.coroutines.ExperimentalCoroutinesApi val Kosmos.deviceEntryBiometricAuthInteractor by Kosmos.Fixture { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractorKosmos.kt index 4fcf43a2a055..44755897f88e 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractorKosmos.kt @@ -18,9 +18,7 @@ package com.android.systemui.deviceentry.domain.interactor import com.android.systemui.keyguard.data.repository.biometricSettingsRepository import com.android.systemui.kosmos.Kosmos -import kotlinx.coroutines.ExperimentalCoroutinesApi -@ExperimentalCoroutinesApi val Kosmos.deviceEntryBiometricSettingsInteractor by Kosmos.Fixture { DeviceEntryBiometricSettingsInteractor( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricsAllowedInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricsAllowedInteractorKosmos.kt index 4357289b227e..3c08e5c55349 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricsAllowedInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricsAllowedInteractorKosmos.kt @@ -14,13 +14,10 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package com.android.systemui.deviceentry.domain.interactor import com.android.systemui.biometrics.data.repository.facePropertyRepository import com.android.systemui.kosmos.Kosmos -import kotlinx.coroutines.ExperimentalCoroutinesApi val Kosmos.deviceEntryBiometricsAllowedInteractor by Kosmos.Fixture { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt index 3dfe0eea500f..33f8f40677af 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt @@ -14,8 +14,6 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package com.android.systemui.deviceentry.domain.interactor import android.content.applicationContext @@ -36,7 +34,6 @@ import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.user.data.repository.userRepository import com.android.systemui.util.mockito.mock -import kotlinx.coroutines.ExperimentalCoroutinesApi val Kosmos.faceAuthLogger by Kosmos.Fixture { mock<FaceAuthenticationLogger>() } val Kosmos.deviceEntryFaceAuthInteractor by diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthStatusInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthStatusInteractorKosmos.kt index 66d3709d14dc..4a489ab2773c 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthStatusInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthStatusInteractorKosmos.kt @@ -14,15 +14,12 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package com.android.systemui.deviceentry.domain.interactor import android.content.res.mainResources import com.android.systemui.keyguard.data.repository.deviceEntryFaceAuthRepository import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope -import kotlinx.coroutines.ExperimentalCoroutinesApi val Kosmos.deviceEntryFaceAuthStatusInteractor by Kosmos.Fixture { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractorKosmos.kt index ebed922c423e..4d767e57e631 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractorKosmos.kt @@ -14,14 +14,11 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package com.android.systemui.deviceentry.domain.interactor import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository import com.android.systemui.kosmos.Kosmos -import kotlinx.coroutines.ExperimentalCoroutinesApi val Kosmos.deviceEntryFingerprintAuthInteractor by Kosmos.Fixture { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt index 490b89bf6b13..6f570a86b19e 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt @@ -14,8 +14,6 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package com.android.systemui.deviceentry.domain.interactor import com.android.keyguard.logging.biometricUnlockLogger @@ -27,9 +25,7 @@ import com.android.systemui.keyguard.domain.interactor.keyguardBypassInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.util.time.systemClock -import kotlinx.coroutines.ExperimentalCoroutinesApi -@OptIn(ExperimentalCoroutinesApi::class) val Kosmos.deviceEntryHapticsInteractor by Kosmos.Fixture { DeviceEntryHapticsInteractor( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt index 096022ce1507..1d3fd300da06 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt @@ -24,9 +24,7 @@ import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.scene.domain.interactor.sceneBackInteractor import com.android.systemui.scene.domain.interactor.sceneInteractor -import kotlinx.coroutines.ExperimentalCoroutinesApi -@ExperimentalCoroutinesApi val Kosmos.deviceEntryInteractor by Kosmos.Fixture { DeviceEntryInteractor( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractorKosmos.kt index f91a044ad802..845d481cbbb7 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractorKosmos.kt @@ -27,9 +27,7 @@ import com.android.systemui.kosmos.Kosmos import com.android.systemui.scene.domain.interactor.sceneContainerOcclusionInteractor import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.statusbar.phone.dozeScrimController -import kotlinx.coroutines.ExperimentalCoroutinesApi -@ExperimentalCoroutinesApi val Kosmos.deviceEntrySourceInteractor by Kosmos.Fixture { DeviceEntrySourceInteractor( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractorKosmos.kt index 81123d09b43a..44d3c33c95fb 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractorKosmos.kt @@ -14,8 +14,6 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package com.android.systemui.deviceentry.domain.interactor import com.android.systemui.biometrics.domain.interactor.fingerprintPropertyInteractor @@ -23,7 +21,6 @@ import com.android.systemui.keyguard.data.repository.biometricSettingsRepository import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture -import kotlinx.coroutines.ExperimentalCoroutinesApi val Kosmos.deviceEntryUdfpsInteractor by Fixture { DeviceEntryUdfpsInteractor( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/FaceHelpMessageDeferralInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/FaceHelpMessageDeferralInteractorKosmos.kt index 724e943c9f55..79a9c57169a3 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/FaceHelpMessageDeferralInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/FaceHelpMessageDeferralInteractorKosmos.kt @@ -19,9 +19,7 @@ package com.android.systemui.deviceentry.domain.interactor import com.android.systemui.biometrics.domain.faceHelpMessageDeferralFactory import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope -import kotlinx.coroutines.ExperimentalCoroutinesApi -@ExperimentalCoroutinesApi val Kosmos.faceHelpMessageDeferralInteractor by Kosmos.Fixture { FaceHelpMessageDeferralInteractor( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorKosmos.kt index 3680e651246b..3d5c99cf180a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorKosmos.kt @@ -27,9 +27,7 @@ import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.plugins.activityStarter import com.android.systemui.power.domain.interactor.powerInteractor -import kotlinx.coroutines.ExperimentalCoroutinesApi -@ExperimentalCoroutinesApi val Kosmos.occludingAppDeviceEntryInteractor by Kosmos.Fixture { OccludingAppDeviceEntryInteractor( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/ui/binder/LiftToRunFaceAuthBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/ui/binder/LiftToRunFaceAuthBinderKosmos.kt index 2fead91b430a..199a4a4d932c 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/ui/binder/LiftToRunFaceAuthBinderKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/ui/binder/LiftToRunFaceAuthBinderKosmos.kt @@ -26,9 +26,7 @@ import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.util.sensors.asyncSensorManager -import kotlinx.coroutines.ExperimentalCoroutinesApi -@ExperimentalCoroutinesApi val Kosmos.liftToRunFaceAuthBinder by Kosmos.Fixture { LiftToRunFaceAuthBinder( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeFocusedDisplayRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeFocusedDisplayRepository.kt index 83df5d874ad6..ad9370f7ac84 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeFocusedDisplayRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeFocusedDisplayRepository.kt @@ -33,7 +33,7 @@ class FakeFocusedDisplayRepository @Inject constructor() : FocusedDisplayReposit override val focusedDisplayId: StateFlow<Int> get() = flow.asStateFlow() - suspend fun emit(focusedDisplay: Int) = flow.emit(focusedDisplay) + suspend fun setDisplayId(focusedDisplay: Int) = flow.emit(focusedDisplay) } @Module 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 3df3ee983ecf..739f6c2af2b4 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 @@ -32,6 +32,7 @@ import com.android.systemui.keyboard.shortcut.data.repository.ShortcutCategories import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperInputDeviceRepository import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperStateRepository import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperTestHelper +import com.android.systemui.keyboard.shortcut.data.source.AccessibilityShortcutsSource import com.android.systemui.keyboard.shortcut.data.source.AppCategoriesShortcutsSource import com.android.systemui.keyboard.shortcut.data.source.CurrentAppShortcutsSource import com.android.systemui.keyboard.shortcut.data.source.InputShortcutsSource @@ -78,6 +79,9 @@ var Kosmos.shortcutHelperInputShortcutsSource: KeyboardShortcutGroupsSource by var Kosmos.shortcutHelperCurrentAppShortcutsSource: KeyboardShortcutGroupsSource by Kosmos.Fixture { CurrentAppShortcutsSource(windowManager) } +var Kosmos.shortcutHelperAccessibilityShortcutsSource: KeyboardShortcutGroupsSource by + Kosmos.Fixture { AccessibilityShortcutsSource(mainResources) } + val Kosmos.shortcutHelperExclusions by Kosmos.Fixture { ShortcutHelperExclusions(applicationContext) } @@ -100,6 +104,7 @@ val Kosmos.defaultShortcutCategoriesRepository by shortcutHelperAppCategoriesShortcutsSource, shortcutHelperInputShortcutsSource, shortcutHelperCurrentAppShortcutsSource, + shortcutHelperAccessibilityShortcutsSource, shortcutHelperInputDeviceRepository, shortcutCategoriesUtils, ) @@ -154,7 +159,7 @@ val Kosmos.shortcutHelperCoreStartable by shortcutHelperStateRepository, activityStarter, testScope, - customInputGesturesRepository + customInputGesturesRepository, ) } @@ -211,6 +216,7 @@ val Kosmos.shortcutCustomizationDialogStarterFactory by return ShortcutCustomizationDialogStarter( shortcutCustomizationViewModelFactory, systemUIDialogFactory, + mainResources, ) } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfigKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfigKosmos.kt deleted file mode 100644 index 568324832b33..000000000000 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfigKosmos.kt +++ /dev/null @@ -1,35 +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.keyguard.data.quickaffordance - -import android.content.applicationContext -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.kosmos.Kosmos -import com.android.systemui.scene.domain.interactor.sceneInteractor - -val Kosmos.glanceableHubQuickAffordanceConfig by - Kosmos.Fixture { - GlanceableHubQuickAffordanceConfig( - context = applicationContext, - communalInteractor = communalInteractor, - communalSceneRepository = communalSceneRepository, - communalSettingsInteractor = communalSettingsInteractor, - sceneInteractor = sceneInteractor, - ) - } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt index 8489d8380041..8ea80081a871 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt @@ -18,6 +18,7 @@ package com.android.systemui.keyguard.data.repository import android.graphics.Point +import android.graphics.RectF import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.shared.model.BiometricUnlockMode import com.android.systemui.keyguard.shared.model.BiometricUnlockModel @@ -129,6 +130,10 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository { override val notificationStackAbsoluteBottom: StateFlow<Float> get() = _notificationStackAbsoluteBottom.asStateFlow() + private val _wallpaperFocalAreaBounds = MutableStateFlow(RectF(0f, 0f, 0f, 0f)) + override val wallpaperFocalAreaBounds: StateFlow<RectF> + get() = _wallpaperFocalAreaBounds.asStateFlow() + private val _isKeyguardEnabled = MutableStateFlow(true) override val isKeyguardEnabled: StateFlow<Boolean> = _isKeyguardEnabled.asStateFlow() @@ -287,6 +292,10 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository { _notificationStackAbsoluteBottom.value = bottom } + override fun setWallpaperFocalAreaBounds(bounds: RectF) { + _wallpaperFocalAreaBounds.value = bounds + } + override fun setCanIgnoreAuthAndReturnToGone(canWake: Boolean) { _canIgnoreAuthAndReturnToGone.value = canWake } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt index d59b5d56db3f..a91ed0f4b904 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt @@ -68,6 +68,7 @@ val Kosmos.defaultKeyguardBlueprint by keyguardSliceViewSection = mock(), udfpsAccessibilityOverlaySection = mock(), accessibilityActionsSection = mock(), + aodPromotedNotificationSection = mock(), ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorKosmos.kt index 40131c772de7..26ebe2e41a17 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorKosmos.kt @@ -14,8 +14,6 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package com.android.systemui.keyguard.domain.interactor import android.content.applicationContext @@ -24,7 +22,6 @@ import com.android.systemui.doze.util.burnInHelperWrapper import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.kosmos.applicationCoroutineScope -import kotlinx.coroutines.ExperimentalCoroutinesApi var Kosmos.burnInInteractor by Fixture { BurnInInteractor( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/DevicePostureInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/DevicePostureInteractorKosmos.kt index 75eb3c9ad7ad..b920dbf88e77 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/DevicePostureInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/DevicePostureInteractorKosmos.kt @@ -18,9 +18,7 @@ package com.android.systemui.keyguard.domain.interactor import com.android.systemui.keyguard.data.repository.devicePostureRepository import com.android.systemui.kosmos.Kosmos -import kotlinx.coroutines.ExperimentalCoroutinesApi -@ExperimentalCoroutinesApi val Kosmos.devicePostureInteractor by Kosmos.Fixture { DevicePostureInteractor( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorKosmos.kt index ce317d43e988..7b0d208298d0 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorKosmos.kt @@ -24,9 +24,7 @@ import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.testDispatcher import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor -import kotlinx.coroutines.ExperimentalCoroutinesApi -@ExperimentalCoroutinesApi val Kosmos.fromAlternateBouncerTransitionInteractor by Kosmos.Fixture { FromAlternateBouncerTransitionInteractor( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt index e6c98cd83b5e..d995b868a162 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt @@ -27,9 +27,7 @@ import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.testDispatcher import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor -import kotlinx.coroutines.ExperimentalCoroutinesApi -@OptIn(ExperimentalCoroutinesApi::class) var Kosmos.fromDreamingTransitionInteractor by Kosmos.Fixture { FromDreamingTransitionInteractor( 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 09f5fd79eeca..1d7671170d5b 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 @@ -25,9 +25,7 @@ import com.android.systemui.kosmos.testScope import com.android.systemui.log.logcatLogBuffer import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.shade.domain.interactor.shadeInteractor -import kotlinx.coroutines.ExperimentalCoroutinesApi -@ExperimentalCoroutinesApi val Kosmos.keyguardDismissActionInteractor by Kosmos.Fixture { KeyguardDismissActionInteractor( 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 339210c07437..277c2ffa6e9a 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 @@ -26,9 +26,7 @@ import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.testDispatcher import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.user.domain.interactor.selectedUserInteractor -import kotlinx.coroutines.ExperimentalCoroutinesApi -@ExperimentalCoroutinesApi val Kosmos.keyguardDismissInteractor by Kosmos.Fixture { KeyguardDismissInteractor( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardSmartspaceInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardSmartspaceInteractorKosmos.kt index b5d5d641b0fe..87109b17ef0e 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardSmartspaceInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardSmartspaceInteractorKosmos.kt @@ -14,14 +14,11 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package com.android.systemui.keyguard.domain.interactor import com.android.systemui.keyguard.data.repository.keyguardSmartspaceRepository import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture -import kotlinx.coroutines.ExperimentalCoroutinesApi var Kosmos.keyguardSmartspaceInteractor by Fixture { KeyguardSmartspaceInteractor( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WallpaperFocalAreaInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WallpaperFocalAreaInteractorKosmos.kt new file mode 100644 index 000000000000..8fd6f62b315f --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WallpaperFocalAreaInteractorKosmos.kt @@ -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. + */ + +package com.android.systemui.keyguard.domain.interactor + +import android.content.applicationContext +import com.android.systemui.keyguard.data.repository.keyguardClockRepository +import com.android.systemui.keyguard.data.repository.keyguardRepository +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.shade.data.repository.shadeRepository +import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor +import com.android.systemui.wallpapers.data.repository.wallpaperRepository + +val Kosmos.wallpaperFocalAreaInteractor by + Kosmos.Fixture { + WallpaperFocalAreaInteractor( + applicationScope = applicationCoroutineScope, + context = applicationContext, + keyguardRepository = keyguardRepository, + shadeRepository = shadeRepository, + activeNotificationsInteractor = activeNotificationsInteractor, + keyguardClockRepository = keyguardClockRepository, + wallpaperRepository = wallpaperRepository, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowKosmos.kt index 1a05d21cc30a..31fb36eb26db 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowKosmos.kt @@ -14,15 +14,12 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package com.android.systemui.keyguard.ui import com.android.keyguard.logging.keyguardTransitionAnimationLogger import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture -import kotlinx.coroutines.ExperimentalCoroutinesApi val Kosmos.keyguardTransitionAnimationFlow by Fixture { KeyguardTransitionAnimationFlow( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinderKosmos.kt index 740d8919cbc0..697e7b9476ca 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinderKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinderKosmos.kt @@ -38,9 +38,7 @@ import com.android.systemui.log.logcatLogBuffer import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.statusbar.gesture.TapGestureDetector import com.android.systemui.util.mockito.mock -import kotlinx.coroutines.ExperimentalCoroutinesApi -@OptIn(ExperimentalCoroutinesApi::class) val Kosmos.alternateBouncerViewBinder by Kosmos.Fixture { AlternateBouncerViewBinder( @@ -52,7 +50,6 @@ val Kosmos.alternateBouncerViewBinder by ) } -@ExperimentalCoroutinesApi private val Kosmos.alternateBouncerDependencies by Kosmos.Fixture { AlternateBouncerDependencies( @@ -69,7 +66,6 @@ private val Kosmos.alternateBouncerDependencies by ) } -@ExperimentalCoroutinesApi private val Kosmos.alternateBouncerUdfpsIconViewModel by Kosmos.Fixture { AlternateBouncerUdfpsIconViewModel( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerMessageAreaViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerMessageAreaViewModelKosmos.kt index b7d9676040d0..938556e71cbb 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerMessageAreaViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerMessageAreaViewModelKosmos.kt @@ -20,9 +20,7 @@ import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor import com.android.systemui.deviceentry.domain.interactor.biometricMessageInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.util.time.systemClock -import kotlinx.coroutines.ExperimentalCoroutinesApi -@ExperimentalCoroutinesApi val Kosmos.alternateBouncerMessageAreaViewModel by Kosmos.Fixture { AlternateBouncerMessageAreaViewModel( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModelKosmos.kt index 3ed9392bab2a..7d729e38fdca 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModelKosmos.kt @@ -14,15 +14,12 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture -import kotlinx.coroutines.ExperimentalCoroutinesApi val Kosmos.alternateBouncerToAodTransitionViewModel by Fixture { AlternateBouncerToAodTransitionViewModel( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToDozingTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToDozingTransitionViewModelKosmos.kt index c6f07068aad4..71cfb40c11d6 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToDozingTransitionViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToDozingTransitionViewModelKosmos.kt @@ -20,9 +20,7 @@ import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsIntera import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture -import kotlinx.coroutines.ExperimentalCoroutinesApi -@ExperimentalCoroutinesApi val Kosmos.alternateBouncerToDozingTransitionViewModel by Fixture { AlternateBouncerToDozingTransitionViewModel( deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelKosmos.kt index b943298f6b53..3ec0ee040269 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelKosmos.kt @@ -14,15 +14,12 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.statusbar.sysuiStatusBarStateController -import kotlinx.coroutines.ExperimentalCoroutinesApi val Kosmos.alternateBouncerToGoneTransitionViewModel by Fixture { AlternateBouncerToGoneTransitionViewModel( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToLockscreenTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToLockscreenTransitionViewModelKosmos.kt index 6c644eee24ff..346580a91f9b 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToLockscreenTransitionViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToLockscreenTransitionViewModelKosmos.kt @@ -14,14 +14,11 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture -import kotlinx.coroutines.ExperimentalCoroutinesApi val Kosmos.alternateBouncerToLockscreenTransitionViewModel by Fixture { AlternateBouncerToLockscreenTransitionViewModel( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToOccludedTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToOccludedTransitionViewModelKosmos.kt index 71ad3c6689f7..87367b24fc7d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToOccludedTransitionViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToOccludedTransitionViewModelKosmos.kt @@ -14,14 +14,11 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture -import kotlinx.coroutines.ExperimentalCoroutinesApi val Kosmos.alternateBouncerToOccludedTransitionViewModel by Fixture { AlternateBouncerToOccludedTransitionViewModel( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelKosmos.kt index 79892442092c..7bf778deeab5 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelKosmos.kt @@ -14,17 +14,13 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.blurConfig import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture -import kotlinx.coroutines.ExperimentalCoroutinesApi -@OptIn(ExperimentalCoroutinesApi::class) val Kosmos.alternateBouncerToPrimaryBouncerTransitionViewModel by Fixture { AlternateBouncerToPrimaryBouncerTransitionViewModel( animationFlow = keyguardTransitionAnimationFlow, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelKosmos.kt index f1d87fe3abb7..3da27cb3c11d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelKosmos.kt @@ -14,8 +14,6 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor @@ -25,7 +23,6 @@ import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInterac import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.statusbar.phone.statusBarKeyguardViewManager -import kotlinx.coroutines.ExperimentalCoroutinesApi val Kosmos.alternateBouncerViewModel by Fixture { AlternateBouncerViewModel( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModelKosmos.kt index 92cfbef987f6..335ab84a0851 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModelKosmos.kt @@ -14,15 +14,12 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture -import kotlinx.coroutines.ExperimentalCoroutinesApi val Kosmos.alternateBouncerWindowViewModel by Fixture { AlternateBouncerWindowViewModel( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelKosmos.kt index c3c2c8c95aad..1471ddbcea61 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelKosmos.kt @@ -14,8 +14,6 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.common.ui.domain.interactor.configurationInteractor @@ -25,7 +23,6 @@ import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInterac import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.kosmos.applicationCoroutineScope -import kotlinx.coroutines.ExperimentalCoroutinesApi var Kosmos.aodBurnInViewModel by Fixture { AodBurnInViewModel( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModelKosmos.kt index b6f278c1b466..6aad53a5d067 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModelKosmos.kt @@ -14,14 +14,11 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture -import kotlinx.coroutines.ExperimentalCoroutinesApi val Kosmos.aodToGoneTransitionViewModel by Fixture { AodToGoneTransitionViewModel( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelKosmos.kt index b8fcec648393..25a8d5d201be 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelKosmos.kt @@ -14,15 +14,12 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.shade.domain.interactor.shadeInteractor -import kotlinx.coroutines.ExperimentalCoroutinesApi val Kosmos.aodToLockscreenTransitionViewModel by Fixture { AodToLockscreenTransitionViewModel( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModelKosmos.kt index 8d066fc05996..3b33ee47bc41 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModelKosmos.kt @@ -14,14 +14,11 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture -import kotlinx.coroutines.ExperimentalCoroutinesApi val Kosmos.aodToOccludedTransitionViewModel by Fixture { AodToOccludedTransitionViewModel( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerViewModelKosmos.kt index faa290be6129..ae411367fcfc 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerViewModelKosmos.kt @@ -20,9 +20,7 @@ import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.blurConfig import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture -import kotlinx.coroutines.ExperimentalCoroutinesApi -@OptIn(ExperimentalCoroutinesApi::class) val Kosmos.aodToPrimaryBouncerTransitionViewModel by Fixture { AodToPrimaryBouncerTransitionViewModel( animationFlow = keyguardTransitionAnimationFlow, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsKosmos.kt index 9774e4aa51a5..0b364eafb418 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsKosmos.kt @@ -14,8 +14,6 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.bouncer.domain.interactor.mockPrimaryBouncerInteractor @@ -25,7 +23,6 @@ import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.statusbar.sysuiStatusBarStateController -import kotlinx.coroutines.ExperimentalCoroutinesApi val Kosmos.bouncerToGoneFlows by Fixture { BouncerToGoneFlows( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModelKosmos.kt index fc4f3a553d51..bd0045501ec8 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModelKosmos.kt @@ -21,9 +21,7 @@ import com.android.systemui.common.ui.domain.interactor.configurationInteractor import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture -import kotlinx.coroutines.ExperimentalCoroutinesApi -@ExperimentalCoroutinesApi val Kosmos.deviceEntryBackgroundViewModel by Fixture { DeviceEntryBackgroundViewModel( context = applicationContext, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryFgIconViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryFgIconViewModelKosmos.kt index 4f638d0e4a38..1a4bd338ade7 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryFgIconViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryFgIconViewModelKosmos.kt @@ -23,9 +23,7 @@ import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsIntera import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture -import kotlinx.coroutines.ExperimentalCoroutinesApi -@ExperimentalCoroutinesApi val Kosmos.deviceEntryForegroundIconViewModel by Fixture { DeviceEntryForegroundViewModel( context = applicationContext, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt index 67fa857a1ecd..f8393d537f82 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt @@ -29,7 +29,6 @@ import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.kosmos.testScope import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.statusbar.phone.statusBarKeyguardViewManager -import kotlinx.coroutines.ExperimentalCoroutinesApi val Kosmos.fakeDeviceEntryIconViewModelTransition by Fixture { FakeDeviceEntryIconTransition() } @@ -37,7 +36,6 @@ val Kosmos.deviceEntryIconViewModelTransitionsMock by Fixture { setOf<DeviceEntryIconTransition>(fakeDeviceEntryIconViewModelTransition) } -@ExperimentalCoroutinesApi val Kosmos.deviceEntryIconViewModel by Fixture { DeviceEntryIconViewModel( transitions = deviceEntryIconViewModelTransitionsMock, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGlanceableHubTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGlanceableHubTransitionViewModelKosmos.kt index ef10459b45cb..87c3dbf9487e 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGlanceableHubTransitionViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGlanceableHubTransitionViewModelKosmos.kt @@ -19,9 +19,7 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture -import kotlinx.coroutines.ExperimentalCoroutinesApi -@ExperimentalCoroutinesApi val Kosmos.dozingToGlanceableHubTransitionViewModel by Fixture { DozingToGlanceableHubTransitionViewModel(animationFlow = keyguardTransitionAnimationFlow) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGoneTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGoneTransitionViewModelKosmos.kt index 36ddc29b8914..7c066036b131 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGoneTransitionViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGoneTransitionViewModelKosmos.kt @@ -19,9 +19,7 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture -import kotlinx.coroutines.ExperimentalCoroutinesApi -@ExperimentalCoroutinesApi val Kosmos.dozingToGoneTransitionViewModel by Fixture { DozingToGoneTransitionViewModel( animationFlow = keyguardTransitionAnimationFlow, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModelKosmos.kt index de52d848e94b..46f9f8dcd962 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModelKosmos.kt @@ -19,9 +19,7 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture -import kotlinx.coroutines.ExperimentalCoroutinesApi -@ExperimentalCoroutinesApi val Kosmos.dozingToLockscreenTransitionViewModel by Fixture { DozingToLockscreenTransitionViewModel( animationFlow = keyguardTransitionAnimationFlow, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModelKosmos.kt index 8162520e5d88..25865ae8700f 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModelKosmos.kt @@ -18,9 +18,7 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow import com.android.systemui.kosmos.Kosmos -import kotlinx.coroutines.ExperimentalCoroutinesApi -@ExperimentalCoroutinesApi val Kosmos.dozingToOccludedTransitionViewModel by Kosmos.Fixture { DozingToOccludedTransitionViewModel( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModelKosmos.kt index d3ccb297fc9d..39f9530bcb17 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModelKosmos.kt @@ -20,9 +20,7 @@ import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.blurConfig import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture -import kotlinx.coroutines.ExperimentalCoroutinesApi -@ExperimentalCoroutinesApi val Kosmos.dozingToPrimaryBouncerTransitionViewModel by Fixture { DozingToPrimaryBouncerTransitionViewModel( animationFlow = keyguardTransitionAnimationFlow, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToAodTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToAodTransitionViewModelKosmos.kt index b5f0b897deba..ec1f906dc179 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToAodTransitionViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToAodTransitionViewModelKosmos.kt @@ -14,15 +14,12 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture -import kotlinx.coroutines.ExperimentalCoroutinesApi var Kosmos.dreamingToAodTransitionViewModel by Fixture { DreamingToAodTransitionViewModel( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGoneTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGoneTransitionViewModelKosmos.kt index f389142554b1..1e832bdf82dd 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGoneTransitionViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGoneTransitionViewModelKosmos.kt @@ -18,9 +18,7 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow import com.android.systemui.kosmos.Kosmos -import kotlinx.coroutines.ExperimentalCoroutinesApi -@ExperimentalCoroutinesApi val Kosmos.dreamingToGoneTransitionViewModel by Kosmos.Fixture { DreamingToGoneTransitionViewModel( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelKosmos.kt index d06bab2f5345..1d0a210110ed 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelKosmos.kt @@ -19,9 +19,7 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture -import kotlinx.coroutines.ExperimentalCoroutinesApi -@ExperimentalCoroutinesApi val Kosmos.dreamingToLockscreenTransitionViewModel by Fixture { DreamingToLockscreenTransitionViewModel( animationFlow = keyguardTransitionAnimationFlow, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModelKosmos.kt index b1c21b8fa6cf..bb1098f14ea6 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModelKosmos.kt @@ -14,17 +14,13 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.common.ui.domain.interactor.configurationInteractor import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture -import kotlinx.coroutines.ExperimentalCoroutinesApi -@OptIn(ExperimentalCoroutinesApi::class) val Kosmos.glanceableHubToLockscreenTransitionViewModel by Fixture { GlanceableHubToLockscreenTransitionViewModel( configurationInteractor = configurationInteractor, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelKosmos.kt index 8549a30c346e..2d24ef2fcfee 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelKosmos.kt @@ -14,8 +14,6 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor @@ -23,7 +21,6 @@ import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.power.domain.interactor.powerInteractor -import kotlinx.coroutines.ExperimentalCoroutinesApi var Kosmos.goneToAodTransitionViewModel by Fixture { GoneToAodTransitionViewModel( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDozingTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDozingTransitionViewModelKosmos.kt index b19d4e87e68c..3f7348be8fe5 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDozingTransitionViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDozingTransitionViewModelKosmos.kt @@ -20,9 +20,7 @@ import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsIntera import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture -import kotlinx.coroutines.ExperimentalCoroutinesApi -@ExperimentalCoroutinesApi val Kosmos.goneToDozingTransitionViewModel by Fixture { GoneToDozingTransitionViewModel( deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelKosmos.kt index b267a962a1ff..86ef95cbab4c 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelKosmos.kt @@ -14,14 +14,11 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture -import kotlinx.coroutines.ExperimentalCoroutinesApi val Kosmos.goneToDreamingTransitionViewModel by Fixture { GoneToDreamingTransitionViewModel( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GoneToLockscreenTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GoneToLockscreenTransitionViewModelKosmos.kt index 1b6fa064854d..4322a887928a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GoneToLockscreenTransitionViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GoneToLockscreenTransitionViewModelKosmos.kt @@ -14,14 +14,11 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture -import kotlinx.coroutines.ExperimentalCoroutinesApi var Kosmos.goneToLockscreenTransitionViewModel by Fixture { GoneToLockscreenTransitionViewModel( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardMediaViewModelFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardMediaViewModelFactoryKosmos.kt new file mode 100644 index 000000000000..16d3fdc26613 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardMediaViewModelFactoryKosmos.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.viewmodel + +import com.android.systemui.keyguard.domain.interactor.keyguardInteractor +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.media.controls.domain.pipeline.interactor.mediaCarouselInteractor + +val Kosmos.keyguardMediaViewModelFactory by + Kosmos.Fixture { + object : KeyguardMediaViewModel.Factory { + override fun create(): KeyguardMediaViewModel { + return KeyguardMediaViewModel(mediaCarouselInteractor, keyguardInteractor) + } + } + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt index 1c0f97d294df..37df05b68f9e 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt @@ -13,8 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.communal.domain.interactor.communalInteractor @@ -22,6 +20,7 @@ import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.keyguard.domain.interactor.pulseExpansionInteractor +import com.android.systemui.keyguard.domain.interactor.wallpaperFocalAreaInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.kosmos.applicationCoroutineScope @@ -31,7 +30,6 @@ import com.android.systemui.statusbar.notification.icon.ui.viewmodel.notificatio import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationsKeyguardInteractor import com.android.systemui.statusbar.phone.dozeParameters import com.android.systemui.statusbar.phone.screenOffAnimationController -import kotlinx.coroutines.ExperimentalCoroutinesApi val Kosmos.keyguardRootViewModel by Fixture { KeyguardRootViewModel( @@ -90,5 +88,6 @@ val Kosmos.keyguardRootViewModel by Fixture { screenOffAnimationController = screenOffAnimationController, aodBurnInViewModel = aodBurnInViewModel, shadeInteractor = shadeInteractor, + wallpaperFocalAreaInteractor = wallpaperFocalAreaInteractor, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelKosmos.kt index f45e33bf6865..5234b5bd17e2 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelKosmos.kt @@ -14,8 +14,6 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor @@ -23,7 +21,6 @@ import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.power.domain.interactor.powerInteractor -import kotlinx.coroutines.ExperimentalCoroutinesApi var Kosmos.lockscreenToAodTransitionViewModel by Fixture { LockscreenToAodTransitionViewModel( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModelKosmos.kt index aa8e9a8c9a8c..bf1af3c47674 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModelKosmos.kt @@ -20,9 +20,7 @@ import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsIntera import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture -import kotlinx.coroutines.ExperimentalCoroutinesApi -@ExperimentalCoroutinesApi val Kosmos.lockscreenToDozingTransitionViewModel by Fixture { LockscreenToDozingTransitionViewModel( deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelKosmos.kt index 56d5ff6e30eb..1246b9455b4b 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelKosmos.kt @@ -14,14 +14,11 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture -import kotlinx.coroutines.ExperimentalCoroutinesApi val Kosmos.lockscreenToDreamingTransitionViewModel by Fixture { LockscreenToDreamingTransitionViewModel( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModelKosmos.kt index 471381f7a13f..0e961ccaf07b 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModelKosmos.kt @@ -14,17 +14,13 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.common.ui.domain.interactor.configurationInteractor import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture -import kotlinx.coroutines.ExperimentalCoroutinesApi -@ExperimentalCoroutinesApi val Kosmos.lockscreenToGlanceableHubTransitionViewModel by Fixture { LockscreenToGlanceableHubTransitionViewModel( configurationInteractor = configurationInteractor, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModelKosmos.kt index 7a023ee29299..172b4f8db92b 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModelKosmos.kt @@ -20,9 +20,7 @@ import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.statusbar.sysuiStatusBarStateController -import kotlinx.coroutines.ExperimentalCoroutinesApi -@ExperimentalCoroutinesApi val Kosmos.lockscreenToGoneTransitionViewModel by Fixture { LockscreenToGoneTransitionViewModel( animationFlow = keyguardTransitionAnimationFlow, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelKosmos.kt index 9953d39e9a49..abd29cadb8f7 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelKosmos.kt @@ -14,15 +14,12 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.common.ui.domain.interactor.configurationInteractor import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture -import kotlinx.coroutines.ExperimentalCoroutinesApi val Kosmos.lockscreenToOccludedTransitionViewModel by Fixture { LockscreenToOccludedTransitionViewModel( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelKosmos.kt index 68280d7622fd..b5c67b66ae5f 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelKosmos.kt @@ -14,15 +14,12 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.blurConfig import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture -import kotlinx.coroutines.ExperimentalCoroutinesApi val Kosmos.lockscreenToPrimaryBouncerTransitionViewModel by Fixture { LockscreenToPrimaryBouncerTransitionViewModel( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAlternateBouncerTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAlternateBouncerTransitionViewModelKosmos.kt index 2acd1b40af3e..39a545a8c451 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAlternateBouncerTransitionViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAlternateBouncerTransitionViewModelKosmos.kt @@ -14,14 +14,11 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture -import kotlinx.coroutines.ExperimentalCoroutinesApi val Kosmos.occludedToAlternateBouncerTransitionViewModel by Fixture { OccludedToAlternateBouncerTransitionViewModel(animationFlow = keyguardTransitionAnimationFlow) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModelKosmos.kt index b7867b6cabde..dd6d9acaa33e 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModelKosmos.kt @@ -14,15 +14,12 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture -import kotlinx.coroutines.ExperimentalCoroutinesApi val Kosmos.occludedToAodTransitionViewModel by Fixture { OccludedToAodTransitionViewModel( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToDozingTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToDozingTransitionViewModelKosmos.kt index 4196e54a085d..4e8896ab11c1 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToDozingTransitionViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToDozingTransitionViewModelKosmos.kt @@ -14,15 +14,12 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture -import kotlinx.coroutines.ExperimentalCoroutinesApi val Kosmos.occludedToDozingTransitionViewModel by Fixture { OccludedToDozingTransitionViewModel( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToGoneTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToGoneTransitionViewModelKosmos.kt index 3b96912b53c6..70e9af1b1058 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToGoneTransitionViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToGoneTransitionViewModelKosmos.kt @@ -14,14 +14,11 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture -import kotlinx.coroutines.ExperimentalCoroutinesApi var Kosmos.occludedToGoneTransitionViewModel by Fixture { OccludedToGoneTransitionViewModel( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelKosmos.kt index f86e9b7216ce..5b1d8f126f84 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelKosmos.kt @@ -14,8 +14,6 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.common.ui.domain.interactor.configurationInteractor @@ -25,7 +23,6 @@ import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInterac import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture -import kotlinx.coroutines.ExperimentalCoroutinesApi var Kosmos.occludedToLockscreenTransitionViewModel by Fixture { OccludedToLockscreenTransitionViewModel( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OffToLockscreenTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OffToLockscreenTransitionViewModelKosmos.kt index 5d62a0f4a0cf..579819f6d265 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OffToLockscreenTransitionViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OffToLockscreenTransitionViewModelKosmos.kt @@ -14,14 +14,11 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture -import kotlinx.coroutines.ExperimentalCoroutinesApi val Kosmos.offToLockscreenTransitionViewModel by Fixture { OffToLockscreenTransitionViewModel(animationFlow = keyguardTransitionAnimationFlow) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModelKosmos.kt index 043a49f5f640..73ef4328657a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModelKosmos.kt @@ -14,8 +14,6 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor @@ -23,7 +21,6 @@ import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.blurConfig import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture -import kotlinx.coroutines.ExperimentalCoroutinesApi val Kosmos.primaryBouncerToAodTransitionViewModel by Fixture { PrimaryBouncerToAodTransitionViewModel( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToDozingTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToDozingTransitionViewModelKosmos.kt index 59ea2c93089c..99297351bdaa 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToDozingTransitionViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToDozingTransitionViewModelKosmos.kt @@ -21,9 +21,7 @@ import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.blurConfig import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture -import kotlinx.coroutines.ExperimentalCoroutinesApi -@ExperimentalCoroutinesApi val Kosmos.primaryBouncerToDozingTransitionViewModel by Fixture { PrimaryBouncerToDozingTransitionViewModel( deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelKosmos.kt index b470ab12828b..acf0827b1614 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelKosmos.kt @@ -14,8 +14,6 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.bouncer.domain.interactor.mockPrimaryBouncerInteractor @@ -25,7 +23,6 @@ import com.android.systemui.keyguard.ui.transitions.blurConfig import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.statusbar.sysuiStatusBarStateController -import kotlinx.coroutines.ExperimentalCoroutinesApi val Kosmos.primaryBouncerToGoneTransitionViewModel by Fixture { PrimaryBouncerToGoneTransitionViewModel( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelKosmos.kt index c3447753a86d..bd5a21195795 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelKosmos.kt @@ -14,15 +14,12 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.blurConfig import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture -import kotlinx.coroutines.ExperimentalCoroutinesApi val Kosmos.primaryBouncerToLockscreenTransitionViewModel by Fixture { PrimaryBouncerToLockscreenTransitionViewModel( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModelKosmos.kt index 8da16fc4e855..e38c419a97f2 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModelKosmos.kt @@ -14,8 +14,6 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package com.android.systemui.keyguard.ui.viewmodel import android.content.applicationContext @@ -29,7 +27,6 @@ import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.statusbar.phone.dozeServiceHost -import kotlinx.coroutines.ExperimentalCoroutinesApi val Kosmos.sideFpsProgressBarViewModel by Kosmos.Fixture { 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 439df543b9fb..a4c2cc275e44 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 @@ -9,7 +9,6 @@ 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 @@ -73,7 +72,6 @@ fun <T> Kosmos.collectValues(flow: Flow<T>): FlowValue<List<T>> = testScope.coll * 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) } @@ -90,7 +88,6 @@ fun <T> Kosmos.currentValue(fn: () -> T) = testScope.currentValue(fn) * Retrieve the result of [fn] after running all pending tasks. Do not use to retrieve the value of * a flow directly; for that, use either `currentValue(StateFlow)` or [collectLastValue] */ -@OptIn(ExperimentalCoroutinesApi::class) fun <T> TestScope.currentValue(fn: () -> T): T { runCurrent() return fn() @@ -102,7 +99,6 @@ fun <T> Kosmos.currentValue(stateFlow: StateFlow<T>): T { } /** Safely verify that a mock has been called after the test scope has caught up */ -@OptIn(ExperimentalCoroutinesApi::class) fun <T> TestScope.verifyCurrent(mock: T): T { runCurrent() return verify(mock) 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 39f1ad42797b..35e90f06ddde 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 @@ -14,8 +14,6 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package com.android.systemui.kosmos import android.content.applicationContext @@ -92,7 +90,6 @@ import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisionin import com.android.systemui.statusbar.ui.viewmodel.keyguardStatusBarViewModel import com.android.systemui.util.time.systemClock import com.android.systemui.volume.domain.interactor.volumeDialogInteractor -import kotlinx.coroutines.ExperimentalCoroutinesApi /** * Helper for using [Kosmos] from Java. diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/TaskSwitcherKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/TaskSwitcherKosmos.kt index 5acadd7f192a..2ef3f4a70998 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/TaskSwitcherKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/TaskSwitcherKosmos.kt @@ -23,7 +23,6 @@ import com.android.systemui.mediaprojection.data.repository.realMediaProjectionR import com.android.systemui.mediaprojection.taskswitcher.data.repository.ActivityTaskManagerTasksRepository import com.android.systemui.mediaprojection.taskswitcher.domain.interactor.TaskSwitchInteractor import com.android.systemui.mediaprojection.taskswitcher.ui.viewmodel.TaskSwitcherNotificationViewModel -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.UnconfinedTestDispatcher val Kosmos.fakeActivityTaskManager by Kosmos.Fixture { FakeActivityTaskManager() } @@ -47,5 +46,4 @@ val Kosmos.taskSwitcherInteractor by val Kosmos.taskSwitcherViewModel by Kosmos.Fixture { TaskSwitcherNotificationViewModel(taskSwitcherInteractor, testDispatcher) } -@OptIn(ExperimentalCoroutinesApi::class) fun taskSwitcherKosmos() = Kosmos().apply { testDispatcher = UnconfinedTestDispatcher() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/FakeVolumeDialogController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/FakeVolumeDialogController.kt index 9d73ae3f176f..a1e7d5cede13 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/FakeVolumeDialogController.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/FakeVolumeDialogController.kt @@ -40,7 +40,8 @@ class FakeVolumeDialogController(private val audioManager: AudioManager) : Volum private val callbacks = CopyOnWriteArraySet<VolumeDialogController.Callbacks>() private var hasVibrator: Boolean = true - private var state = VolumeDialogController.State() + var state = VolumeDialogController.State() + private set override fun setActiveStream(stream: Int) { updateState { 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 49957f0b43cc..65e580cafcb5 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 @@ -39,9 +39,7 @@ import com.android.systemui.shade.largeScreenHeaderHelper import com.android.systemui.shade.transition.largeScreenShadeInterpolator import com.android.systemui.statusbar.disableflags.domain.interactor.disableFlagsInteractor import com.android.systemui.statusbar.sysuiStatusBarStateController -import kotlinx.coroutines.ExperimentalCoroutinesApi -@OptIn(ExperimentalCoroutinesApi::class) val Kosmos.qsFragmentComposeViewModelFactory by Kosmos.Fixture { object : QSFragmentComposeViewModel.Factory { 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 aa6ce9a433df..f8fa5db4ddf7 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 @@ -23,6 +23,7 @@ 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 +import com.android.systemui.shade.ui.viewmodel.shadeHeaderViewModelFactory val Kosmos.quickSettingsContainerViewModelFactory by Kosmos.Fixture { @@ -33,6 +34,7 @@ val Kosmos.quickSettingsContainerViewModelFactory by return QuickSettingsContainerViewModel( brightnessSliderViewModelFactory, quickQuickSettingsViewModelFactory, + shadeHeaderViewModelFactory, supportsBrightnessMirroring, tileGridViewModel, editModeViewModel, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/resolver/SceneResolverKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/resolver/SceneResolverKosmos.kt index a4a63ec6ca21..9d18fbfccb36 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/resolver/SceneResolverKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/resolver/SceneResolverKosmos.kt @@ -14,8 +14,6 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package com.android.systemui.scene.domain.resolver import com.android.compose.animation.scene.SceneKey @@ -25,7 +23,6 @@ import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.scene.shared.model.SceneFamilies -import kotlinx.coroutines.ExperimentalCoroutinesApi val Kosmos.sceneFamilyResolvers: Map<SceneKey, SceneResolver> get() = mapOf(SceneFamilies.Home to homeSceneFamilyResolver) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/ScrimStartableKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/ScrimStartableKosmos.kt index b64c84075936..12b46536b2a2 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/ScrimStartableKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/ScrimStartableKosmos.kt @@ -14,8 +14,6 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package com.android.systemui.scene.domain.startable import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor @@ -31,7 +29,6 @@ import com.android.systemui.settings.brightness.domain.interactor.brightnessMirr import com.android.systemui.statusbar.phone.dozeServiceHost import com.android.systemui.statusbar.phone.scrimController import com.android.systemui.statusbar.phone.statusBarKeyguardViewManager -import kotlinx.coroutines.ExperimentalCoroutinesApi val Kosmos.scrimStartable by Fixture { ScrimStartable( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/StatusBarStartableKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/StatusBarStartableKosmos.kt index ee69c30fe6b9..881d110dff25 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/StatusBarStartableKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/StatusBarStartableKosmos.kt @@ -14,8 +14,6 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package com.android.systemui.scene.domain.startable import android.content.applicationContext @@ -33,7 +31,6 @@ import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.scene.domain.interactor.sceneContainerOcclusionInteractor import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.user.domain.interactor.selectedUserInteractor -import kotlinx.coroutines.ExperimentalCoroutinesApi val Kosmos.statusBarStartable by Fixture { StatusBarStartable( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt index b9f0c9a70d3d..e2b2026550f1 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt @@ -14,8 +14,6 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package com.android.systemui.shade import com.android.systemui.assist.AssistManager @@ -39,7 +37,6 @@ import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.statusbar.policy.deviceProvisionedController import com.android.systemui.statusbar.window.StatusBarWindowControllerStore import com.android.systemui.util.mockito.mock -import kotlinx.coroutines.ExperimentalCoroutinesApi val Kosmos.shadeControllerSceneImpl by Kosmos.Fixture { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeTestUtil.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeTestUtil.kt index b3d89dbb834d..e143324baeae 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeTestUtil.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeTestUtil.kt @@ -25,7 +25,6 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.data.repository.FakeShadeRepository import com.android.systemui.shade.data.repository.ShadeRepository -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.TestScope @@ -193,7 +192,6 @@ class ShadeTestUtilLegacyImpl( } /** Sets up shade state for tests when the scene container flag is enabled. */ -@OptIn(ExperimentalCoroutinesApi::class) class ShadeTestUtilSceneImpl( val testScope: TestScope, val sceneInteractor: SceneInteractor, 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 aaef27d257c5..d9a348d93533 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 @@ -16,12 +16,15 @@ package com.android.systemui.shade.data.repository +import com.android.systemui.display.data.repository.FakeFocusedDisplayRepository import com.android.systemui.display.data.repository.displayRepository import com.android.systemui.keyguard.data.repository.keyguardRepository import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.shade.display.AnyExternalShadeDisplayPolicy import com.android.systemui.shade.display.DefaultDisplayShadePolicy +import com.android.systemui.shade.display.FakeShadeDisplayPolicy +import com.android.systemui.shade.display.FocusShadeDisplayPolicy import com.android.systemui.shade.display.ShadeDisplayPolicy import com.android.systemui.shade.display.ShadeExpansionIntent import com.android.systemui.shade.display.StatusBarTouchShadeDisplayPolicy @@ -46,8 +49,6 @@ val Kosmos.statusBarTouchShadeDisplayPolicy: StatusBarTouchShadeDisplayPolicy by StatusBarTouchShadeDisplayPolicy( displayRepository = displayRepository, backgroundScope = testScope.backgroundScope, - keyguardRepository = keyguardRepository, - shadeOnDefaultDisplayWhenLocked = false, shadeInteractor = { shadeInteractor }, notificationElement = { notificationElement }, qsShadeElement = { qsElement }, @@ -55,13 +56,15 @@ val Kosmos.statusBarTouchShadeDisplayPolicy: StatusBarTouchShadeDisplayPolicy by } val Kosmos.shadeExpansionIntent: ShadeExpansionIntent by Kosmos.Fixture { statusBarTouchShadeDisplayPolicy } -val Kosmos.shadeDisplaysRepository: MutableShadeDisplaysRepository by +val Kosmos.shadeDisplaysRepository: ShadeDisplaysRepository by Kosmos.Fixture { ShadeDisplaysRepositoryImpl( bgScope = testScope.backgroundScope, globalSettings = fakeGlobalSettings, policies = shadeDisplayPolicies, defaultPolicy = defaultShadeDisplayPolicy, + shadeOnDefaultDisplayWhenLocked = true, + keyguardRepository = keyguardRepository, ) } @@ -71,8 +74,16 @@ val Kosmos.shadeDisplayPolicies: Set<ShadeDisplayPolicy> by defaultShadeDisplayPolicy, anyExternalShadeDisplayPolicy, statusBarTouchShadeDisplayPolicy, + FakeShadeDisplayPolicy, ) } val Kosmos.fakeShadeDisplaysRepository: FakeShadeDisplayRepository by Kosmos.Fixture { FakeShadeDisplayRepository() } +val Kosmos.fakeFocusedDisplayRepository: FakeFocusedDisplayRepository by + Kosmos.Fixture { FakeFocusedDisplayRepository() } + +val Kosmos.focusShadeDisplayPolicy: FocusShadeDisplayPolicy by + Kosmos.Fixture { + FocusShadeDisplayPolicy(focusedDisplayRepository = fakeFocusedDisplayRepository) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/PrivacyChipInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/PrivacyChipInteractorKosmos.kt index 7334286f00c4..b92ad9439936 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/PrivacyChipInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/PrivacyChipInteractorKosmos.kt @@ -21,6 +21,7 @@ import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.privacy.privacyDialogController import com.android.systemui.privacy.privacyDialogControllerV2 import com.android.systemui.shade.data.repository.fakePrivacyChipRepository +import com.android.systemui.shade.data.repository.shadeDialogContextInteractor import com.android.systemui.statusbar.policy.deviceProvisionedController var Kosmos.privacyChipInteractor: PrivacyChipInteractor by @@ -31,5 +32,6 @@ var Kosmos.privacyChipInteractor: PrivacyChipInteractor by privacyDialogController = privacyDialogController, privacyDialogControllerV2 = privacyDialogControllerV2, deviceProvisionedController = deviceProvisionedController, + shadeDialogContextInteractor = shadeDialogContextInteractor, ) } 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 index 923de2dcbf68..170d067b5c0c 100644 --- 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 @@ -18,12 +18,16 @@ package com.android.systemui.shade.domain.interactor import android.content.mockedContext import android.window.WindowContext +import com.android.systemui.common.ui.data.repository.configurationRepository import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.shade.ShadeDisplayChangeLatencyTracker import com.android.systemui.shade.ShadeWindowLayoutParams import com.android.systemui.shade.data.repository.fakeShadeDisplaysRepository import com.android.systemui.shade.data.repository.shadeExpansionIntent +import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor +import com.android.systemui.statusbar.notification.row.notificationRebindingTracker +import com.android.systemui.statusbar.notification.stack.notificationStackRebindingHider import java.util.Optional import org.mockito.kotlin.any import org.mockito.kotlin.mock @@ -46,10 +50,14 @@ val Kosmos.shadeDisplaysInteractor by ShadeDisplaysInteractor( fakeShadeDisplaysRepository, mockedWindowContext, + configurationRepository, testScope.backgroundScope, testScope.backgroundScope.coroutineContext, mockedShadeDisplayChangeLatencyTracker, Optional.of(shadeExpandedStateInteractor), shadeExpansionIntent, + activeNotificationsInteractor, + notificationRebindingTracker, + Optional.of(notificationStackRebindingHider), ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorKosmos.kt index 7892e962d63d..1b50094ec0b7 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorKosmos.kt @@ -16,14 +16,57 @@ package com.android.systemui.shade.domain.interactor +import android.provider.Settings import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.kosmos.testScope +import com.android.systemui.shade.data.repository.fakeShadeRepository import com.android.systemui.shade.data.repository.shadeRepository +import com.android.systemui.shared.settings.data.repository.secureSettingsRepository +import kotlinx.coroutines.launch val Kosmos.shadeModeInteractor by Fixture { ShadeModeInteractorImpl( applicationScope = applicationCoroutineScope, repository = shadeRepository, + secureSettingsRepository = secureSettingsRepository, ) } + +// TODO(b/391578667): Make this user-aware once supported by FakeSecureSettingsRepository. +/** + * Enables the Dual Shade setting, and (optionally) sets the shade layout to be wide (`true`) + * or narrow (`false`). + * + * In a wide layout, notifications and quick settings shades each take up only half the screen + * width. In a narrow layout, they each take up the entire screen width. + */ +fun Kosmos.enableDualShade(wideLayout: Boolean? = null) { + testScope.launch { + secureSettingsRepository.setInt(Settings.Secure.DUAL_SHADE, 1) + + if (wideLayout != null) { + fakeShadeRepository.setShadeLayoutWide(wideLayout) + } + } +} + +// TODO(b/391578667): Make this user-aware once supported by FakeSecureSettingsRepository. +fun Kosmos.disableDualShade() { + testScope.launch { secureSettingsRepository.setInt(Settings.Secure.DUAL_SHADE, 0) } +} + +fun Kosmos.enableSingleShade() { + testScope.launch { + disableDualShade() + fakeShadeRepository.setShadeLayoutWide(false) + } +} + +fun Kosmos.enableSplitShade() { + testScope.launch { + disableDualShade() + fakeShadeRepository.setShadeLayoutWide(true) + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelKosmos.kt index f5b856df8835..7eb9f3472482 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelKosmos.kt @@ -20,6 +20,7 @@ import android.content.applicationContext import com.android.systemui.broadcast.broadcastDispatcher import com.android.systemui.kosmos.Kosmos import com.android.systemui.plugins.activityStarter +import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.shade.domain.interactor.privacyChipInteractor import com.android.systemui.shade.domain.interactor.shadeHeaderClockInteractor import com.android.systemui.shade.domain.interactor.shadeInteractor @@ -31,6 +32,7 @@ val Kosmos.shadeHeaderViewModel: ShadeHeaderViewModel by ShadeHeaderViewModel( context = applicationContext, activityStarter = activityStarter, + sceneInteractor = sceneInteractor, shadeInteractor = shadeInteractor, mobileIconsInteractor = mobileIconsInteractor, mobileIconsViewModel = mobileIconsViewModel, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/RankingBuilder.java b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/RankingBuilder.java index 6cd6594c3404..c6daed1aa58f 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/RankingBuilder.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/RankingBuilder.java @@ -61,6 +61,7 @@ public class RankingBuilder { private boolean mIsBubble = false; private int mProposedImportance = IMPORTANCE_UNSPECIFIED; private boolean mSensitiveContent = false; + private String mSummarization = null; public RankingBuilder() { } @@ -92,6 +93,7 @@ public class RankingBuilder { mIsBubble = ranking.isBubble(); mProposedImportance = ranking.getProposedImportance(); mSensitiveContent = ranking.hasSensitiveContent(); + mSummarization = ranking.getSummarization(); } public Ranking build() { @@ -122,7 +124,8 @@ public class RankingBuilder { mRankingAdjustment, mIsBubble, mProposedImportance, - mSensitiveContent); + mSensitiveContent, + mSummarization); return ranking; } @@ -262,6 +265,11 @@ public class RankingBuilder { return this; } + public RankingBuilder setSummarization(String summary) { + mSummarization = summary; + return this; + } + private static <E> ArrayList<E> copyList(List<E> list) { if (list == null) { return null; 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 index 88bf9a5f2d5b..6593547f393d 100644 --- 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 @@ -27,9 +27,7 @@ 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( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorKosmos.kt index 774782cc019c..dc7595f7f2e4 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorKosmos.kt @@ -14,8 +14,6 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package com.android.systemui.statusbar.notification.icon.domain.interactor import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor @@ -27,7 +25,6 @@ import com.android.systemui.statusbar.notification.data.repository.notifications import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor import com.android.systemui.statusbar.notification.domain.interactor.headsUpNotificationIconInteractor import com.android.wm.shell.bubbles.bubblesOptional -import kotlinx.coroutines.ExperimentalCoroutinesApi val Kosmos.alwaysOnDisplayNotificationIconsInteractor by Fixture { AlwaysOnDisplayNotificationIconsInteractor( 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 3fddd47c25f0..6246d986f9e5 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 @@ -380,6 +380,7 @@ class ExpandableNotificationRowBuilder( mSmartReplyController, Mockito.mock(IStatusBarService::class.java, STUB_ONLY), Mockito.mock(UiEventLogger::class.java, STUB_ONLY), + Mockito.mock(NotificationRebindingTracker::class.java, STUB_ONLY), ) row.setAboveShelfChangedListener {} mBindStage.getStageParams(entry).requireContentViews(extraInflationFlags) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderHapticsViewBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/NotificationRebindingTrackerKosmos.kt index d6845b1ff7e3..deac37a55717 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderHapticsViewBinderKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/NotificationRebindingTrackerKosmos.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 The Android Open Source Project + * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,20 +14,17 @@ * limitations under the License. */ -package com.android.systemui.volume.dialog.sliders.ui +package com.android.systemui.statusbar.notification.row -import com.android.systemui.haptics.msdl.msdlPlayer -import com.android.systemui.haptics.vibratorHelper import com.android.systemui.kosmos.Kosmos -import com.android.systemui.util.time.systemClock -import com.android.systemui.volume.dialog.sliders.ui.viewmodel.volumeDialogSliderInputEventsViewModel +import com.android.systemui.kosmos.testScope +import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor -val Kosmos.volumeDialogSliderHapticsViewBinder by +val Kosmos.notificationRebindingTracker by Kosmos.Fixture { - VolumeDialogSliderHapticsViewBinder( - volumeDialogSliderInputEventsViewModel, - vibratorHelper, - msdlPlayer, - systemClock, + NotificationRebindingTracker( + activeNotificationsInteractor = activeNotificationsInteractor, + bgScope = testScope.backgroundScope, + appScope = testScope.backgroundScope, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/AmbientStateKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/AmbientStateKosmos.kt index 65f4ec1c437c..d65a4a0532e3 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/AmbientStateKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/AmbientStateKosmos.kt @@ -23,9 +23,7 @@ import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.shade.transition.largeScreenShadeInterpolator import com.android.systemui.statusbar.notification.headsup.mockAvalancheController import com.android.systemui.statusbar.phone.statusBarKeyguardViewManager -import kotlinx.coroutines.ExperimentalCoroutinesApi -@OptIn(ExperimentalCoroutinesApi::class) val Kosmos.ambientState by Fixture { AmbientState( /*context=*/ applicationContext, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerKosmos.kt index 569429f180df..73f296415a5a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerKosmos.kt @@ -21,3 +21,6 @@ import com.android.systemui.util.mockito.mock val Kosmos.notificationStackScrollLayoutController by Kosmos.Fixture { mock<NotificationStackScrollLayoutController>() } + +val Kosmos.notificationStackRebindingHider by + Kosmos.Fixture { mock<NotificationStackRebindingHider>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorKosmos.kt index b1e9d89dfd42..e250575ad3fc 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorKosmos.kt @@ -14,8 +14,6 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package com.android.systemui.statusbar.notification.stack.domain.interactor import android.content.applicationContext @@ -25,7 +23,6 @@ import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.shade.largeScreenHeaderHelper import com.android.systemui.statusbar.policy.splitShadeStateController -import kotlinx.coroutines.ExperimentalCoroutinesApi val Kosmos.sharedNotificationContainerInteractor by Kosmos.Fixture { 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 8461da77796d..45c56ae0ab7a 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 @@ -60,9 +60,7 @@ import com.android.systemui.statusbar.notification.stack.domain.interactor.notif import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor import com.android.systemui.unfold.domain.interactor.unfoldTransitionInteractor import com.android.systemui.window.ui.viewmodel.fakeBouncerTransitions -import kotlinx.coroutines.ExperimentalCoroutinesApi -@OptIn(ExperimentalCoroutinesApi::class) val Kosmos.sharedNotificationContainerViewModel by Fixture { SharedNotificationContainerViewModel( interactor = sharedNotificationContainerInteractor, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/BiometricUnlockController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/BiometricUnlockController.kt index f377e28bb51a..c87a20e660a1 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/BiometricUnlockController.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/BiometricUnlockController.kt @@ -14,14 +14,11 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package com.android.systemui.statusbar.phone import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.util.mockito.mock -import kotlinx.coroutines.ExperimentalCoroutinesApi val Kosmos.biometricUnlockController: BiometricUnlockController by Fixture { mock<BiometricUnlockController>() diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/DozeServiceHostKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/DozeServiceHostKosmos.kt index d0bf584d9a62..78140416cb40 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/DozeServiceHostKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/DozeServiceHostKosmos.kt @@ -31,9 +31,7 @@ import com.android.systemui.statusbar.notificationShadeWindowController import com.android.systemui.statusbar.policy.batteryController import com.android.systemui.statusbar.policy.deviceProvisionedController import com.android.systemui.statusbar.pulseExpansionHandler -import kotlinx.coroutines.ExperimentalCoroutinesApi -@OptIn(ExperimentalCoroutinesApi::class) val Kosmos.dozeServiceHost: DozeServiceHost by Kosmos.Fixture { DozeServiceHost( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ManagedProfileControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ManagedProfileControllerKosmos.kt index ef04b9d907f1..7c8ad12ccf0d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ManagedProfileControllerKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ManagedProfileControllerKosmos.kt @@ -14,14 +14,11 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package com.android.systemui.statusbar.phone import android.testing.LeakCheck import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.utils.leaks.FakeManagedProfileController -import kotlinx.coroutines.ExperimentalCoroutinesApi val Kosmos.fakeManagedProfileController by Fixture { FakeManagedProfileController(LeakCheck()) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerKosmos.kt index ddce4c896c14..4e15ea2d9377 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerKosmos.kt @@ -18,7 +18,5 @@ package com.android.systemui.statusbar.phone import com.android.systemui.kosmos.Kosmos import com.android.systemui.util.mockito.mock -import kotlinx.coroutines.ExperimentalCoroutinesApi -@OptIn(ExperimentalCoroutinesApi::class) var Kosmos.statusBarKeyguardViewManager by Kosmos.Fixture { mock<StatusBarKeyguardViewManager>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt index 7743a1c7e0cd..0d6ac4481742 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt @@ -47,9 +47,7 @@ import com.android.systemui.statusbar.notificationShadeWindowController import com.android.systemui.statusbar.policy.keyguardStateController import com.android.systemui.wmshell.bubblesManager import java.util.Optional -import kotlinx.coroutines.ExperimentalCoroutinesApi -@OptIn(ExperimentalCoroutinesApi::class) val Kosmos.statusBarNotificationActivityStarter by Kosmos.Fixture { StatusBarNotificationActivityStarter( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/coroutines/MainDispatcherRule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/coroutines/MainDispatcherRule.kt index 577620347991..a0d9227cc048 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/coroutines/MainDispatcherRule.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/coroutines/MainDispatcherRule.kt @@ -17,7 +17,6 @@ package com.android.systemui.util.coroutines import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestDispatcher import kotlinx.coroutines.test.resetMain import kotlinx.coroutines.test.setMain @@ -28,7 +27,6 @@ import org.junit.runner.Description * Overrides main dispatcher to passed testDispatcher. You probably want to use it when using * viewModelScope which has hardcoded main dispatcher. */ -@OptIn(ExperimentalCoroutinesApi::class) class MainDispatcherRule(val testDispatcher: TestDispatcher) : TestWatcher() { override fun starting(description: Description) { Dispatchers.setMain(testDispatcher) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/time/FakeSystemClockKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/time/FakeSystemClockKosmos.kt index f3a8b14abab8..703d6ad83eac 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/time/FakeSystemClockKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/time/FakeSystemClockKosmos.kt @@ -20,10 +20,8 @@ import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.currentTime -@OptIn(ExperimentalCoroutinesApi::class) val Kosmos.systemClock by Kosmos.Fixture<SystemClock> { mock { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponentKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponentKosmos.kt index 36fa82f82f0d..4ca044d60f3f 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponentKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponentKosmos.kt @@ -32,10 +32,8 @@ import com.android.systemui.volume.dialog.data.repository.volumeDialogVisibility import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSliderType import com.android.systemui.volume.dialog.sliders.domain.model.volumeDialogSliderType import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogOverscrollViewBinder -import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSliderHapticsViewBinder import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSliderViewBinder import com.android.systemui.volume.dialog.sliders.ui.volumeDialogOverscrollViewBinder -import com.android.systemui.volume.dialog.sliders.ui.volumeDialogSliderHapticsViewBinder import com.android.systemui.volume.dialog.sliders.ui.volumeDialogSliderViewBinder import com.android.systemui.volume.mediaControllerRepository import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.mediaControllerInteractor @@ -65,9 +63,6 @@ fun Kosmos.volumeDialogSliderComponent(type: VolumeDialogSliderType): VolumeDial override fun sliderViewBinder(): VolumeDialogSliderViewBinder = localKosmos.volumeDialogSliderViewBinder - override fun sliderHapticsViewBinder(): VolumeDialogSliderHapticsViewBinder = - localKosmos.volumeDialogSliderHapticsViewBinder - override fun overscrollViewBinder(): VolumeDialogOverscrollViewBinder = localKosmos.volumeDialogOverscrollViewBinder } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinderKosmos.kt index c6db717e004f..484a7cc30152 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinderKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinderKosmos.kt @@ -16,14 +16,16 @@ package com.android.systemui.volume.dialog.sliders.ui +import com.android.systemui.haptics.slider.sliderHapticsViewModelFactory import com.android.systemui.kosmos.Kosmos -import com.android.systemui.volume.dialog.sliders.ui.viewmodel.volumeDialogSliderInputEventsViewModel +import com.android.systemui.volume.dialog.sliders.ui.viewmodel.volumeDialogOverscrollViewModel import com.android.systemui.volume.dialog.sliders.ui.viewmodel.volumeDialogSliderViewModel val Kosmos.volumeDialogSliderViewBinder by Kosmos.Fixture { VolumeDialogSliderViewBinder( volumeDialogSliderViewModel, - volumeDialogSliderInputEventsViewModel, + volumeDialogOverscrollViewModel, + sliderHapticsViewModelFactory, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModelKosmos.kt index b26081c40c38..90bbb28ff519 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModelKosmos.kt @@ -21,6 +21,7 @@ import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.util.time.systemClock import com.android.systemui.volume.dialog.domain.interactor.volumeDialogVisibilityInteractor import com.android.systemui.volume.dialog.shared.volumeDialogLogger +import com.android.systemui.volume.dialog.sliders.domain.interactor.volumeDialogSliderInputEventsInteractor import com.android.systemui.volume.dialog.sliders.domain.interactor.volumeDialogSliderInteractor import com.android.systemui.volume.dialog.sliders.domain.model.volumeDialogSliderType @@ -29,6 +30,7 @@ val Kosmos.volumeDialogSliderViewModel by VolumeDialogSliderViewModel( sliderType = volumeDialogSliderType, interactor = volumeDialogSliderInteractor, + inputEventsInteractor = volumeDialogSliderInputEventsInteractor, visibilityInteractor = volumeDialogVisibilityInteractor, coroutineScope = applicationCoroutineScope, volumeDialogSliderIconProvider = volumeDialogSliderIconProvider, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModuleKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModuleKosmos.kt index 0c814c566d63..556c34d85f8e 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModuleKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModuleKosmos.kt @@ -16,13 +16,16 @@ package com.android.systemui.volume.panel.component.mediaoutput +import android.content.applicationContext import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testScope +import com.android.systemui.volume.panel.component.mediaoutput.domain.MediaOutputAvailabilityCriteria import com.android.systemui.volume.panel.component.mediaoutput.ui.composable.MediaOutputComponent import com.android.systemui.volume.panel.component.mediaoutput.ui.viewmodel.mediaOutputViewModel -import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria -import com.android.systemui.volume.panel.domain.availableCriteria var Kosmos.mediaOutputComponent: MediaOutputComponent by Kosmos.Fixture { MediaOutputComponent(mediaOutputViewModel) } -var Kosmos.mediaOutputAvailabilityCriteria: ComponentAvailabilityCriteria by - Kosmos.Fixture { availableCriteria } +var Kosmos.mediaOutputAvailabilityCriteria by + Kosmos.Fixture { + MediaOutputAvailabilityCriteria(applicationContext, testScope.backgroundScope) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryKosmos.kt index ddb9a3ffee6d..f0c0d30e6db4 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryKosmos.kt @@ -19,7 +19,6 @@ package com.android.systemui.wallpapers.data.repository import android.content.applicationContext import com.android.app.wallpaperManager import com.android.systemui.broadcast.broadcastDispatcher -import com.android.systemui.keyguard.data.repository.keyguardClockRepository import com.android.systemui.keyguard.data.repository.keyguardRepository import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture @@ -34,8 +33,7 @@ val Kosmos.wallpaperRepository by Fixture { bgDispatcher = testDispatcher, broadcastDispatcher = broadcastDispatcher, userRepository = userRepository, - wallpaperManager = wallpaperManager, - keyguardClockRepository = keyguardClockRepository, keyguardRepository = keyguardRepository, + wallpaperManager = wallpaperManager, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/window/domain/interactor/WindowRootViewBlurInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/window/domain/interactor/WindowRootViewBlurInteractorKosmos.kt index ad30ea26c5b8..151f3d45b906 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/window/domain/interactor/WindowRootViewBlurInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/window/domain/interactor/WindowRootViewBlurInteractorKosmos.kt @@ -17,7 +17,9 @@ package com.android.systemui.window.domain.interactor import com.android.systemui.keyguard.domain.interactor.keyguardInteractor +import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.window.data.repository.windowRootViewBlurRepository val Kosmos.windowRootViewBlurInteractor by @@ -25,5 +27,7 @@ val Kosmos.windowRootViewBlurInteractor by WindowRootViewBlurInteractor( repository = windowRootViewBlurRepository, keyguardInteractor = keyguardInteractor, + keyguardTransitionInteractor = keyguardTransitionInteractor, + applicationScope = applicationCoroutineScope, ) } diff --git a/packages/Vcn/framework-b/framework-vcn-jarjar-rules.txt b/packages/Vcn/framework-b/framework-vcn-jarjar-rules.txt index 7e27b24f749c..33287b7f3449 100644 --- a/packages/Vcn/framework-b/framework-vcn-jarjar-rules.txt +++ b/packages/Vcn/framework-b/framework-vcn-jarjar-rules.txt @@ -1,2 +1,3 @@ rule android.net.vcn.persistablebundleutils.** android.net.connectivity.android.net.vcn.persistablebundleutils.@1 -rule android.net.vcn.util.** android.net.connectivity.android.net.vcn.util.@1
\ No newline at end of file +rule android.net.vcn.util.** android.net.connectivity.android.net.vcn.util.@1 +rule android.util.IndentingPrintWriter android.net.connectivity.android.util.IndentingPrintWriter
\ No newline at end of file diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp index 8e998426685b..65550f2b4273 100644 --- a/ravenwood/Android.bp +++ b/ravenwood/Android.bp @@ -175,9 +175,9 @@ java_library { } java_device_for_host { - name: "ravenwood-junit-impl-for-ravenizer", + name: "ravenwood-junit-for-ravenizer", libs: [ - "ravenwood-junit-impl", + "ravenwood-junit", ], visibility: [":__subpackages__"], } @@ -661,6 +661,9 @@ android_ravenwood_libgroup { // StatsD "framework-statsd.ravenwood", + // Graphics + "framework-graphics.ravenwood", + // Provide runtime versions of utils linked in below "junit", "truth", diff --git a/ravenwood/Framework.bp b/ravenwood/Framework.bp index 99fc31b258e9..71496b0d5766 100644 --- a/ravenwood/Framework.bp +++ b/ravenwood/Framework.bp @@ -399,3 +399,55 @@ java_genrule { "framework-statsd.ravenwood.jar", ], } + +////////////////////// +// framework-graphics +////////////////////// + +java_genrule { + name: "framework-graphics.ravenwood-base", + tools: ["hoststubgen"], + cmd: "$(location hoststubgen) " + + "@$(location :ravenwood-standard-options) " + + + "--debug-log $(location framework-graphics.log) " + + "--stats-file $(location framework-graphics_stats.csv) " + + "--supported-api-list-file $(location framework-graphics_apis.csv) " + + "--gen-keep-all-file $(location framework-graphics_keep_all.txt) " + + "--gen-input-dump-file $(location framework-graphics_dump.txt) " + + + "--out-impl-jar $(location ravenwood.jar) " + + "--in-jar $(location :framework-graphics.impl{.jar}) " + + + "--policy-override-file $(location :ravenwood-common-policies) ", + srcs: [ + ":framework-graphics.impl{.jar}", + + ":ravenwood-common-policies", + ":ravenwood-standard-options", + ], + out: [ + "ravenwood.jar", + + // Following files are created just as FYI. + "framework-graphics_keep_all.txt", + "framework-graphics_dump.txt", + + "framework-graphics.log", + "framework-graphics_stats.csv", + "framework-graphics_apis.csv", + ], + visibility: ["//visibility:private"], +} + +java_genrule { + name: "framework-graphics.ravenwood", + defaults: ["ravenwood-internal-only-visibility-genrule"], + cmd: "cp $(in) $(out)", + srcs: [ + ":framework-graphics.ravenwood-base{ravenwood.jar}", + ], + out: [ + "framework-graphics.ravenwood.jar", + ], +} diff --git a/ravenwood/scripts/pta-framework.sh b/ravenwood/scripts/pta-framework.sh index 224ab59e2e09..46c2c01c8ee8 100755 --- a/ravenwood/scripts/pta-framework.sh +++ b/ravenwood/scripts/pta-framework.sh @@ -79,6 +79,7 @@ run_pta() { $extra_args if ! [[ -f $OUT_SCRIPT ]] ; then + echo "No files need updating." # no operations generated. exit 0 fi @@ -88,4 +89,4 @@ run_pta() { return 0 } -run_pta "$extra_args"
\ No newline at end of file +run_pta "$extra_args" diff --git a/ravenwood/texts/ravenwood-framework-policies.txt b/ravenwood/texts/ravenwood-framework-policies.txt index 4033782c607e..fff9e6ad41d5 100644 --- a/ravenwood/texts/ravenwood-framework-policies.txt +++ b/ravenwood/texts/ravenwood-framework-policies.txt @@ -62,4 +62,4 @@ class android.text.ClipboardManager keep # no-pta # Just enough to allow ResourcesManager to run class android.hardware.display.DisplayManagerGlobal keep # no-pta - method getInstance ()Landroid/hardware/display/DisplayManagerGlobal; ignore + method getInstance ()Landroid/hardware/display/DisplayManagerGlobal; ignore # no-pta diff --git a/ravenwood/texts/ravenwood-standard-options.txt b/ravenwood/texts/ravenwood-standard-options.txt index 27223d8b72ff..91fd9283aff2 100644 --- a/ravenwood/texts/ravenwood-standard-options.txt +++ b/ravenwood/texts/ravenwood-standard-options.txt @@ -31,6 +31,9 @@ --remove-annotation android.ravenwood.annotation.RavenwoodRemove +--ignore-annotation + android.ravenwood.annotation.RavenwoodIgnore + --substitute-annotation android.ravenwood.annotation.RavenwoodReplace diff --git a/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/policytoannot/Annotations.kt b/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/policytoannot/Annotations.kt index 4a11259a8ef7..ef1cb5dfca89 100644 --- a/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/policytoannot/Annotations.kt +++ b/ravenwood/tools/ravenhelper/src/com/android/platform/test/ravenwood/ravenhelper/policytoannot/Annotations.kt @@ -45,7 +45,8 @@ class Annotations { "@android.ravenwood.annotation.RavenwoodRedirect" FilterPolicy.Throw -> "@android.ravenwood.annotation.RavenwoodThrow" - FilterPolicy.Ignore -> null // Ignore has no annotation. (because it's not very safe.) + FilterPolicy.Ignore -> + "@android.ravenwood.annotation.RavenwoodIgnore" FilterPolicy.Remove -> "@android.ravenwood.annotation.RavenwoodRemove" } diff --git a/ravenwood/tools/ravenizer/Android.bp b/ravenwood/tools/ravenizer/Android.bp index 2892d0778ec6..a52a04b44f2d 100644 --- a/ravenwood/tools/ravenizer/Android.bp +++ b/ravenwood/tools/ravenizer/Android.bp @@ -19,7 +19,7 @@ java_binary_host { "ow2-asm-tree", "ow2-asm-util", "junit", - "ravenwood-junit-impl-for-ravenizer", + "ravenwood-junit-for-ravenizer", ], visibility: ["//visibility:public"], } diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig index 7f0bf0375b4a..722255404dd1 100644 --- a/services/accessibility/accessibility.aconfig +++ b/services/accessibility/accessibility.aconfig @@ -145,6 +145,16 @@ flag { } flag { + name: "event_dispatcher_raw_event" + namespace: "accessibility" + description: "Fixes EventDispatcher#sendMotionEvent callers to properly provide raw event" + bug: "385812366" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "fix_drag_pointer_when_ending_drag" namespace: "accessibility" description: "Send the correct pointer id when transitioning from dragging to delegating states." diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java index 70c4c1311fc9..fda57d6bb986 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java @@ -21,8 +21,6 @@ import static android.view.MotionEvent.ACTION_SCROLL; import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY; -import static com.android.hardware.input.Flags.enableTalkbackAndMagnifierKeyGestures; - import android.accessibilityservice.AccessibilityTrace; import android.annotation.MainThread; import android.annotation.NonNull; @@ -47,6 +45,7 @@ import android.view.MotionEvent.PointerProperties; import android.view.accessibility.AccessibilityEvent; import com.android.server.LocalServices; +import com.android.server.accessibility.autoclick.AutoclickController; import com.android.server.accessibility.gestures.TouchExplorer; import com.android.server.accessibility.magnification.FullScreenMagnificationController; import com.android.server.accessibility.magnification.FullScreenMagnificationGestureHandler; @@ -758,7 +757,7 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo addFirstEventHandler(Display.DEFAULT_DISPLAY, mMouseKeysInterceptor); } - if (enableTalkbackAndMagnifierKeyGestures() && isAnyMagnificationEnabled()) { + if (Flags.enableMagnificationKeyboardControl() && isAnyMagnificationEnabled()) { mMagnificationKeyHandler = new MagnificationKeyHandler( mAms.getMagnificationController()); addFirstEventHandler(Display.DEFAULT_DISPLAY, mMagnificationKeyHandler); diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 875b655fe3d2..91775f8eed96 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -5084,39 +5084,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub final List<String> permittedServices = dpm.getPermittedAccessibilityServices(userId); // permittedServices null means all accessibility services are allowed. - boolean allowed = permittedServices == null || permittedServices.contains(packageName); - if (allowed) { - if (android.permission.flags.Flags.enhancedConfirmationModeApisEnabled() - && android.security.Flags.extendEcmToAllSettings()) { - try { - final EnhancedConfirmationManager userContextEcm = - mContext.createContextAsUser(UserHandle.of(userId), /* flags = */ 0) - .getSystemService(EnhancedConfirmationManager.class); - if (userContextEcm != null) { - return !userContextEcm.isRestricted(packageName, - AppOpsManager.OPSTR_BIND_ACCESSIBILITY_SERVICE); - } - return false; - } catch (PackageManager.NameNotFoundException e) { - Log.e(LOG_TAG, "Exception when retrieving package:" + packageName, e); - return false; - } - } else { - try { - final int mode = mContext.getSystemService(AppOpsManager.class) - .noteOpNoThrow(AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS, - uid, packageName); - final boolean ecmEnabled = mContext.getResources().getBoolean( - com.android.internal.R.bool.config_enhancedConfirmationModeEnabled); - return !ecmEnabled || mode == AppOpsManager.MODE_ALLOWED - || mode == AppOpsManager.MODE_DEFAULT; - } catch (Exception e) { - // Fallback in case if app ops is not available in testing. - return false; - } - } - } - return false; + return permittedServices == null || permittedServices.contains(packageName); } finally { Binder.restoreCallingIdentity(identity); } diff --git a/services/accessibility/java/com/android/server/accessibility/AutoclickController.java b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java index 8b758d29a2ac..1bc9c783df76 100644 --- a/services/accessibility/java/com/android/server/accessibility/AutoclickController.java +++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java @@ -14,11 +14,14 @@ * limitations under the License. */ -package com.android.server.accessibility; +package com.android.server.accessibility.autoclick; -import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; +import static android.view.MotionEvent.BUTTON_PRIMARY; +import static android.view.accessibility.AccessibilityManager.AUTOCLICK_CURSOR_AREA_SIZE_DEFAULT; +import static android.view.accessibility.AccessibilityManager.AUTOCLICK_DELAY_DEFAULT; +import static android.view.accessibility.AccessibilityManager.AUTOCLICK_IGNORE_MINOR_CURSOR_MOVEMENT_DEFAULT; -import static com.android.server.accessibility.AutoclickIndicatorView.SHOW_INDICATOR_DELAY_TIME; +import static com.android.server.accessibility.autoclick.AutoclickIndicatorView.SHOW_INDICATOR_DELAY_TIME; import android.accessibilityservice.AccessibilityTrace; import android.annotation.NonNull; @@ -26,7 +29,6 @@ import android.annotation.Nullable; import android.content.ContentResolver; import android.content.Context; import android.database.ContentObserver; -import android.graphics.PixelFormat; import android.net.Uri; import android.os.Handler; import android.os.SystemClock; @@ -37,10 +39,14 @@ import android.view.MotionEvent; import android.view.MotionEvent.PointerCoords; import android.view.MotionEvent.PointerProperties; import android.view.WindowManager; -import android.view.accessibility.AccessibilityManager; import androidx.annotation.VisibleForTesting; +import com.android.internal.accessibility.util.AccessibilityUtils; +import com.android.server.accessibility.AccessibilityTraceManager; +import com.android.server.accessibility.BaseEventStreamTransformation; +import com.android.server.accessibility.Flags; + /** * Implements "Automatically click on mouse stop" feature. * @@ -96,8 +102,7 @@ public class AutoclickController extends BaseEventStreamTransformation { initiateAutoclickIndicator(handler); } - mClickScheduler = - new ClickScheduler(handler, AccessibilityManager.AUTOCLICK_DELAY_DEFAULT); + mClickScheduler = new ClickScheduler(handler, AUTOCLICK_DELAY_DEFAULT); mAutoclickSettingsObserver = new AutoclickSettingsObserver(mUserId, handler); mAutoclickSettingsObserver.start( mContext.getContentResolver(), @@ -117,21 +122,8 @@ public class AutoclickController extends BaseEventStreamTransformation { mAutoclickIndicatorScheduler = new AutoclickIndicatorScheduler(handler); mAutoclickIndicatorView = new AutoclickIndicatorView(mContext); - final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(); - layoutParams.type = WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY; - layoutParams.flags = - WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE - | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE - | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; - layoutParams.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; - layoutParams.setFitInsetsTypes(0); - layoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; - layoutParams.format = PixelFormat.TRANSLUCENT; - layoutParams.setTitle(AutoclickIndicatorView.class.getSimpleName()); - layoutParams.inputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL; - mWindowManager = mContext.getSystemService(WindowManager.class); - mWindowManager.addView(mAutoclickIndicatorView, layoutParams); + mWindowManager.addView(mAutoclickIndicatorView, mAutoclickIndicatorView.getLayoutParams()); } @Override @@ -209,6 +201,11 @@ public class AutoclickController extends BaseEventStreamTransformation { private final Uri mAutoclickCursorAreaSizeSettingUri = Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_AUTOCLICK_CURSOR_AREA_SIZE); + /** URI used to identify ignore minor cursor movement setting with content resolver. */ + private final Uri mAutoclickIgnoreMinorCursorMovementSettingUri = + Settings.Secure.getUriFor( + Settings.Secure.ACCESSIBILITY_AUTOCLICK_IGNORE_MINOR_CURSOR_MOVEMENT); + private ContentResolver mContentResolver; private ClickScheduler mClickScheduler; private AutoclickIndicatorScheduler mAutoclickIndicatorScheduler; @@ -247,18 +244,32 @@ public class AutoclickController extends BaseEventStreamTransformation { mContentResolver = contentResolver; mClickScheduler = clickScheduler; mAutoclickIndicatorScheduler = autoclickIndicatorScheduler; - mContentResolver.registerContentObserver(mAutoclickDelaySettingUri, false, this, + mContentResolver.registerContentObserver( + mAutoclickDelaySettingUri, + /* notifyForDescendants= */ false, + /* observer= */ this, mUserId); // Initialize mClickScheduler's initial delay value. - onChange(true, mAutoclickDelaySettingUri); + onChange(/* selfChange= */ true, mAutoclickDelaySettingUri); if (Flags.enableAutoclickIndicator()) { // Register observer to listen to cursor area size setting change. mContentResolver.registerContentObserver( - mAutoclickCursorAreaSizeSettingUri, false, this, mUserId); + mAutoclickCursorAreaSizeSettingUri, + /* notifyForDescendants= */ false, + /* observer= */ this, + mUserId); // Initialize mAutoclickIndicatorView's initial size. - onChange(true, mAutoclickCursorAreaSizeSettingUri); + onChange(/* selfChange= */ true, mAutoclickCursorAreaSizeSettingUri); + + // Register observer to listen to ignore minor cursor movement setting change. + mContentResolver.registerContentObserver( + mAutoclickIgnoreMinorCursorMovementSettingUri, + /* notifyForDescendants= */ false, + /* observer= */ this, + mUserId); + onChange(/* selfChange= */ true, mAutoclickIgnoreMinorCursorMovementSettingUri); } } @@ -279,21 +290,41 @@ public class AutoclickController extends BaseEventStreamTransformation { @Override public void onChange(boolean selfChange, Uri uri) { if (mAutoclickDelaySettingUri.equals(uri)) { - int delay = Settings.Secure.getIntForUser( - mContentResolver, Settings.Secure.ACCESSIBILITY_AUTOCLICK_DELAY, - AccessibilityManager.AUTOCLICK_DELAY_DEFAULT, mUserId); - mClickScheduler.updateDelay(delay); - } - if (Flags.enableAutoclickIndicator() - && mAutoclickCursorAreaSizeSettingUri.equals(uri)) { - int size = + int delay = Settings.Secure.getIntForUser( mContentResolver, - Settings.Secure.ACCESSIBILITY_AUTOCLICK_CURSOR_AREA_SIZE, - AccessibilityManager.AUTOCLICK_CURSOR_AREA_SIZE_DEFAULT, + Settings.Secure.ACCESSIBILITY_AUTOCLICK_DELAY, + AUTOCLICK_DELAY_DEFAULT, mUserId); - if (mAutoclickIndicatorScheduler != null) { - mAutoclickIndicatorScheduler.updateCursorAreaSize(size); + mClickScheduler.updateDelay(delay); + } + + if (Flags.enableAutoclickIndicator()) { + if (mAutoclickCursorAreaSizeSettingUri.equals(uri)) { + int size = + Settings.Secure.getIntForUser( + mContentResolver, + Settings.Secure.ACCESSIBILITY_AUTOCLICK_CURSOR_AREA_SIZE, + AUTOCLICK_CURSOR_AREA_SIZE_DEFAULT, + mUserId); + if (mAutoclickIndicatorScheduler != null) { + mAutoclickIndicatorScheduler.updateCursorAreaSize(size); + } + mClickScheduler.updateMovementSlope(size); + } + + if (mAutoclickIgnoreMinorCursorMovementSettingUri.equals(uri)) { + boolean ignoreMinorCursorMovement = + Settings.Secure.getIntForUser( + mContentResolver, + Settings.Secure + .ACCESSIBILITY_AUTOCLICK_IGNORE_MINOR_CURSOR_MOVEMENT, + AUTOCLICK_IGNORE_MINOR_CURSOR_MOVEMENT_DEFAULT + ? AccessibilityUtils.State.ON + : AccessibilityUtils.State.OFF, + mUserId) + == AccessibilityUtils.State.ON; + mClickScheduler.setIgnoreMinorCursorMovement(ignoreMinorCursorMovement); } } } @@ -365,11 +396,16 @@ public class AutoclickController extends BaseEventStreamTransformation { @VisibleForTesting final class ClickScheduler implements Runnable { /** - * Minimal distance pointer has to move relative to anchor in order for movement not to be - * discarded as noise. Anchor is the position of the last MOVE event that was not considered - * noise. + * Default minimal distance pointer has to move relative to anchor in order for movement not + * to be discarded as noise. Anchor is the position of the last MOVE event that was not + * considered noise. */ - private static final double MOVEMENT_SLOPE = 20f; + private static final double DEFAULT_MOVEMENT_SLOPE = 20f; + + private double mMovementSlope = DEFAULT_MOVEMENT_SLOPE; + + /** Whether the minor cursor movement should be ignored. */ + private boolean mIgnoreMinorCursorMovement = AUTOCLICK_IGNORE_MINOR_CURSOR_MOVEMENT_DEFAULT; /** Whether there is pending click. */ private boolean mActive; @@ -553,7 +589,19 @@ public class AutoclickController extends BaseEventStreamTransformation { float deltaX = mAnchorCoords.x - event.getX(pointerIndex); float deltaY = mAnchorCoords.y - event.getY(pointerIndex); double delta = Math.hypot(deltaX, deltaY); - return delta > MOVEMENT_SLOPE; + double slope = + ((Flags.enableAutoclickIndicator() && mIgnoreMinorCursorMovement) + ? mMovementSlope + : DEFAULT_MOVEMENT_SLOPE); + return delta > slope; + } + + public void setIgnoreMinorCursorMovement(boolean ignoreMinorCursorMovement) { + mIgnoreMinorCursorMovement = ignoreMinorCursorMovement; + } + + private void updateMovementSlope(double slope) { + mMovementSlope = slope; } /** @@ -581,18 +629,30 @@ public class AutoclickController extends BaseEventStreamTransformation { final long now = SystemClock.uptimeMillis(); - MotionEvent downEvent = MotionEvent.obtain(now, now, MotionEvent.ACTION_DOWN, 1, - mTempPointerProperties, mTempPointerCoords, mMetaState, - MotionEvent.BUTTON_PRIMARY, 1.0f, 1.0f, mLastMotionEvent.getDeviceId(), 0, - mLastMotionEvent.getSource(), mLastMotionEvent.getFlags()); + MotionEvent downEvent = + MotionEvent.obtain( + /* downTime= */ now, + /* eventTime= */ now, + MotionEvent.ACTION_DOWN, + /* pointerCount= */ 1, + mTempPointerProperties, + mTempPointerCoords, + mMetaState, + BUTTON_PRIMARY, + /* xPrecision= */ 1.0f, + /* yPrecision= */ 1.0f, + mLastMotionEvent.getDeviceId(), + /* edgeFlags= */ 0, + mLastMotionEvent.getSource(), + mLastMotionEvent.getFlags()); MotionEvent pressEvent = MotionEvent.obtain(downEvent); pressEvent.setAction(MotionEvent.ACTION_BUTTON_PRESS); - pressEvent.setActionButton(MotionEvent.BUTTON_PRIMARY); + pressEvent.setActionButton(BUTTON_PRIMARY); MotionEvent releaseEvent = MotionEvent.obtain(downEvent); releaseEvent.setAction(MotionEvent.ACTION_BUTTON_RELEASE); - releaseEvent.setActionButton(MotionEvent.BUTTON_PRIMARY); + releaseEvent.setActionButton(BUTTON_PRIMARY); releaseEvent.setButtonState(0); MotionEvent upEvent = MotionEvent.obtain(downEvent); diff --git a/services/accessibility/java/com/android/server/accessibility/AutoclickIndicatorView.java b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickIndicatorView.java index f87dcdb200bb..557e1b2afcd5 100644 --- a/services/accessibility/java/com/android/server/accessibility/AutoclickIndicatorView.java +++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickIndicatorView.java @@ -14,17 +14,21 @@ * limitations under the License. */ -package com.android.server.accessibility; +package com.android.server.accessibility.autoclick; +import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; import static android.view.accessibility.AccessibilityManager.AUTOCLICK_CURSOR_AREA_SIZE_DEFAULT; import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; +import android.graphics.PixelFormat; import android.graphics.RectF; import android.util.DisplayMetrics; import android.view.View; +import android.view.WindowInsets; +import android.view.WindowManager; import android.view.accessibility.AccessibilityManager; import android.view.animation.LinearInterpolator; @@ -81,6 +85,26 @@ public class AutoclickIndicatorView extends View { mRingRect = new RectF(); } + /** + * Retrieves the layout params for AutoclickIndicatorView, used when it's added to the Window + * Manager. + */ + public final WindowManager.LayoutParams getLayoutParams() { + final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(); + layoutParams.type = WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY; + layoutParams.flags = + WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE + | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; + layoutParams.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; + layoutParams.setFitInsetsTypes(WindowInsets.Type.statusBars()); + layoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; + layoutParams.format = PixelFormat.TRANSLUCENT; + layoutParams.setTitle(AutoclickIndicatorView.class.getSimpleName()); + layoutParams.inputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL; + return layoutParams; + } + @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/EventDispatcher.java b/services/accessibility/java/com/android/server/accessibility/gestures/EventDispatcher.java index bf9202f1b266..5c0bbf4e01eb 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/EventDispatcher.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/EventDispatcher.java @@ -31,6 +31,7 @@ import android.view.accessibility.AccessibilityManager; import com.android.server.accessibility.AccessibilityManagerService; import com.android.server.accessibility.EventStreamTransformation; +import com.android.server.accessibility.Flags; import com.android.server.policy.WindowManagerPolicy; /** @@ -297,7 +298,8 @@ class EventDispatcher { sendMotionEvent( prototype, action, - mState.getLastReceivedEvent(), + Flags.eventDispatcherRawEvent() ? mState.getLastReceivedRawEvent() : + mState.getLastReceivedEvent(), pointerIdBits, policyFlags); } @@ -327,7 +329,8 @@ class EventDispatcher { sendMotionEvent( event, action, - mState.getLastReceivedEvent(), + Flags.eventDispatcherRawEvent() ? mState.getLastReceivedRawEvent() : + mState.getLastReceivedEvent(), pointerIdBits, policyFlags); } @@ -394,8 +397,10 @@ class EventDispatcher { continue; } final int action = computeInjectionAction(MotionEvent.ACTION_POINTER_UP, i); - sendMotionEvent( - prototype, action, mState.getLastReceivedEvent(), pointerIdBits, policyFlags); + sendMotionEvent(prototype, action, + Flags.eventDispatcherRawEvent() ? mState.getLastReceivedRawEvent() : + mState.getLastReceivedEvent(), + pointerIdBits, policyFlags); pointerIdBits &= ~(1 << pointerId); } } 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 2c106d31ae59..fd230f69aec0 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java @@ -497,13 +497,14 @@ public class TouchExplorer extends BaseEventStreamTransformation // We have just decided that the user is touch, // exploring so start sending events. - mSendHoverEnterAndMoveDelayed.addEvent(event, mState.getLastReceivedEvent()); + mSendHoverEnterAndMoveDelayed.addEvent(event, + Flags.eventDispatcherRawEvent() ? rawEvent : mState.getLastReceivedEvent()); mSendHoverEnterAndMoveDelayed.forceSendAndRemove(); mSendHoverExitDelayed.cancel(); mDispatcher.sendMotionEvent( event, ACTION_HOVER_MOVE, - event, + Flags.eventDispatcherRawEvent() ? rawEvent : event, pointerIdBits, policyFlags); return true; @@ -1099,7 +1100,8 @@ public class TouchExplorer extends BaseEventStreamTransformation * * @param policyFlags The policy flags associated with the event. */ - private void sendHoverExitAndTouchExplorationGestureEndIfNeeded(int policyFlags) { + @VisibleForTesting + void sendHoverExitAndTouchExplorationGestureEndIfNeeded(int policyFlags) { MotionEvent event = mState.getLastInjectedHoverEvent(); if (event != null && event.getActionMasked() != ACTION_HOVER_EXIT) { final int pointerIdBits = event.getPointerIdBits(); @@ -1109,7 +1111,8 @@ public class TouchExplorer extends BaseEventStreamTransformation mDispatcher.sendMotionEvent( event, ACTION_HOVER_EXIT, - mState.getLastReceivedEvent(), + Flags.eventDispatcherRawEvent() ? mState.getLastReceivedRawEvent() : + mState.getLastReceivedEvent(), pointerIdBits, policyFlags); } @@ -1131,7 +1134,8 @@ public class TouchExplorer extends BaseEventStreamTransformation mDispatcher.sendMotionEvent( event, ACTION_HOVER_ENTER, - mState.getLastReceivedEvent(), + Flags.eventDispatcherRawEvent() ? mState.getLastReceivedRawEvent() : + mState.getLastReceivedEvent(), pointerIdBits, policyFlags); } diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationKeyHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationKeyHandler.java index a65580c82124..f20755328479 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationKeyHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationKeyHandler.java @@ -20,6 +20,7 @@ import android.view.Display; import android.view.KeyEvent; import com.android.server.accessibility.BaseEventStreamTransformation; +import com.android.server.accessibility.Flags; /* * A class that listens to key presses used to control magnification. @@ -79,7 +80,7 @@ public class MagnificationKeyHandler extends BaseEventStreamTransformation { @Override public void onKeyEvent(KeyEvent event, int policyFlags) { - if (!com.android.hardware.input.Flags.enableTalkbackAndMagnifierKeyGestures()) { + if (!Flags.enableMagnificationKeyboardControl()) { // Send to the rest of the handlers. super.onKeyEvent(event, policyFlags); return; diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java index c68e54956c99..25494fce9fbf 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java @@ -19,6 +19,7 @@ package com.android.server.autofill; import static android.Manifest.permission.MANAGE_AUTO_FILL; import static android.content.Context.AUTOFILL_MANAGER_SERVICE; import static android.service.autofill.Flags.fixGetAutofillComponent; +import static android.service.autofill.Flags.improveFillDialogAconfig; import static android.view.autofill.AutofillManager.MAX_TEMP_AUGMENTED_SERVICE_DURATION_MS; import static android.view.autofill.AutofillManager.getSmartSuggestionModeToString; @@ -70,6 +71,7 @@ import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; import android.util.TimeUtils; +import android.view.InsetsController; import android.view.autofill.AutofillFeatureFlags; import android.view.autofill.AutofillId; import android.view.autofill.AutofillManager; @@ -96,6 +98,7 @@ import com.android.server.autofill.ui.AutoFillUI; import com.android.server.infra.AbstractMasterSystemService; import com.android.server.infra.FrameworkResourcesServiceNameResolver; import com.android.server.infra.SecureSettingsServiceNameResolver; +import com.android.server.wm.WindowManagerInternal; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -243,6 +246,8 @@ public final class AutofillManagerService private static final boolean DEFAULT_PCC_USE_FALLBACK = true; + private static final boolean DBG = false; + public AutofillManagerService(Context context) { super(context, new SecureSettingsServiceNameResolver(context, Settings.Secure.AUTOFILL_SERVICE), @@ -301,8 +306,79 @@ public final class AutofillManagerService mCredentialAutofillService = null; Slog.w(TAG, "Invalid CredentialAutofillService"); } + + if (improveFillDialogAconfig()) { + WindowManagerInternal windowManagerInternal = LocalServices.getService( + WindowManagerInternal.class); + WindowManagerInternal.ImeInsetsAnimationChangeListener + imeInsetsAnimationChangeListener = + new WindowManagerInternal.ImeInsetsAnimationChangeListener() { + @Override + public void onAnimationStart( + @InsetsController.AnimationType int animationType, int userId) { + if (DBG) { + Slog.e(TAG, + "onAnimationStart() notifyImeAnimationStart() " + + "animationType:" + + String.valueOf(animationType)); + } + synchronized (mLock) { + + // We are mostly interested in animations that show up the IME + if (animationType == InsetsController.ANIMATION_TYPE_HIDE) { + // IME is going away + mIsImeShowing = false; + } + if (animationType != InsetsController.ANIMATION_TYPE_SHOW) { + return; + } + mIsImeShowing = true; + mImeAnimatingWhileShowingUp = true; + final AutofillManagerServiceImpl service = + peekServiceForUserWithLocalBinderIdentityLocked(userId); + if (service != null) { + service.notifyImeAnimationStart(); + } else if (sVerbose) { + Slog.v(TAG, + "notifyImeAnimationStart(): no service for " + userId); + } + } + } + + @Override + public void onAnimationEnd( + @InsetsController.AnimationType int animationType, int userId) { + if (DBG) { + Slog.e(TAG, + "onAnimationEnd() notifyImeAnimationEnd() " + + "animationType:" + + String.valueOf(animationType)); + } + // We are only interested in animations that show up the IME + if (animationType != InsetsController.ANIMATION_TYPE_SHOW) { + return; + } + mImeAnimatingWhileShowingUp = false; + synchronized (mLock) { + final AutofillManagerServiceImpl service = + peekServiceForUserWithLocalBinderIdentityLocked(userId); + if (service != null) { + service.notifyImeAnimationEnd(); + } else if (sVerbose) { + Slog.v(TAG, "notifyImeAnimationEnd(): no service for " + + userId); + } + } + } + }; + windowManagerInternal.setImeInsetsAnimationChangeListener( + imeInsetsAnimationChangeListener); + } } + public boolean mImeAnimatingWhileShowingUp = false; + public boolean mIsImeShowing = false; + @Override // from AbstractMasterSystemService protected String getServiceSettingsProperty() { return Settings.Secure.AUTOFILL_SERVICE; diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java index 11710c9d8a9b..eda62334ff39 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java @@ -73,6 +73,7 @@ import android.util.LocalLog; import android.util.Pair; import android.util.Slog; import android.util.SparseArray; +import android.view.autofill.AutofillFeatureFlags; import android.view.autofill.AutofillId; import android.view.autofill.AutofillManager; import android.view.autofill.AutofillManager.AutofillCommitReason; @@ -208,6 +209,11 @@ final class AutofillManagerServiceImpl private final DisabledInfoCache mDisabledInfoCache; + // Tracks active session id. There is no guarantee that such a session exists. For eg, if the + // session is destroyed, the id may no longer be valid. We don't update the state in all the + // cases. + private int mActiveSessionId = NO_SESSION; + AutofillManagerServiceImpl(AutofillManagerService master, Object lock, LocalLog uiLatencyHistory, LocalLog wtfHistory, int userId, AutoFillUI ui, AutofillCompatState autofillCompatState, @@ -386,6 +392,7 @@ final class AutofillManagerServiceImpl @NonNull Rect virtualBounds, @Nullable AutofillValue value, boolean hasCallback, @NonNull ComponentName clientActivity, boolean compatMode, boolean bindInstantServiceAllowed, int flags) { + mActiveSessionId = NO_SESSION; // FLAG_AUGMENTED_AUTOFILL_REQUEST is set in the flags when standard autofill is disabled // but the package is allowlisted for augmented autofill boolean forAugmentedAutofillOnly = (flags @@ -444,6 +451,7 @@ final class AutofillManagerServiceImpl if (newSession == null) { return NO_SESSION; } + mActiveSessionId = newSession.id; // Service can be null when it's only for augmented autofill String servicePackageName = mInfo == null ? null : mInfo.getServiceInfo().packageName; @@ -672,7 +680,7 @@ final class AutofillManagerServiceImpl flags, mInputMethodManagerInternal, isPrimaryCredential); mSessions.put(newSession.id, newSession); - if (Flags.multipleFillHistory() && !forAugmentedAutofillOnly) { + if (AutofillFeatureFlags.isMultipleFillEventHistoryEnabled() && !forAugmentedAutofillOnly) { mFillHistories.put(newSession.id, new FillEventHistory(sessionId, null)); } @@ -747,6 +755,7 @@ final class AutofillManagerServiceImpl Slog.d(TAG, "restarting session " + sessionId + " due to manual request on " + autofillId); } + mActiveSessionId = sessionId; return true; } if (sVerbose) { @@ -756,6 +765,8 @@ final class AutofillManagerServiceImpl return false; } + + mActiveSessionId = sessionId; session.updateLocked(autofillId, virtualBounds, value, action, flags); return false; } @@ -772,7 +783,8 @@ final class AutofillManagerServiceImpl FillEventHistory history = null; - if (Flags.multipleFillHistory() && mFillHistories != null) { + if (AutofillFeatureFlags.isMultipleFillEventHistoryEnabled() + && mFillHistories != null) { history = mFillHistories.get(sessionId); mFillHistories.delete(sessionId); } @@ -874,21 +886,54 @@ final class AutofillManagerServiceImpl } @GuardedBy("mLock") + public void notifyImeAnimationStart() { + if (!isEnabledLocked()) { + Slog.wtf(TAG, "Service not enabled"); + return; + } + final Session session = mSessions.get(mActiveSessionId); + if (session == null) { + Slog.v(TAG, "notifyImeAnimationEnd(): no session for " + mActiveSessionId); + return; + } + session.notifyImeAnimationStart(SystemClock.elapsedRealtime()); + } + + @GuardedBy("mLock") public void notifyImeAnimationEnd(int sessionId, long endTimeMs, int uid) { if (!isEnabledLocked()) { Slog.wtf(TAG, "Service not enabled"); return; } final Session session = mSessions.get(sessionId); - if (session == null || uid != session.uid) { + if (session == null) { Slog.v(TAG, "notifyImeAnimationEnd(): no session for " + sessionId + "(" + uid + ")"); return; } + if (uid != session.uid) { + Slog.v(TAG, "notifyImeAnimationEnd(): Mismatched session id's " + + sessionId + "(" + uid + ")"); + return; + } session.notifyImeAnimationEnd(endTimeMs); } @GuardedBy("mLock") + public void notifyImeAnimationEnd() { + if (!isEnabledLocked()) { + Slog.wtf(TAG, "Service not enabled"); + return; + } + final Session session = mSessions.get(mActiveSessionId); + if (session == null) { + Slog.v(TAG, "notifyImeAnimationEnd(): no session for " + mActiveSessionId); + return; + } + session.notifyImeAnimationEnd(SystemClock.elapsedRealtime()); + } + + @GuardedBy("mLock") @Override // from PerUserSystemService protected void handlePackageUpdateLocked(@NonNull String packageName) { final ServiceInfo serviceInfo = mFieldClassificationStrategy.getServiceInfo(); @@ -922,7 +967,7 @@ final class AutofillManagerServiceImpl } } mSessions.clear(); - if (Flags.multipleFillHistory()) { + if (AutofillFeatureFlags.isMultipleFillEventHistoryEnabled()) { mFillHistories.clear(); } @@ -991,7 +1036,7 @@ final class AutofillManagerServiceImpl mEventHistory.addEvent(event); } - if (Flags.multipleFillHistory()) { + if (AutofillFeatureFlags.isMultipleFillEventHistoryEnabled()) { FillEventHistory history = mFillHistories.get(sessionId); if (history != null) { history.addEvent(event); @@ -1180,7 +1225,7 @@ final class AutofillManagerServiceImpl logViewEnteredForHistory(sessionId, clientState, mEventHistory, focusedId); } - if (Flags.multipleFillHistory()) { + if (AutofillFeatureFlags.isMultipleFillEventHistoryEnabled()) { FillEventHistory history = mFillHistories.get(sessionId); if (history != null) { logViewEnteredForHistory(sessionId, clientState, history, focusedId); diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 3ecff3b3ebae..6fdb2b6b83f7 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -442,6 +442,9 @@ final class Session private Bundle mClientState; @GuardedBy("mLock") + private Bundle mClientStateForSecondary; + + @GuardedBy("mLock") boolean mDestroyed; /** @@ -980,13 +983,19 @@ final class Session mergePreviousSessionLocked(/* forSave= */ false); final List<String> hints = getTypeHintsForProvider(); + Bundle sendBackClientState = mClientState; + if (Flags.multipleFillHistory() + && mRequestId.isSecondaryProvider(requestId)) { + sendBackClientState = mClientStateForSecondary; + } + mDelayedFillPendingIntent = createPendingIntent(requestId); request = new FillRequest( requestId, contexts, hints, - mClientState, + sendBackClientState, flags, /* inlineSuggestionsRequest= */ null, /* delayedFillIntentSender= */ mDelayedFillPendingIntent == null @@ -3317,7 +3326,7 @@ final class Session AUTHENTICATION_RESULT_SUCCESS); if (newClientState != null) { if (sDebug) Slog.d(TAG, "Updating client state from auth dataset"); - mClientState = newClientState; + setClientState(newClientState, requestId); } Dataset datasetFromResult = getEffectiveDatasetForAuthentication((Dataset) result); final Dataset oldDataset = authenticatedResponse.getDatasets().get(datasetIdx); @@ -6700,6 +6709,18 @@ final class Session } @GuardedBy("mLock") + private void setClientState(@Nullable Bundle newClientState, int requestId) { + if (Flags.multipleFillHistory() + && mRequestId.isSecondaryProvider(requestId)) { + // Set the secondary clientstate + mClientStateForSecondary = newClientState; + } else { + // The old way - only set the primary provider clientstate + mClientState = newClientState; + } + } + + @GuardedBy("mLock") private void processResponseLocked( @NonNull FillResponse newResponse, @Nullable Bundle newClientState, int flags) { // Make sure we are hiding the UI which will be shown @@ -6734,7 +6755,9 @@ final class Session mResponses = new SparseArray<>(2); } mResponses.put(requestId, newResponse); - mClientState = newClientState != null ? newClientState : newResponse.getClientState(); + + setClientState(newClientState != null ? newClientState : newResponse.getClientState(), + requestId); boolean webviewRequestedCredman = newClientState != null diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index a37b2b926c9c..02a8f6218468 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -612,7 +612,7 @@ public class CompanionDeviceManagerService extends SystemService { @Override public void enablePermissionsSync(int associationId) { - if (UserHandle.getAppId(Binder.getCallingUid()) == SYSTEM_UID) { + if (UserHandle.getAppId(Binder.getCallingUid()) != SYSTEM_UID) { throw new SecurityException("Caller must be system UID"); } mSystemDataTransferProcessor.enablePermissionsSync(associationId); @@ -620,7 +620,7 @@ public class CompanionDeviceManagerService extends SystemService { @Override public void disablePermissionsSync(int associationId) { - if (UserHandle.getAppId(Binder.getCallingUid()) == SYSTEM_UID) { + if (UserHandle.getAppId(Binder.getCallingUid()) != SYSTEM_UID) { throw new SecurityException("Caller must be system UID"); } mSystemDataTransferProcessor.disablePermissionsSync(associationId); @@ -628,7 +628,7 @@ public class CompanionDeviceManagerService extends SystemService { @Override public PermissionSyncRequest getPermissionSyncRequest(int associationId) { - if (UserHandle.getAppId(Binder.getCallingUid()) == SYSTEM_UID) { + if (UserHandle.getAppId(Binder.getCallingUid()) != SYSTEM_UID) { throw new SecurityException("Caller must be system UID"); } return mSystemDataTransferProcessor.getPermissionSyncRequest(associationId); @@ -704,7 +704,7 @@ public class CompanionDeviceManagerService extends SystemService { @Override public byte[] getBackupPayload(int userId) { - if (UserHandle.getAppId(Binder.getCallingUid()) == SYSTEM_UID) { + if (UserHandle.getAppId(Binder.getCallingUid()) != SYSTEM_UID) { throw new SecurityException("Caller must be system"); } return mBackupRestoreProcessor.getBackupPayload(userId); @@ -712,7 +712,7 @@ public class CompanionDeviceManagerService extends SystemService { @Override public void applyRestoredPayload(byte[] payload, int userId) { - if (UserHandle.getAppId(Binder.getCallingUid()) == SYSTEM_UID) { + if (UserHandle.getAppId(Binder.getCallingUid()) != SYSTEM_UID) { throw new SecurityException("Caller must be system"); } mBackupRestoreProcessor.applyRestoredPayload(payload, userId); diff --git a/services/core/Android.bp b/services/core/Android.bp index bb493370f9fc..420dcfe9cea6 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -143,6 +143,7 @@ java_library_static { ":platform-compat-overrides", ":display-device-config", ":display-layout-config", + ":display-topology", ":device-state-config", "java/com/android/server/EventLogTags.logtags", "java/com/android/server/am/EventLogTags.logtags", diff --git a/services/core/java/com/android/server/MasterClearReceiver.java b/services/core/java/com/android/server/MasterClearReceiver.java index 2b30c013235c..6f2ecd82a79c 100644 --- a/services/core/java/com/android/server/MasterClearReceiver.java +++ b/services/core/java/com/android/server/MasterClearReceiver.java @@ -130,7 +130,7 @@ public class MasterClearReceiver extends BroadcastReceiver { if (mWipeExternalStorage) { // thr will be started at the end of this task. Slog.i(TAG, "Wiping external storage on async task"); - new WipeDataTask(context, thr).execute(); + new WipeDataTask(context, thr).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } else { Slog.i(TAG, "NOT wiping external storage; starting thread " + thr.getName()); thr.start(); diff --git a/services/core/java/com/android/server/am/BroadcastController.java b/services/core/java/com/android/server/am/BroadcastController.java index bfacfbba4e22..bec5db79ff9d 100644 --- a/services/core/java/com/android/server/am/BroadcastController.java +++ b/services/core/java/com/android/server/am/BroadcastController.java @@ -317,7 +317,7 @@ class BroadcastController { Slog.w(TAG, "registerReceiverWithFeature: no app for " + caller); return null; } - if (callerApp.info.uid != SYSTEM_UID + if (!UserHandle.isCore(callerApp.info.uid) && !callerApp.getPkgList().containsKey(callerPackage)) { throw new SecurityException("Given caller package " + callerPackage + " is not running in process " + callerApp); diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java index 0b7890167c08..3817ba1a28b9 100644 --- a/services/core/java/com/android/server/am/PendingIntentRecord.java +++ b/services/core/java/com/android/server/am/PendingIntentRecord.java @@ -305,10 +305,6 @@ public final class PendingIntentRecord extends IIntentSender.Stub { this.stringName = null; } - @VisibleForTesting TempAllowListDuration getAllowlistDurationLocked(IBinder allowlistToken) { - return mAllowlistDuration.get(allowlistToken); - } - void setAllowBgActivityStarts(IBinder token, int flags) { if (token == null) return; if ((flags & FLAG_ACTIVITY_SENDER) != 0) { @@ -327,12 +323,6 @@ public final class PendingIntentRecord extends IIntentSender.Stub { mAllowBgActivityStartsForActivitySender.remove(token); mAllowBgActivityStartsForBroadcastSender.remove(token); mAllowBgActivityStartsForServiceSender.remove(token); - if (mAllowlistDuration != null) { - mAllowlistDuration.remove(token); - if (mAllowlistDuration.isEmpty()) { - mAllowlistDuration = null; - } - } } public void registerCancelListenerLocked(IResultReceiver receiver) { @@ -713,7 +703,7 @@ public final class PendingIntentRecord extends IIntentSender.Stub { return res; } - @VisibleForTesting BackgroundStartPrivileges getBackgroundStartPrivilegesForActivitySender( + private BackgroundStartPrivileges getBackgroundStartPrivilegesForActivitySender( IBinder allowlistToken) { return mAllowBgActivityStartsForActivitySender.contains(allowlistToken) ? BackgroundStartPrivileges.allowBackgroundActivityStarts(allowlistToken) diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java index a54a66312b1d..5184a2c4ec30 100644 --- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java +++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java @@ -111,6 +111,7 @@ public class SettingsToPropertiesMapper { DeviceConfig.NAMESPACE_LMKD_NATIVE, DeviceConfig.NAMESPACE_MEDIA_NATIVE, DeviceConfig.NAMESPACE_MGLRU_NATIVE, + DeviceConfig.NAMESPACE_MMD_NATIVE, DeviceConfig.NAMESPACE_NETD_NATIVE, DeviceConfig.NAMESPACE_NNAPI_NATIVE, DeviceConfig.NAMESPACE_PROFCOLLECT_NATIVE_BOOT, diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 8e09e3b8e112..833599810210 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -604,7 +604,7 @@ public class AppOpsService extends IAppOpsService.Stub { } } - /** Returned from {@link #verifyAndGetBypass(int, String, String, String, boolean)}. */ + /** Returned from {@link #verifyAndGetBypass(int, String, String, int, String, boolean)}. */ private static final class PackageVerificationResult { final RestrictionBypass bypass; @@ -3087,10 +3087,10 @@ public class AppOpsService extends IAppOpsService.Stub { public int checkPackage(int uid, String packageName) { Objects.requireNonNull(packageName); try { - verifyAndGetBypass(uid, packageName, null, null, true); + verifyAndGetBypass(uid, packageName, null, Process.INVALID_UID, null, true); // When the caller is the system, it's possible that the packageName is the special // one (e.g., "root") which isn't actually existed. - if (resolveUid(packageName) == uid + if (resolveNonAppUid(packageName) == uid || (isPackageExisted(packageName) && !filterAppAccessUnlocked(packageName, UserHandle.getUserId(uid)))) { return AppOpsManager.MODE_ALLOWED; @@ -3306,7 +3306,7 @@ public class AppOpsService extends IAppOpsService.Stub { boolean shouldCollectMessage, int notedCount) { PackageVerificationResult pvr; try { - pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName); + pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyUid, proxyPackageName); if (!pvr.isAttributionTagValid) { attributionTag = null; } @@ -3930,7 +3930,7 @@ public class AppOpsService extends IAppOpsService.Stub { // Test if the proxied operation will succeed before starting the proxy operation final SyncNotedAppOp testProxiedOp = startOperationDryRun(code, proxiedUid, resolvedProxiedPackageName, proxiedAttributionTag, - proxiedVirtualDeviceId, resolvedProxyPackageName, proxiedFlags, + proxiedVirtualDeviceId, proxyUid, resolvedProxyPackageName, proxiedFlags, startIfModeDefault); if (!shouldStartForMode(testProxiedOp.getOpMode(), startIfModeDefault)) { @@ -3970,7 +3970,7 @@ public class AppOpsService extends IAppOpsService.Stub { int attributionChainId) { PackageVerificationResult pvr; try { - pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName); + pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyUid, proxyPackageName); if (!pvr.isAttributionTagValid) { attributionTag = null; } @@ -4097,11 +4097,11 @@ public class AppOpsService extends IAppOpsService.Stub { */ private SyncNotedAppOp startOperationDryRun(int code, int uid, @NonNull String packageName, @Nullable String attributionTag, int virtualDeviceId, - String proxyPackageName, @OpFlags int flags, + int proxyUid, String proxyPackageName, @OpFlags int flags, boolean startIfModeDefault) { PackageVerificationResult pvr; try { - pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName); + pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyUid, proxyPackageName); if (!pvr.isAttributionTagValid) { attributionTag = null; } @@ -4656,13 +4656,17 @@ public class AppOpsService extends IAppOpsService.Stub { private boolean isSpecialPackage(int callingUid, @Nullable String packageName) { final String resolvedPackage = AppOpsManager.resolvePackageName(callingUid, packageName); return callingUid == Process.SYSTEM_UID - || resolveUid(resolvedPackage) != Process.INVALID_UID; + || resolveNonAppUid(resolvedPackage) != Process.INVALID_UID; } private boolean isCallerAndAttributionTrusted(@NonNull AttributionSource attributionSource) { if (attributionSource.getUid() != Binder.getCallingUid() && attributionSource.isTrusted(mContext)) { - return true; + // if there is a next attribution source, it must be trusted, as well. + if (attributionSource.getNext() == null + || attributionSource.getNext().isTrusted(mContext)) { + return true; + } } return mContext.checkPermission(android.Manifest.permission.UPDATE_APP_OPS_STATS, Binder.getCallingPid(), Binder.getCallingUid(), null) @@ -4757,19 +4761,20 @@ public class AppOpsService extends IAppOpsService.Stub { } /** - * @see #verifyAndGetBypass(int, String, String, String, boolean) + * @see #verifyAndGetBypass(int, String, String, int, String, boolean) */ private @NonNull PackageVerificationResult verifyAndGetBypass(int uid, String packageName, @Nullable String attributionTag) { - return verifyAndGetBypass(uid, packageName, attributionTag, null); + return verifyAndGetBypass(uid, packageName, attributionTag, Process.INVALID_UID, null); } /** - * @see #verifyAndGetBypass(int, String, String, String, boolean) + * @see #verifyAndGetBypass(int, String, String, int, String, boolean) */ private @NonNull PackageVerificationResult verifyAndGetBypass(int uid, String packageName, - @Nullable String attributionTag, @Nullable String proxyPackageName) { - return verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName, false); + @Nullable String attributionTag, int proxyUid, @Nullable String proxyPackageName) { + return verifyAndGetBypass(uid, packageName, attributionTag, proxyUid, proxyPackageName, + false); } /** @@ -4780,14 +4785,15 @@ public class AppOpsService extends IAppOpsService.Stub { * @param uid The uid the package belongs to * @param packageName The package the might belong to the uid * @param attributionTag attribution tag or {@code null} if no need to verify - * @param proxyPackageName The proxy package, from which the attribution tag is to be pulled + * @param proxyUid The proxy uid, from which the attribution tag is to be pulled + * @param proxyPackageName The proxy package, from which the attribution tag may be pulled * @param suppressErrorLogs Whether to print to logcat about nonmatching parameters * * @return PackageVerificationResult containing {@link RestrictionBypass} and whether the * attribution tag is valid */ private @NonNull PackageVerificationResult verifyAndGetBypass(int uid, String packageName, - @Nullable String attributionTag, @Nullable String proxyPackageName, + @Nullable String attributionTag, int proxyUid, @Nullable String proxyPackageName, boolean suppressErrorLogs) { if (uid == Process.ROOT_UID) { // For backwards compatibility, don't check package name for root UID. @@ -4831,34 +4837,47 @@ public class AppOpsService extends IAppOpsService.Stub { int callingUid = Binder.getCallingUid(); - // Allow any attribution tag for resolvable uids - int pkgUid; + // Allow any attribution tag for resolvable, non-app uids + int nonAppUid; if (Objects.equals(packageName, "com.android.shell")) { // Special case for the shell which is a package but should be able // to bypass app attribution tag restrictions. - pkgUid = Process.SHELL_UID; + nonAppUid = Process.SHELL_UID; } else { - pkgUid = resolveUid(packageName); + nonAppUid = resolveNonAppUid(packageName); } - if (pkgUid != Process.INVALID_UID) { - if (pkgUid != UserHandle.getAppId(uid)) { + if (nonAppUid != Process.INVALID_UID) { + if (nonAppUid != UserHandle.getAppId(uid)) { if (!suppressErrorLogs) { Slog.e(TAG, "Bad call made by uid " + callingUid + ". " - + "Package \"" + packageName + "\" does not belong to uid " + uid - + "."); + + "Package \"" + packageName + "\" does not belong to uid " + uid + + "."); + } + String otherUidMessage = + DEBUG ? " but it is really " + nonAppUid : " but it is not"; + throw new SecurityException("Specified package \"" + packageName + + "\" under uid " + UserHandle.getAppId(uid) + otherUidMessage); + } + // We only allow bypassing the attribution tag verification if the proxy is a + // system app (or is null), in order to prevent abusive apps clogging the appops + // system with unlimited attribution tags via proxy calls. + boolean proxyIsSystemAppOrNull = true; + if (proxyPackageName != null) { + int proxyAppId = UserHandle.getAppId(proxyUid); + if (proxyAppId >= Process.FIRST_APPLICATION_UID) { + proxyIsSystemAppOrNull = + mPackageManagerInternal.isSystemPackage(proxyPackageName); } - String otherUidMessage = DEBUG ? " but it is really " + pkgUid : " but it is not"; - throw new SecurityException("Specified package \"" + packageName + "\" under uid " - + UserHandle.getAppId(uid) + otherUidMessage); } return new PackageVerificationResult(RestrictionBypass.UNRESTRICTED, - /* isAttributionTagValid */ true); + /* isAttributionTagValid */ proxyIsSystemAppOrNull); } int userId = UserHandle.getUserId(uid); RestrictionBypass bypass = null; boolean isAttributionTagValid = false; + int pkgUid = nonAppUid; final long ident = Binder.clearCallingIdentity(); try { PackageManagerInternal pmInt = LocalServices.getService(PackageManagerInternal.class); @@ -5649,7 +5668,7 @@ public class AppOpsService extends IAppOpsService.Stub { if (nonpackageUid != -1) { packageName = null; } else { - packageUid = resolveUid(packageName); + packageUid = resolveNonAppUid(packageName); if (packageUid < 0) { packageUid = AppGlobals.getPackageManager().getPackageUid(packageName, PackageManager.MATCH_UNINSTALLED_PACKAGES, userId); @@ -6749,7 +6768,13 @@ public class AppOpsService extends IAppOpsService.Stub { if (restricted && attrOp.isRunning()) { attrOp.pause(); } else if (attrOp.isPaused()) { - attrOp.resume(); + RestrictionBypass bypass = verifyAndGetBypass(uid, ops.packageName, attrOp.tag) + .bypass; + if (!isOpRestrictedLocked(uid, code, ops.packageName, attrOp.tag, + Context.DEVICE_ID_DEFAULT, bypass, false)) { + // Only resume if there are no other restrictions remaining on this op + attrOp.resume(); + } } } } @@ -7198,7 +7223,7 @@ public class AppOpsService extends IAppOpsService.Stub { } } - private static int resolveUid(String packageName) { + private static int resolveNonAppUid(String packageName) { if (packageName == null) { return Process.INVALID_UID; } diff --git a/services/core/java/com/android/server/audio/AudioManagerShellCommand.java b/services/core/java/com/android/server/audio/AudioManagerShellCommand.java index 030ce12f5063..fece7a899f0a 100644 --- a/services/core/java/com/android/server/audio/AudioManagerShellCommand.java +++ b/services/core/java/com/android/server/audio/AudioManagerShellCommand.java @@ -65,6 +65,12 @@ class AudioManagerShellCommand extends ShellCommand { return setRingerMode(); case "set-volume": return setVolume(); + case "get-min-volume": + return getMinVolume(); + case "get-max-volume": + return getMaxVolume(); + case "get-stream-volume": + return getStreamVolume(); case "set-device-volume": return setDeviceVolume(); case "adj-mute": @@ -106,6 +112,12 @@ class AudioManagerShellCommand extends ShellCommand { pw.println(" Sets the Ringer mode to one of NORMAL|SILENT|VIBRATE"); pw.println(" set-volume STREAM_TYPE VOLUME_INDEX"); pw.println(" Sets the volume for STREAM_TYPE to VOLUME_INDEX"); + pw.println(" get-min-volume STREAM_TYPE"); + pw.println(" Gets the min volume for STREAM_TYPE"); + pw.println(" get-max-volume STREAM_TYPE"); + pw.println(" Gets the max volume for STREAM_TYPE"); + pw.println(" get-stream-volume STREAM_TYPE"); + pw.println(" Gets the volume for STREAM_TYPE"); pw.println(" set-device-volume STREAM_TYPE VOLUME_INDEX NATIVE_DEVICE_TYPE"); pw.println(" Sets for NATIVE_DEVICE_TYPE the STREAM_TYPE volume to VOLUME_INDEX"); pw.println(" adj-mute STREAM_TYPE"); @@ -296,6 +308,33 @@ class AudioManagerShellCommand extends ShellCommand { return 0; } + private int getMinVolume() { + final Context context = mService.mContext; + final AudioManager am = context.getSystemService(AudioManager.class); + final int stream = readIntArg(); + final int result = am.getStreamMinVolume(stream); + getOutPrintWriter().println("AudioManager.getStreamMinVolume(" + stream + ") -> " + result); + return 0; + } + + private int getMaxVolume() { + final Context context = mService.mContext; + final AudioManager am = context.getSystemService(AudioManager.class); + final int stream = readIntArg(); + final int result = am.getStreamMaxVolume(stream); + getOutPrintWriter().println("AudioManager.getStreamMaxVolume(" + stream + ") -> " + result); + return 0; + } + + private int getStreamVolume() { + final Context context = mService.mContext; + final AudioManager am = context.getSystemService(AudioManager.class); + final int stream = readIntArg(); + final int result = am.getStreamVolume(stream); + getOutPrintWriter().println("AudioManager.getStreamVolume(" + stream + ") -> " + result); + return 0; + } + private int setDeviceVolume() { final Context context = mService.mContext; final AudioDeviceVolumeManager advm = (AudioDeviceVolumeManager) context.getSystemService( diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 12b666fc458a..320dd8f188c0 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -47,9 +47,10 @@ import static android.media.AudioManager.RINGER_MODE_NORMAL; import static android.media.AudioManager.RINGER_MODE_SILENT; import static android.media.AudioManager.RINGER_MODE_VIBRATE; import static android.media.AudioManager.STREAM_SYSTEM; -import static android.media.IAudioManagerNative.HardeningType; import static android.media.audio.Flags.autoPublicVolumeApiHardening; import static android.media.audio.Flags.automaticBtDeviceType; +import static android.media.audio.Flags.cacheGetStreamMinMaxVolume; +import static android.media.audio.Flags.cacheGetStreamVolume; import static android.media.audio.Flags.concurrentAudioRecordBypassPermission; import static android.media.audio.Flags.featureSpatialAudioHeadtrackingLowLatency; import static android.media.audio.Flags.focusFreezeTestApi; @@ -68,6 +69,7 @@ import static com.android.media.audio.Flags.alarmMinVolumeZero; import static com.android.media.audio.Flags.asDeviceConnectionFailure; import static com.android.media.audio.Flags.audioserverPermissions; import static com.android.media.audio.Flags.disablePrescaleAbsoluteVolume; +import static com.android.media.audio.Flags.deferWearPermissionUpdates; import static com.android.media.audio.Flags.equalScoLeaVcIndexRange; import static com.android.media.audio.Flags.replaceStreamBtSco; import static com.android.media.audio.Flags.ringMyCar; @@ -514,6 +516,15 @@ public class AudioService extends IAudioService.Stub // check playback or record activity every 6 seconds for UIDs owning mode IN_COMMUNICATION private static final int CHECK_MODE_FOR_UID_PERIOD_MS = 6000; + // Roughly chosen to be long enough to suppress the autocork behavior of the permission + // cache (50ms), while not introducing visible permission leaks - since the app needs to + // restart, and trigger an action which requires permissions from audioserver before this + // delay. For RECORD_AUDIO, we are additionally protected by appops. + private static final int SCHEDULED_PERMISSION_UPDATE_DELAY_MS = 60; + + // Increased delay to not interefere with low core app launch latency + private static final int SCHEDULED_PERMISSION_UPDATE_LONG_DELAY_MS = 500; + /** @see AudioSystemThread */ private AudioSystemThread mAudioSystemThread; /** @see AudioHandler */ @@ -888,6 +899,16 @@ public class AudioService extends IAudioService.Stub public void permissionUpdateBarrier() { AudioService.this.permissionUpdateBarrier(); } + + /** + * Update mute state event for port + * @param portId Port id to update + * @param event the mute event containing info about the mute + */ + @Override + public void portMuteEvent(int portId, int event) { + mPlaybackMonitor.portMuteEvent(portId, event, Binder.getCallingUid()); + } }; // List of binder death handlers for setMode() client processes. @@ -1890,6 +1911,12 @@ public class AudioService extends IAudioService.Stub mSpatializerHelper.onRoutingUpdated(); } checkMuteAwaitConnection(); + if (cacheGetStreamVolume()) { + if (DEBUG_VOL) { + Log.d(TAG, "Clear volume cache after routing update"); + } + AudioManager.clearVolumeCache(AudioManager.VOLUME_CACHING_API); + } } //----------------------------------------------------------------- @@ -4966,6 +4993,10 @@ public class AudioService extends IAudioService.Stub + ringMyCar()); pw.println("\tandroid.media.audio.Flags.concurrentAudioRecordBypassPermission:" + concurrentAudioRecordBypassPermission()); + pw.println("\tandroid.media.audio.Flags.cacheGetStreamMinMaxVolume:" + + cacheGetStreamMinMaxVolume()); + pw.println("\tandroid.media.audio.Flags.cacheGetStreamVolume:" + + cacheGetStreamVolume()); } private void dumpAudioMode(PrintWriter pw) { @@ -7032,6 +7063,13 @@ public class AudioService extends IAudioService.Stub streamState.mIsMuted = false; } } + if (cacheGetStreamVolume()) { + if (DEBUG_VOL) { + Log.d(TAG, + "Clear volume cache after possibly changing mute in readAudioSettings"); + } + AudioManager.clearVolumeCache(AudioManager.VOLUME_CACHING_API); + } } readVolumeGroupsSettings(userSwitch); @@ -9252,11 +9290,23 @@ public class AudioService extends IAudioService.Stub public void put(int key, int value) { super.put(key, value); record("put", key, value); + if (cacheGetStreamVolume()) { + if (DEBUG_VOL) { + Log.d(TAG, "Clear volume cache after update index map"); + } + AudioManager.clearVolumeCache(AudioManager.VOLUME_CACHING_API); + } } @Override public void setValueAt(int index, int value) { super.setValueAt(index, value); record("setValueAt", keyAt(index), value); + if (cacheGetStreamVolume()) { + if (DEBUG_VOL) { + Log.d(TAG, "Clear volume cache after update index map"); + } + AudioManager.clearVolumeCache(AudioManager.VOLUME_CACHING_API); + } } // Record all changes in the VolumeStreamState @@ -9356,6 +9406,12 @@ public class AudioService extends IAudioService.Stub mIndexMinNoPerm = mIndexMin; } } + if (cacheGetStreamMinMaxVolume() && mStreamType == AudioSystem.STREAM_VOICE_CALL) { + if (DEBUG_VOL) { + Log.d(TAG, "Clear min volume cache from updateIndexFactors"); + } + AudioManager.clearVolumeCache(AudioManager.VOLUME_MIN_CACHING_API); + } final int status = AudioSystem.initStreamVolume( mStreamType, indexMinVolCurve, indexMaxVolCurve); @@ -9393,11 +9449,19 @@ public class AudioService extends IAudioService.Stub * @param index minimum index expressed in "UI units", i.e. no 10x factor */ public void updateNoPermMinIndex(int index) { + boolean changedNoPermMinIndex = + cacheGetStreamMinMaxVolume() && (index * 10) != mIndexMinNoPerm; mIndexMinNoPerm = index * 10; if (mIndexMinNoPerm < mIndexMin) { Log.e(TAG, "Invalid mIndexMinNoPerm for stream " + mStreamType); mIndexMinNoPerm = mIndexMin; } + if (changedNoPermMinIndex) { + if (DEBUG_VOL) { + Log.d(TAG, "Clear min volume cache from updateNoPermMinIndex"); + } + AudioManager.clearVolumeCache(AudioManager.VOLUME_MIN_CACHING_API); + } } /** @@ -9972,8 +10036,9 @@ public class AudioService extends IAudioService.Stub * @return true if the mute state was changed */ public boolean mute(boolean state, boolean apply, String src) { + boolean changed; synchronized (VolumeStreamState.class) { - boolean changed = state != mIsMuted; + changed = state != mIsMuted; if (changed) { sMuteLogger.enqueue( new AudioServiceEvents.StreamMuteEvent(mStreamType, state, src)); @@ -9991,8 +10056,16 @@ public class AudioService extends IAudioService.Stub doMute(); } } - return changed; } + + if (cacheGetStreamVolume() && changed) { + if (DEBUG_VOL) { + Log.d(TAG, "Clear volume cache after changing mute state"); + } + AudioManager.clearVolumeCache(AudioManager.VOLUME_CACHING_API); + } + + return changed; } public void doMute() { @@ -10975,12 +11048,7 @@ public class AudioService extends IAudioService.Stub } /* Listen to permission invalidations for the PermissionProvider */ - private void setupPermissionListener() { - // Roughly chosen to be long enough to suppress the autocork behavior of the permission - // cache (50ms), while not introducing visible permission leaks - since the app needs to - // restart, and trigger an action which requires permissions from audioserver before this - // delay. For RECORD_AUDIO, we are additionally protected by appops. - final long UPDATE_DELAY_MS = 60; + private void setupPermissionListener() { // instanceof to simplify the construction requirements of AudioService for testing: no // delayed execution during unit tests. if (mAudioServerLifecycleExecutor instanceof ScheduledExecutorService exec) { @@ -11022,7 +11090,7 @@ public class AudioService extends IAudioService.Stub Thread.getDefaultUncaughtExceptionHandler() .uncaughtException(Thread.currentThread(), e); } - }, UPDATE_DELAY_MS, TimeUnit.MILLISECONDS)); + }, getAudioPermissionsDelay(), TimeUnit.MILLISECONDS)); } }; mSysPropListenerNativeHandle = mAudioSystem.listenForSystemPropertyChange( @@ -13029,10 +13097,10 @@ public class AudioService extends IAudioService.Stub int uid = intent.getIntExtra(Intent.EXTRA_UID, Process.INVALID_UID); if (intent.getBooleanExtra(EXTRA_REPLACING, false) || intent.getBooleanExtra(EXTRA_ARCHIVAL, false)) return; - if (action.equals(ACTION_PACKAGE_ADDED)) { + if (ACTION_PACKAGE_ADDED.equals(action)) { audioserverExecutor.execute(() -> provider.onModifyPackageState(uid, pkgName, false /* isRemoved */)); - } else if (action.equals(ACTION_PACKAGE_REMOVED)) { + } else if (ACTION_PACKAGE_REMOVED.equals(action)) { audioserverExecutor.execute(() -> provider.onModifyPackageState(uid, pkgName, true /* isRemoved */)); } @@ -15513,6 +15581,18 @@ public class AudioService extends IAudioService.Stub } } + private long getAudioPermissionsDelay() { + return isAudioPermissionUpdatesAddtionallyDelayed() + ? SCHEDULED_PERMISSION_UPDATE_LONG_DELAY_MS + : SCHEDULED_PERMISSION_UPDATE_DELAY_MS; + } + + private boolean isAudioPermissionUpdatesAddtionallyDelayed() { + // Additional delays on low core devices in order to optimize app launch latencies + return deferWearPermissionUpdates() + && mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH); + } + //==================== // Helper functions for app ops //==================== diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java index e2e06b63c7d6..57b5febf4df0 100644 --- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java +++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java @@ -435,6 +435,49 @@ public final class PlaybackActivityMonitor /** * Update event for port * @param portId Port id to update + * @param event the mute event containing info about the mute + * @param binderUid Calling binder uid + */ + public void portMuteEvent(int portId, @PlayerMuteEvent int event, int binderUid) { + if (!UserHandle.isCore(binderUid)) { + Log.e(TAG, "Forbidden operation from uid " + binderUid); + return; + } + + synchronized (mPlayerLock) { + int piid; + if (portToPiidSimplification()) { + int idxOfPiid = mPiidToPortId.indexOfValue(portId); + if (idxOfPiid < 0) { + Log.w(TAG, "No piid assigned for invalid/internal port id " + portId); + return; + } + piid = mPiidToPortId.keyAt(idxOfPiid); + } else { + piid = mPortIdToPiid.get(portId, PLAYER_PIID_INVALID); + if (piid == PLAYER_PIID_INVALID) { + Log.w(TAG, "No piid assigned for invalid/internal port id " + portId); + return; + } + } + final AudioPlaybackConfiguration apc = mPlayers.get(piid); + if (apc == null) { + Log.w(TAG, "No AudioPlaybackConfiguration assigned for piid " + piid); + return; + } + + if (apc.getPlayerType() + == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL) { + // FIXME SoundPool not ready for state reporting + return; + } + mEventHandler.sendMessage( + mEventHandler.obtainMessage(MSG_IIL_UPDATE_PLAYER_MUTED_EVENT, piid, event, null)); + } + } + /** + * Update event for port + * @param portId Port id to update * @param event The new port event * @param extras The values associated with this event * @param binderUid Calling binder uid @@ -479,15 +522,10 @@ public final class PlaybackActivityMonitor return; } - if (event == AudioPlaybackConfiguration.PLAYER_UPDATE_MUTED) { - mEventHandler.sendMessage( - mEventHandler.obtainMessage(MSG_IIL_UPDATE_PLAYER_MUTED_EVENT, piid, - portId, - extras)); - } else if (event == AudioPlaybackConfiguration.PLAYER_UPDATE_FORMAT) { + if (event == AudioPlaybackConfiguration.PLAYER_UPDATE_FORMAT) { mEventHandler.sendMessage( mEventHandler.obtainMessage(MSG_IIL_UPDATE_PLAYER_FORMAT, piid, - portId, + -1, extras)); } } @@ -1695,9 +1733,7 @@ public final class PlaybackActivityMonitor * event for player getting muted * args: * msg.arg1: piid - * msg.arg2: port id - * msg.obj: extras describing the mute reason - * type: PersistableBundle + * msg.arg2: mute reason */ private static final int MSG_IIL_UPDATE_PLAYER_MUTED_EVENT = 2; @@ -1705,7 +1741,6 @@ public final class PlaybackActivityMonitor * event for player reporting playback format and spatialization status * args: * msg.arg1: piid - * msg.arg2: port id * msg.obj: extras describing the sample rate, channel mask, spatialized * type: PersistableBundle */ @@ -1729,17 +1764,9 @@ public final class PlaybackActivityMonitor break; case MSG_IIL_UPDATE_PLAYER_MUTED_EVENT: - // TODO: replace PersistableBundle with own struct - PersistableBundle extras = (PersistableBundle) msg.obj; - if (extras == null) { - Log.w(TAG, "Received mute event with no extras"); - break; - } - @PlayerMuteEvent int eventValue = extras.getInt(EXTRA_PLAYER_EVENT_MUTE); - synchronized (mPlayerLock) { int piid = msg.arg1; - + @PlayerMuteEvent int eventValue = msg.arg2; int[] eventValues = new int[1]; eventValues[0] = eventValue; diff --git a/services/core/java/com/android/server/backup/SystemBackupAgent.java b/services/core/java/com/android/server/backup/SystemBackupAgent.java index f66c7e115fc0..677e0c055455 100644 --- a/services/core/java/com/android/server/backup/SystemBackupAgent.java +++ b/services/core/java/com/android/server/backup/SystemBackupAgent.java @@ -35,7 +35,7 @@ import android.os.UserHandle; import android.os.UserManager; import android.util.Slog; -import com.android.server.backup.Flags; +import com.android.server.display.DisplayBackupHelper; import com.android.server.notification.NotificationBackupHelper; import com.google.android.collect.Sets; @@ -67,6 +67,7 @@ public class SystemBackupAgent extends BackupAgentHelper { private static final String APP_GENDER_HELPER = "app_gender"; private static final String COMPANION_HELPER = "companion"; private static final String SYSTEM_GENDER_HELPER = "system_gender"; + private static final String DISPLAY_HELPER = "display"; // These paths must match what the WallpaperManagerService uses. The leaf *_FILENAME // are also used in the full-backup file format, so must not change unless steps are @@ -104,7 +105,8 @@ public class SystemBackupAgent extends BackupAgentHelper { APP_LOCALES_HELPER, COMPANION_HELPER, APP_GENDER_HELPER, - SYSTEM_GENDER_HELPER); + SYSTEM_GENDER_HELPER, + DISPLAY_HELPER); /** Helpers that are enabled for full, non-system users. */ private static final Set<String> sEligibleHelpersForNonSystemUser = @@ -146,6 +148,7 @@ public class SystemBackupAgent extends BackupAgentHelper { addHelperIfEligibleForUser(COMPANION_HELPER, new CompanionBackupHelper(mUserId)); addHelperIfEligibleForUser(SYSTEM_GENDER_HELPER, new SystemGrammaticalGenderBackupHelper(mUserId)); + addHelperIfEligibleForUser(DISPLAY_HELPER, new DisplayBackupHelper(mUserId)); } @Override diff --git a/services/core/java/com/android/server/display/DisplayBackupHelper.java b/services/core/java/com/android/server/display/DisplayBackupHelper.java new file mode 100644 index 000000000000..0d3a09fb2305 --- /dev/null +++ b/services/core/java/com/android/server/display/DisplayBackupHelper.java @@ -0,0 +1,137 @@ +/* + * 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.display; + + +import android.annotation.Nullable; +import android.app.backup.BlobBackupHelper; +import android.hardware.display.DisplayManagerInternal; +import android.util.AtomicFile; +import android.util.AtomicFileOutputStream; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.LocalServices; +import com.android.server.display.feature.DisplayManagerFlags; +import com.android.server.display.utils.DebugUtils; + +import java.io.IOException; + +/** + * Display manager specific information backup helper. Backs-up the entire files for the given + * user. + * @hide + */ +public class DisplayBackupHelper extends BlobBackupHelper { + private static final String TAG = "DisplayBackupHelper"; + + // current schema of the backup state blob + private static final int BLOB_VERSION = 1; + + // key under which the data blob is committed to back up + private static final String KEY_DISPLAY = "display"; + + // To enable these logs, run: + // adb shell setprop persist.log.tag.DisplayBackupHelper DEBUG + // adb reboot + private static final boolean DEBUG = DebugUtils.isDebuggable(TAG); + + private final int mUserId; + private final Injector mInjector; + + /** + * Construct a helper to manage backup/restore of entire files within Display Manager. + * + * @param userId id of the user for which backup will be done. + */ + public DisplayBackupHelper(int userId) { + this(userId, new Injector()); + } + + @VisibleForTesting + DisplayBackupHelper(int userId, Injector injector) { + super(BLOB_VERSION, KEY_DISPLAY); + mUserId = userId; + mInjector = injector; + } + + @Override + protected byte[] getBackupPayload(String key) { + if (!KEY_DISPLAY.equals(key) || !mInjector.isDisplayTopologyFlagEnabled()) { + return null; + } + try { + var result = mInjector.readTopologyFile(mUserId); + Slog.i(TAG, "getBackupPayload for " + key + " done, size=" + result.length); + return result; + } catch (IOException e) { + if (DEBUG) Slog.d(TAG, "Skip topology backup", e); + return null; + } + } + + @Override + protected void applyRestoredPayload(String key, byte[] payload) { + if (!KEY_DISPLAY.equals(key) || !mInjector.isDisplayTopologyFlagEnabled()) { + return; + } + try (var oStream = mInjector.writeTopologyFile(mUserId)) { + oStream.write(payload); + oStream.markSuccess(); + Slog.i(TAG, "applyRestoredPayload for " + key + " size=" + payload.length + + " to " + oStream); + } catch (IOException e) { + Slog.e(TAG, "applyRestoredPayload failed", e); + return; + } + var displayManagerInternal = mInjector.getDisplayManagerInternal(); + if (displayManagerInternal == null) { + Slog.e(TAG, "DisplayManagerInternal is null"); + return; + } + + displayManagerInternal.reloadTopologies(mUserId); + } + + @VisibleForTesting + static class Injector { + private final boolean mIsDisplayTopologyEnabled = + new DisplayManagerFlags().isDisplayTopologyEnabled(); + + boolean isDisplayTopologyFlagEnabled() { + return mIsDisplayTopologyEnabled; + } + + @Nullable + DisplayManagerInternal getDisplayManagerInternal() { + return LocalServices.getService(DisplayManagerInternal.class); + } + + byte[] readTopologyFile(int userId) throws IOException { + return getTopologyFile(userId).readFully(); + } + + AtomicFileOutputStream writeTopologyFile(int userId) throws IOException { + return new AtomicFileOutputStream(getTopologyFile(userId)); + } + + private AtomicFile getTopologyFile(int userId) { + return new AtomicFile(DisplayTopologyXmlStore.getUserTopologyFile(userId), + /*commitTag=*/ "topology-state"); + } + }; +} diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 3598b9b2e879..49b451a83efa 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -37,10 +37,12 @@ import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_CAN_S import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_DEVICE_DISPLAY_GROUP; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP; +import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_FOCUS; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS; +import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_STEAL_TOP_FOCUS_DISABLED; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED; import static android.hardware.display.DisplayManagerGlobal.DisplayEvent; import static android.hardware.display.DisplayViewport.VIEWPORT_EXTERNAL; @@ -70,6 +72,7 @@ import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.AppOpsManager; +import android.app.backup.BackupManager; import android.app.compat.CompatChanges; import android.companion.virtual.IVirtualDevice; import android.companion.virtual.VirtualDeviceManager; @@ -676,9 +679,10 @@ public final class DisplayManagerService extends SystemService { mExternalDisplayStatsService); mExternalDisplayPolicy = new ExternalDisplayPolicy(new ExternalDisplayPolicyInjector()); if (mFlags.isDisplayTopologyEnabled()) { - mDisplayTopologyCoordinator = - new DisplayTopologyCoordinator(this::isExtendedDisplayEnabled, - this::deliverTopologyUpdate, new HandlerExecutor(mHandler), mSyncRoot); + final var backupManager = new BackupManager(mContext); + mDisplayTopologyCoordinator = new DisplayTopologyCoordinator( + this::isExtendedDisplayEnabled, this::deliverTopologyUpdate, + new HandlerExecutor(mHandler), mSyncRoot, backupManager::dataChanged); } else { mDisplayTopologyCoordinator = null; } @@ -758,6 +762,11 @@ public final class DisplayManagerService extends SystemService { } @Override + public void onUserUnlocked(@NonNull final TargetUser to) { + scheduleTopologiesReload(to.getUserIdentifier(), /*isUserSwitching=*/ true); + } + + @Override public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) { final int newUserId = to.getUserIdentifier(); final int userSerial = getUserManager().getUserSerialNumber(newUserId); @@ -796,6 +805,11 @@ public final class DisplayManagerService extends SystemService { if (mFlags.isDisplayContentModeManagementEnabled()) { updateMirrorBuiltInDisplaySettingLocked(); } + + final UserManager userManager = getUserManager(); + if (null != userManager && userManager.isUserUnlockingOrUnlocked(mCurrentUserId)) { + scheduleTopologiesReload(mCurrentUserId, /*isUserSwitching=*/ true); + } } } @@ -873,6 +887,8 @@ public final class DisplayManagerService extends SystemService { mSmallAreaDetectionController = (mFlags.isSmallAreaDetectionEnabled()) ? SmallAreaDetectionController.create(mContext) : null; + + scheduleTopologiesReload(mCurrentUserId, /*isUserSwitching=*/ false); } @VisibleForTesting @@ -925,6 +941,15 @@ public final class DisplayManagerService extends SystemService { return mDisplayNotificationManager; } + private void scheduleTopologiesReload(final int userId, final boolean isUserSwitching) { + if (mDisplayTopologyCoordinator != null) { + // Need background thread due to xml files read operations not allowed on Display thread + BackgroundThread.getHandler().post(() -> + mDisplayTopologyCoordinator.reloadTopologies( + userId, isUserSwitching)); + } + } + private void loadStableDisplayValuesLocked() { final Point size = mPersistentDataStore.getStableDisplaySize(); if (size.x > 0 && size.y > 0) { @@ -1800,7 +1825,11 @@ public final class DisplayManagerService extends SystemService { } if ((flags & VIRTUAL_DISPLAY_FLAG_PUBLIC) != 0) { - flags |= VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR; + if ((flags & VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY) == 0) { + Slog.d(TAG, "Public virtual displays are auto mirror by default, hence adding " + + "VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR."); + flags |= VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR; + } // Public displays can't be allowed to show content when locked. if ((flags & VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD) != 0) { @@ -1808,10 +1837,16 @@ public final class DisplayManagerService extends SystemService { "Public display must not be marked as SHOW_WHEN_LOCKED_INSECURE"); } } - if ((flags & VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY) != 0) { + if ((flags & VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY) != 0 + && (flags & VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) != 0) { + Slog.d(TAG, "Own content displays cannot auto mirror other displays, hence ignoring " + + "VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR."); flags &= ~VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR; } - if ((flags & VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) != 0) { + if ((flags & VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) != 0 + && (flags & VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP) != 0) { + Slog.d(TAG, "Auto mirror displays must be in the default display group, hence ignoring " + + "VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP."); flags &= ~VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP; } // Put the display in the virtual device's display group only if it's not a mirror display, @@ -1821,6 +1856,8 @@ public final class DisplayManagerService extends SystemService { && (flags & VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) == 0 && (flags & VIRTUAL_DISPLAY_FLAG_TRUSTED) == VIRTUAL_DISPLAY_FLAG_TRUSTED && virtualDevice != null) { + Slog.d(TAG, "Own content displays owned by virtual devices are put in that device's " + + "display group, hence adding VIRTUAL_DISPLAY_FLAG_DEVICE_DISPLAY_GROUP."); flags |= VIRTUAL_DISPLAY_FLAG_DEVICE_DISPLAY_GROUP; } @@ -1852,8 +1889,7 @@ public final class DisplayManagerService extends SystemService { Binder.restoreCallingIdentity(firstToken); } - if (callingUid != Process.SYSTEM_UID - && (flags & VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) != 0) { + if (callingUid != Process.SYSTEM_UID && (flags & VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) != 0) { // Only a valid media projection or a virtual device can create a mirror virtual // display. if (!canProjectVideo(projection) && !canCreateMirrorDisplays(virtualDevice) @@ -1901,6 +1937,14 @@ public final class DisplayManagerService extends SystemService { } } + if ((flags & VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED) != 0 + && (flags & VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP) == 0 + && (flags & VIRTUAL_DISPLAY_FLAG_DEVICE_DISPLAY_GROUP) == 0) { + Slog.d(TAG, "Always unlocked displays cannot be in the default display group, hence " + + "ignoring flag VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED."); + flags &= ~VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED; + } + if ((flags & VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED) != 0) { if (callingUid != Process.SYSTEM_UID && !checkCallingPermission(ADD_ALWAYS_UNLOCKED_DISPLAY, @@ -1911,7 +1955,24 @@ public final class DisplayManagerService extends SystemService { } } - if ((flags & VIRTUAL_DISPLAY_FLAG_TRUSTED) == 0) { + if ((flags & VIRTUAL_DISPLAY_FLAG_OWN_FOCUS) != 0 + && (flags & VIRTUAL_DISPLAY_FLAG_TRUSTED) == 0) { + Slog.d(TAG, "Untrusted displays cannot have own focus, hence ignoring flag " + + "VIRTUAL_DISPLAY_FLAG_OWN_FOCUS."); + flags &= ~VIRTUAL_DISPLAY_FLAG_OWN_FOCUS; + } + + if ((flags & VIRTUAL_DISPLAY_FLAG_STEAL_TOP_FOCUS_DISABLED) != 0 + && (flags & VIRTUAL_DISPLAY_FLAG_OWN_FOCUS) == 0) { + Slog.d(TAG, "Virtual displays that cannot steal top focus must have their own " + + " focus, hence ignoring flag VIRTUAL_DISPLAY_FLAG_STEAL_TOP_FOCUS_DISABLED."); + flags &= ~VIRTUAL_DISPLAY_FLAG_STEAL_TOP_FOCUS_DISABLED; + } + + if ((flags & VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS) != 0 + && (flags & VIRTUAL_DISPLAY_FLAG_TRUSTED) == 0) { + Slog.d(TAG, "Untrusted displays cannot show system decorations, hence ignoring flag " + + "VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS."); flags &= ~VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS; } @@ -4157,7 +4218,8 @@ public final class DisplayManagerService extends SystemService { public boolean shouldReceiveRefreshRateWithChangeUpdate(int event) { if (mFlags.isRefreshRateEventForForegroundAppsEnabled() - && event == DisplayManagerGlobal.EVENT_DISPLAY_REFRESH_RATE_CHANGED) { + && event == DisplayManagerGlobal.EVENT_DISPLAY_REFRESH_RATE_CHANGED + && mActivityManagerInternal != null) { int procState = mActivityManagerInternal.getUidProcessState(mUid); int importance = ActivityManager.RunningAppProcessInfo .procStateToImportance(procState); @@ -5937,6 +5999,14 @@ public final class DisplayManagerService extends SystemService { public boolean isDisplayReadyForMirroring(int displayId) { return mExternalDisplayPolicy.isDisplayReadyForMirroring(displayId); } + + @Override + public void reloadTopologies(final int userId) { + // Reload topologies only if the userId matches the current user id. + if (userId == mCurrentUserId) { + scheduleTopologiesReload(mCurrentUserId, /*isUserSwitching=*/ false); + } + } } class DesiredDisplayModeSpecsObserver diff --git a/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java b/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java index 88650394020b..c73ab32f117a 100644 --- a/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java +++ b/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java @@ -19,6 +19,8 @@ package com.android.server.display; import static android.hardware.display.DisplayTopology.pxToDp; import android.hardware.display.DisplayTopology; +import android.util.Slog; +import android.util.SparseArray; import android.view.Display; import android.view.DisplayInfo; @@ -26,6 +28,8 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import java.io.PrintWriter; +import java.util.HashMap; +import java.util.Map; import java.util.concurrent.Executor; import java.util.function.BooleanSupplier; import java.util.function.Consumer; @@ -35,9 +39,25 @@ import java.util.function.Consumer; * persisting the topology. */ class DisplayTopologyCoordinator { + private static final String TAG = "DisplayTopologyCoordinator"; + + private static String getUniqueId(DisplayInfo info) { + if (info.displayId == Display.DEFAULT_DISPLAY && info.type == Display.TYPE_INTERNAL) { + return "internal"; + } + return info.uniqueId; + } + + // Persistent data store for display topologies. + private final DisplayTopologyStore mTopologyStore; @GuardedBy("mSyncRoot") private DisplayTopology mTopology; + @GuardedBy("mSyncRoot") + private final Map<String, Integer> mUniqueIdToDisplayIdMapping = new HashMap<>(); + + @GuardedBy("mSyncRoot") + private final SparseArray<String> mDisplayIdToUniqueIdMapping = new SparseArray<>(); /** * Check if extended displays are enabled. If not, a topology is not needed. @@ -52,23 +72,29 @@ class DisplayTopologyCoordinator { private final Consumer<DisplayTopology> mOnTopologyChangedCallback; private final Executor mTopologyChangeExecutor; private final DisplayManagerService.SyncRoot mSyncRoot; + private final Runnable mTopologySavedCallback; DisplayTopologyCoordinator(BooleanSupplier isExtendedDisplayEnabled, Consumer<DisplayTopology> onTopologyChangedCallback, - Executor topologyChangeExecutor, DisplayManagerService.SyncRoot syncRoot) { + Executor topologyChangeExecutor, DisplayManagerService.SyncRoot syncRoot, + Runnable topologySavedCallback) { this(new Injector(), isExtendedDisplayEnabled, onTopologyChangedCallback, - topologyChangeExecutor, syncRoot); + topologyChangeExecutor, syncRoot, topologySavedCallback); } @VisibleForTesting DisplayTopologyCoordinator(Injector injector, BooleanSupplier isExtendedDisplayEnabled, Consumer<DisplayTopology> onTopologyChangedCallback, - Executor topologyChangeExecutor, DisplayManagerService.SyncRoot syncRoot) { + Executor topologyChangeExecutor, DisplayManagerService.SyncRoot syncRoot, + Runnable topologySavedCallback) { mTopology = injector.getTopology(); mIsExtendedDisplayEnabled = isExtendedDisplayEnabled; mOnTopologyChangedCallback = onTopologyChangedCallback; mTopologyChangeExecutor = topologyChangeExecutor; mSyncRoot = syncRoot; + mTopologyStore = injector.createTopologyStore( + mDisplayIdToUniqueIdMapping, mUniqueIdToDisplayIdMapping); + mTopologySavedCallback = topologySavedCallback; } /** @@ -80,7 +106,10 @@ class DisplayTopologyCoordinator { return; } synchronized (mSyncRoot) { + addDisplayIdMappingLocked(info); + mTopology.addDisplay(info.displayId, getWidth(info), getHeight(info)); + restoreTopologyLocked(); sendTopologyUpdateLocked(); } } @@ -104,12 +133,40 @@ class DisplayTopologyCoordinator { void onDisplayRemoved(int displayId) { synchronized (mSyncRoot) { if (mTopology.removeDisplay(displayId)) { + removeDisplayIdMappingLocked(displayId); + restoreTopologyLocked(); sendTopologyUpdateLocked(); } } } /** + * Loads all topologies from the persistent topology store for the given userId. + * @param userId the user id, same as returned from + * {@link android.app.ActivityManagerInternal#getCurrentUserId()}. + * @param isUserSwitching whether the id of the user is currently switching. + */ + void reloadTopologies(int userId, boolean isUserSwitching) { + boolean isTopologySaved = false; + synchronized (mSyncRoot) { + mTopologyStore.reloadTopologies(userId); + boolean isTopologyRestored = restoreTopologyLocked(); + if (isTopologyRestored) { + sendTopologyUpdateLocked(); + } + if (isUserSwitching && !isTopologyRestored) { + // During user switch, if topology is not restored - last user topology is the + // good initial guess. Save this topology for consistent use in the future. + isTopologySaved = mTopologyStore.saveTopology(mTopology); + } + } + + if (isTopologySaved) { + mTopologySavedCallback.run(); + } + } + + /** * @return A deep copy of the topology. */ DisplayTopology getTopology() { @@ -119,10 +176,16 @@ class DisplayTopologyCoordinator { } void setTopology(DisplayTopology topology) { + final boolean isTopologySaved; synchronized (mSyncRoot) { + topology.normalize(); mTopology = topology; - mTopology.normalize(); sendTopologyUpdateLocked(); + isTopologySaved = mTopologyStore.saveTopology(topology); + } + + if (isTopologySaved) { + mTopologySavedCallback.run(); } } @@ -136,6 +199,24 @@ class DisplayTopologyCoordinator { } } + @GuardedBy("mSyncRoot") + private void removeDisplayIdMappingLocked(final int displayId) { + final String uniqueId = mDisplayIdToUniqueIdMapping.get(displayId); + if (null == uniqueId) { + Slog.e(TAG, "Can't find uniqueId for displayId=" + displayId); + return; + } + mDisplayIdToUniqueIdMapping.remove(displayId); + mUniqueIdToDisplayIdMapping.remove(uniqueId); + } + + @GuardedBy("mSyncRoot") + private void addDisplayIdMappingLocked(DisplayInfo info) { + final String uniqueId = getUniqueId(info); + mUniqueIdToDisplayIdMapping.put(uniqueId, info.displayId); + mDisplayIdToUniqueIdMapping.put(info.displayId, uniqueId); + } + /** * @param info The display info * @return The width of the display in dp @@ -157,6 +238,21 @@ class DisplayTopologyCoordinator { && info.displayGroupId == Display.DEFAULT_DISPLAY_GROUP; } + /** + * Restores {@link #mTopology} from {@link #mTopologyStore}, saves it in {@link #mTopology}. + * @return true if the topology was restored, false otherwise. + */ + @GuardedBy("mSyncRoot") + private boolean restoreTopologyLocked() { + var restoredTopology = mTopologyStore.restoreTopology(mTopology); + if (restoredTopology == null) { + return false; + } + mTopology = restoredTopology; + mTopology.normalize(); + return true; + } + @GuardedBy("mSyncRoot") private void sendTopologyUpdateLocked() { DisplayTopology copy = mTopology.copy(); @@ -168,5 +264,21 @@ class DisplayTopologyCoordinator { DisplayTopology getTopology() { return new DisplayTopology(); } + + DisplayTopologyStore createTopologyStore( + SparseArray<String> displayIdToUniqueIdMapping, + Map<String, Integer> uniqueIdToDisplayIdMapping) { + return new DisplayTopologyXmlStore(new DisplayTopologyXmlStore.Injector() { + @Override + public SparseArray<String> getDisplayIdToUniqueIdMapping() { + return displayIdToUniqueIdMapping; + } + + @Override + public Map<String, Integer> getUniqueIdToDisplayIdMapping() { + return uniqueIdToDisplayIdMapping; + } + }); + } } } diff --git a/services/core/java/com/android/server/display/DisplayTopologyStore.java b/services/core/java/com/android/server/display/DisplayTopologyStore.java new file mode 100644 index 000000000000..2256c11feee8 --- /dev/null +++ b/services/core/java/com/android/server/display/DisplayTopologyStore.java @@ -0,0 +1,33 @@ +/* + * Copyright 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display; + +import android.annotation.Nullable; +import android.hardware.display.DisplayTopology; + +/** + * Allows to save and restore {@link DisplayTopology}. + * See implementation: {@link DisplayTopologyXmlStore} + */ +interface DisplayTopologyStore { + boolean saveTopology(DisplayTopology topology); + + @Nullable + DisplayTopology restoreTopology(DisplayTopology topology); + + void reloadTopologies(int userId); +} diff --git a/services/core/java/com/android/server/display/DisplayTopologyXmlStore.java b/services/core/java/com/android/server/display/DisplayTopologyXmlStore.java new file mode 100644 index 000000000000..b7f31b75f6dc --- /dev/null +++ b/services/core/java/com/android/server/display/DisplayTopologyXmlStore.java @@ -0,0 +1,582 @@ +/* + * 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.display; + +import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.Display.INVALID_DISPLAY; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.Comparator.comparingInt; + +import android.annotation.Nullable; +import android.hardware.display.DisplayTopology; +import android.os.Environment; +import android.util.AtomicFile; +import android.util.AtomicFilePrintWriter; +import android.util.Slog; +import android.util.SparseArray; + +// automatically generated classes from display-topology.xsd +import com.android.server.display.topology.Children; +import com.android.server.display.topology.Display; +import com.android.server.display.topology.DisplayTopologyState; +import com.android.server.display.topology.Position; +import com.android.server.display.topology.Topology; +import com.android.server.display.topology.XmlParser; +import com.android.server.display.topology.XmlWriter; +import com.android.server.display.utils.DebugUtils; + +import org.xmlpull.v1.XmlPullParserException; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.xml.datatype.DatatypeConfigurationException; + +/** + * Saves and restores {@link DisplayTopology} to/from xml files with topologies for each + * {@link DisplayTopologyXmlStore#mUserId} user. + */ +class DisplayTopologyXmlStore implements DisplayTopologyStore { + private static final String TAG = "DisplayManager.DisplayTopologyXmlStore"; + private static final String ETC_DIR = "etc"; + private static final String DISPLAY_CONFIG_DIR = "displayconfig"; + + // To enable these logs, run: + // adb shell setprop persist.log.tag.DisplayManager.DisplayTopologyXmlStore DEBUG + // adb reboot + private static final boolean DEBUG = DebugUtils.isDebuggable(TAG); + + private static final int PERSISTENT_TOPOLOGY_VERSION = 1; + /** + * {@link #restoreTopology} needs to reorder topologies to keep the most recently used + * topologies order close to 0. In case current topology displays change often, the persistence + * of the reordered topologies can become a performance issue. To avoid persistence for small + * changes in the order values lets use this constant, serving as the threshold when + * to trigger persistence during {@link #restoreTopology}. + */ + private static final int MIN_REORDER_WHICH_TRIGGERS_PERSISTENCE = 10; + private static final int MAX_NUMBER_OF_TOPOLOGIES = 100; + + static File getUserTopologyFile(int userId) { + return new File(Environment.getDataSystemCeDirectory(userId), "display_topology.xml"); + } + + private static File getVendorTopologyFile() { + return Environment.buildPath(Environment.getVendorDirectory(), + ETC_DIR, DISPLAY_CONFIG_DIR, "display_topology.xml"); + } + + private static File getProductTopologyFile() { + return Environment.buildPath(Environment.getProductDirectory(), + ETC_DIR, DISPLAY_CONFIG_DIR, "display_topology.xml"); + } + + private static List<Topology> readTopologiesFromInputStream( + @Nullable InputStream iStream) + throws DatatypeConfigurationException, XmlPullParserException, IOException { + if (null == iStream) { + if (DEBUG) { + Slog.d(TAG, "iStream is null"); + } + return List.of(); + } + // use parser automatically generated from display-topology.xsd + var topologyState = XmlParser.read(iStream); + if (topologyState.getVersion() > PERSISTENT_TOPOLOGY_VERSION) { + Slog.e(TAG, "Topology version=" + topologyState.getVersion() + + " is not supported by DisplayTopologyXmlStore version=" + + PERSISTENT_TOPOLOGY_VERSION); + return List.of(); + } + if (DEBUG) { + Slog.d(TAG, "readTopologiesFromInputStream: done"); + } + + var topologyList = topologyState.getTopology(); + topologyList.sort(comparingInt(Topology::getOrder)); + return topologyList; + } + + private static int getOrderOrDefault(@Nullable Topology topology, int defaultOrder) { + return null != topology ? topology.getOrder() : defaultOrder; + } + + private final Injector mInjector; + private int mUserId = -1; + private final List<Topology> mImmutableTopologies = new ArrayList<>(); + private final Map<String, Topology> mTopologies = new HashMap<>(); + + DisplayTopologyXmlStore(Injector injector) { + mInjector = injector; + reloadImmutableTopologies(); + } + + /** + * Persists the topology into XML + * @param topology the topology to persist + * @return true if persisted successfully, false otherwise + */ + @Override + public boolean saveTopology(DisplayTopology topology) { + String topologyId = getTopologyId(topology); + if (DEBUG) { + Slog.d(TAG, "saveTopology userId=" + mUserId + ", topologyId=" + topologyId); + } + if (null == topologyId) { + Slog.w(TAG, "saveTopology cancelled: topology id is null for " + topology); + return false; + } + + Topology topologyToPersist = convertTopologyForPersistence(topology, topologyId); + if (null == topologyToPersist) { + Slog.w(TAG, "saveTopology cancelled: can't convert topology " + topology); + return false; + } + + if (!prependTopology(topologyToPersist)) { + Slog.w(TAG, "saveTopology cancelled: can't prependTopology"); + return false; + } + saveTopologiesToFile(); + return true; + } + + /** + * Searches for the topology's id in the store. If topology is found in the store, + * then uses the passed topology display width and height, and the persisted topology + * structure, position and offset. + * @param topology original topology which we would like to restore to a state which was + * previously persisted, keeping the current width and height. + * @return null if topology is not found, or the new restored topology otherwise. + */ + @Nullable + @Override + public DisplayTopology restoreTopology(DisplayTopology topology) { + String topologyId = getTopologyId(topology); + if (DEBUG) { + Slog.d(TAG, "restoreTopology userId=" + mUserId + ", topologyId=" + topologyId); + } + if (null == topologyId) { + Slog.w(TAG, "restoreTopology cancelled: topology id is null for " + topology); + return null; + } + + Topology restoredTopology = mTopologies.get(topologyId); + if (null == restoredTopology) { + // Topology is not found in persistent storage. + if (DEBUG) { + Slog.d(TAG, "restoreTopology userId=" + mUserId + ", topologyId=" + topologyId + + " is not found"); + } + return null; + } + + // Reorder and save to file for significant changes in topologies order. + if (restoredTopology.getOrder() >= MIN_REORDER_WHICH_TRIGGERS_PERSISTENCE) { + moveTopologyToHead(restoredTopology); + saveTopologiesToFile(); + } + return convertPersistentTopologyToDisplayTopology(topology, restoredTopology.getDisplay(), + mInjector.getUniqueIdToDisplayIdMapping()); + } + + @Override + public void reloadTopologies(int userId) { + if (DEBUG) { + Slog.d(TAG, "reloadTopologies mUserId=" + mUserId + "->userId=" + userId); + } + if (mUserId != userId) { + mUserId = userId; + resetTopologies(); + } + reloadTopologies(); + } + + private void resetTopologies() { + mTopologies.clear(); + appendTopologies(mImmutableTopologies); + } + + /** + * Increases all orders by 1 for those topologies currently below the order of the + * passed topology. Sets the order of the passed topology to 0. + */ + private void moveTopologyToHead(Topology topology) { + if (topology.getOrder() == 0) { + return; + } + for (var t : mTopologies.values()) { + if (t.getOrder() < topology.getOrder()) { + t.setOrder(t.getOrder() + 1); + } + } + topology.setOrder(0); + } + + private void reloadImmutableTopologies() { + mImmutableTopologies.clear(); + try (InputStream iStream = mInjector.readProductTopologies()) { + mImmutableTopologies.addAll(readTopologiesFromInputStream(iStream)); + } catch (IOException | XmlPullParserException | DatatypeConfigurationException e) { + Slog.e(TAG, "reloadImmutableTopologies for product topologies failed", e); + } + try (InputStream iStream = mInjector.readVendorTopologies()) { + mImmutableTopologies.addAll(readTopologiesFromInputStream(iStream)); + } catch (IOException | XmlPullParserException | DatatypeConfigurationException e) { + Slog.e(TAG, "reloadImmutableTopologies for vendor topologies failed", e); + } + for (var topology : mImmutableTopologies) { + topology.setImmutable(true); + } + } + + private void reloadTopologies() { + if (mUserId < 0) { + Slog.e(TAG, "Can't reload topologies for userId=" + mUserId); + return; + } + try (InputStream iStream = mInjector.readUserTopologies(mUserId)) { + appendTopologies(readTopologiesFromInputStream(iStream)); + } catch (IOException | XmlPullParserException | DatatypeConfigurationException e) { + Slog.e(TAG, "reloadTopologies failed", e); + } + } + + private void appendTopologies(List<Topology> topologyList) { + for (var topology : topologyList) { + appendTopology(topology); + } + } + + private void appendTopology(Topology topology) { + Topology restoredTopology = mTopologies.get(topology.getId()); + if (null != restoredTopology && restoredTopology.getImmutable()) { + Slog.w(TAG, "addTopology: can't override immutable topology " + + topology.getId()); + return; + } + + // If topology is not found, and we exceed the limit of topologies + // (so we can't add more topologies), then skip this topology + if (null == restoredTopology && mTopologies.size() >= MAX_NUMBER_OF_TOPOLOGIES) { + if (DEBUG) { + Slog.d(TAG, "appendTopology: MAX_NUMBER_OF_TOPOLOGIES is reached," + + " can't append topology" + topology.getId()); + } + return; + } + topology.setOrder(getOrderOrDefault(restoredTopology, mTopologies.size())); + mTopologies.put(topology.getId(), topology); + } + + private boolean prependTopology(Topology topology) { + Topology restoredTopology = mTopologies.get(topology.getId()); + if (null != restoredTopology && restoredTopology.getImmutable()) { + Slog.w(TAG, "prependTopology: can't override immutable topology " + + topology.getId()); + return false; + } + + // If topology is not found, and we exceed the limit of topologies + // remove the max order mutable topology. + if (null == restoredTopology && mTopologies.size() >= MAX_NUMBER_OF_TOPOLOGIES) { + Topology topologyToRemove = findMaxOrderMutableTopology(); + if (topologyToRemove == null) { + Slog.w(TAG, "prependTopology: can't find a topology to remove to free up space"); + return false; + } + mTopologies.remove(topologyToRemove.getId()); + if (DEBUG) { + Slog.d(TAG, "prependTopology: remove topology " + topologyToRemove.getId()); + } + } + + topology.setOrder(Integer.MAX_VALUE); + moveTopologyToHead(topology); + mTopologies.put(topology.getId(), topology); + return true; + } + + /** + * Higher order of the topology means lower priority. + */ + @Nullable + private Topology findMaxOrderMutableTopology() { + Topology res = null; + for (var topology : mTopologies.values()) { + if (topology.getImmutable()) { + continue; + } + if (res == null || res.getOrder() < topology.getOrder()) { + res = topology; + } + } + return res; + } + + private void saveTopologiesToFile() { + if (mUserId < 0) { + Slog.e(TAG, "Can't save topologies for userId=" + mUserId); + return; + } + if (mTopologies.isEmpty()) { + if (DEBUG) { + Slog.d(TAG, "No topologies to save for userId=" + mUserId); + } + return; + } + var topologyState = new DisplayTopologyState(); + topologyState.setVersion(PERSISTENT_TOPOLOGY_VERSION); + for (var topology : mTopologies.values()) { + if (!topology.getImmutable()) { + topologyState.getTopology().add(topology); + } + } + + try (var pw = mInjector.getTopologyFilePrintWriter(mUserId)) { + // use writer automatically generated from display-topology.xsd + XmlWriter.write(new XmlWriter(pw), topologyState); + pw.markSuccess(); + if (DEBUG) Slog.d(TAG, "saveTopologiesToFile " + pw); + } catch (IOException e) { + Slog.e(TAG, "saveTopologiesToFile failed", e); + } + } + + private DisplayTopology convertPersistentTopologyToDisplayTopology( + DisplayTopology currentDisplayTopology, + Display persistentDisplayTopology, + Map<String, Integer> uniqueIdToDisplayIdMapping) { + var rootNode = convertPersistentDisplayToTreeNode(persistentDisplayTopology, + currentDisplayTopology, uniqueIdToDisplayIdMapping); + int primaryDisplayId = findPrimaryDisplayId(persistentDisplayTopology, + uniqueIdToDisplayIdMapping); + if (primaryDisplayId == INVALID_DISPLAY) { + Slog.e(TAG, "Primary display id is not found in persistent topology"); + primaryDisplayId = DEFAULT_DISPLAY; + } + return new DisplayTopology(rootNode, primaryDisplayId); + } + + private DisplayTopology.TreeNode convertPersistentDisplayToTreeNode( + Display persistentDisplay, + DisplayTopology currentDisplayTopology, + Map<String, Integer> uniqueIdToDisplayIdMapping + ) { + Integer displayId = uniqueIdToDisplayIdMapping.get(persistentDisplay.getId()); + if (null == displayId) { + throw new IllegalStateException("Can't map uniqueId=" + + persistentDisplay.getId() + " to displayId"); + } + + var displayNode = DisplayTopology.findDisplay(displayId, + currentDisplayTopology.getRoot()); + if (null == displayNode) { + throw new IllegalStateException("Can't find displayId=" + + displayId + " in current topology"); + } + + List<DisplayTopology.TreeNode> children = new ArrayList<>(); + for (var child : persistentDisplay.getChildren().getDisplay()) { + children.add(convertPersistentDisplayToTreeNode(child, currentDisplayTopology, + uniqueIdToDisplayIdMapping)); + } + + return new DisplayTopology.TreeNode( + displayId, displayNode.getWidth(), displayNode.getHeight(), + toDisplayTopologyPosition(persistentDisplay.getPosition()), + persistentDisplay.getOffset(), children); + } + + private int findPrimaryDisplayId(Display persistentDisplay, + Map<String, Integer> uniqueIdToDisplayIdMapping) { + if (persistentDisplay.getPrimary()) { + var displayId = uniqueIdToDisplayIdMapping.get(persistentDisplay.getId()); + if (null == displayId) { + throw new IllegalStateException("Can't map uniqueId=" + + persistentDisplay.getId() + " to displayId"); + } + return displayId; + } + for (var child : persistentDisplay.getChildren().getDisplay()) { + var displayId = findPrimaryDisplayId(child, uniqueIdToDisplayIdMapping); + if (displayId != INVALID_DISPLAY) { + return displayId; + } + } + return INVALID_DISPLAY; + } + + @Nullable + private Topology convertTopologyForPersistence(DisplayTopology topology, String topologyId) { + var rootNode = convertTreeNodeForPersistence(topology.getRoot(), + topology.getPrimaryDisplayId(), mInjector.getDisplayIdToUniqueIdMapping()); + if (null == rootNode) { + return null; + } + + Topology persistentTopology = new Topology(); + persistentTopology.setDisplay(rootNode); + persistentTopology.setId(topologyId); + return persistentTopology; + } + + @Nullable + private Display convertTreeNodeForPersistence( + @Nullable DisplayTopology.TreeNode node, + int primaryDisplayId, + SparseArray<String> idsToUniqueIds) { + if (null == node) { + Slog.e(TAG, "Can't convertTreeNodeForPersistence, node == null"); + return null; + } + var uniqueId = idsToUniqueIds.get(node.getDisplayId()); + if (null == uniqueId) { + Slog.e(TAG, "Can't convertTreeNodeForPersistence," + + " uniqueId is not found for " + node.getDisplayId()); + return null; + } + Children children = new Children(); + for (var child : node.getChildren()) { + var display = convertTreeNodeForPersistence(child, primaryDisplayId, idsToUniqueIds); + if (null == display) { + return null; + } + children.getDisplay().add(display); + } + var root = new Display(); + root.setPosition(toPersistentPosition(node.getPosition())); + root.setId(uniqueId); + root.setOffset(node.getOffset()); + root.setPrimary(node.getDisplayId() == primaryDisplayId); + root.setChildren(children); + return root; + } + + private Position toPersistentPosition(@DisplayTopology.TreeNode.Position int pos) { + return switch (pos) { + case DisplayTopology.TreeNode.POSITION_LEFT -> Position.left; + case DisplayTopology.TreeNode.POSITION_TOP -> Position.top; + case DisplayTopology.TreeNode.POSITION_RIGHT -> Position.right; + case DisplayTopology.TreeNode.POSITION_BOTTOM -> Position.bottom; + default -> throw new IllegalArgumentException("Unknown position=" + pos); + }; + } + + @DisplayTopology.TreeNode.Position + private int toDisplayTopologyPosition(Position pos) { + return switch (pos) { + case left -> DisplayTopology.TreeNode.POSITION_LEFT; + case top -> DisplayTopology.TreeNode.POSITION_TOP; + case right -> DisplayTopology.TreeNode.POSITION_RIGHT; + case bottom -> DisplayTopology.TreeNode.POSITION_BOTTOM; + }; + } + + private List<String> getUniqueIds(@Nullable DisplayTopology.TreeNode node, + SparseArray<String> mapping, List<String> uniqueIds) { + if (null == node) { + return uniqueIds; + } + uniqueIds.add(mapping.get(node.getDisplayId())); + for (var child : node.getChildren()) { + getUniqueIds(child, mapping, uniqueIds); + } + return uniqueIds; + } + + @Nullable + private String getTopologyId(DisplayTopology topology) { + SparseArray<String> mapping = mInjector.getDisplayIdToUniqueIdMapping(); + return getTopologyId(getUniqueIds(topology.getRoot(), mapping, new ArrayList<>())); + } + + @Nullable + private String getTopologyId(List<String> uniqueIds) { + if (uniqueIds.isEmpty() || uniqueIds.contains(null)) { + return null; + } + Collections.sort(uniqueIds); + return String.join("|", uniqueIds); + } + + abstract static class Injector { + /** + * Necessary mapping for conversion of {@link DisplayTopology} which uses + * {@link android.view.DisplayInfo#displayId} to {@link DisplayTopologyState} + * which uses {@link android.view.DisplayInfo#uniqueId} + * + * @return mapping from {@link android.view.DisplayInfo#displayId} + * to {@link android.view.DisplayInfo#uniqueId} + */ + public abstract SparseArray<String> getDisplayIdToUniqueIdMapping(); + + /** + * Necessary mapping for conversion opposite to {@link #getDisplayIdToUniqueIdMapping()} + * + * @return mapping from {@link android.view.DisplayInfo#uniqueId} + * to {@link android.view.DisplayInfo#displayId} + */ + public abstract Map<String, Integer> getUniqueIdToDisplayIdMapping(); + + /** + * Reads vendor topologies, if configured. + * @return input stream with vendor-defined topologies, or null if not configured. + */ + @Nullable + public InputStream readVendorTopologies() throws FileNotFoundException { + return getFileInputStream(getVendorTopologyFile()); + } + + /** + * Reads product topologies, if configured. + * @return input stream with product-defined topologies, or null if not configured. + */ + @Nullable + public InputStream readProductTopologies() throws FileNotFoundException { + return getFileInputStream(getProductTopologyFile()); + } + + @Nullable + InputStream readUserTopologies(int userId) throws FileNotFoundException { + return getFileInputStream(getUserTopologyFile(userId)); + } + + AtomicFilePrintWriter getTopologyFilePrintWriter(int userId) throws IOException { + var atomicFile = new AtomicFile(getUserTopologyFile(userId), + /*commitTag=*/ "topology-state"); + return new AtomicFilePrintWriter(atomicFile, UTF_8); + } + + @Nullable + private FileInputStream getFileInputStream(File file) throws FileNotFoundException { + if (DEBUG) { + Slog.d(TAG, "File: " + file + " exists=" + file.exists()); + } + return !file.exists() ? null : new FileInputStream(file); + } + } +} diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java index 558afd1da380..abbdeb9da364 100644 --- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java +++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java @@ -33,13 +33,6 @@ import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_SUPPO import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_TOUCH_FEEDBACK_DISABLED; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED; -import static com.android.server.display.DisplayDeviceInfo.FLAG_ALWAYS_UNLOCKED; -import static com.android.server.display.DisplayDeviceInfo.FLAG_DEVICE_DISPLAY_GROUP; -import static com.android.server.display.DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP; -import static com.android.server.display.DisplayDeviceInfo.FLAG_STEAL_TOP_FOCUS_DISABLED; -import static com.android.server.display.DisplayDeviceInfo.FLAG_TOUCH_FEEDBACK_DISABLED; -import static com.android.server.display.DisplayDeviceInfo.FLAG_TRUSTED; - import android.annotation.Nullable; import android.content.Context; import android.graphics.Point; @@ -574,15 +567,13 @@ public class VirtualDisplayAdapter extends DisplayAdapter { mInfo.flags &= ~DisplayDeviceInfo.FLAG_NEVER_BLANK; } else { mInfo.flags |= DisplayDeviceInfo.FLAG_OWN_CONTENT_ONLY; - - if ((mFlags & VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP) != 0) { - mInfo.flags |= FLAG_OWN_DISPLAY_GROUP; - } + } + if ((mFlags & VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP) != 0) { + mInfo.flags |= DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP; } if ((mFlags & VIRTUAL_DISPLAY_FLAG_DEVICE_DISPLAY_GROUP) != 0) { - mInfo.flags |= FLAG_DEVICE_DISPLAY_GROUP; + mInfo.flags |= DisplayDeviceInfo.FLAG_DEVICE_DISPLAY_GROUP; } - if ((mFlags & VIRTUAL_DISPLAY_FLAG_SECURE) != 0) { mInfo.flags |= DisplayDeviceInfo.FLAG_SECURE; } @@ -611,41 +602,19 @@ public class VirtualDisplayAdapter extends DisplayAdapter { mInfo.flags |= DisplayDeviceInfo.FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS; } if ((mFlags & VIRTUAL_DISPLAY_FLAG_TRUSTED) != 0) { - mInfo.flags |= FLAG_TRUSTED; + mInfo.flags |= DisplayDeviceInfo.FLAG_TRUSTED; } if ((mFlags & VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED) != 0) { - if ((mInfo.flags & DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP) != 0 - || (mFlags & VIRTUAL_DISPLAY_FLAG_DEVICE_DISPLAY_GROUP) != 0) { - mInfo.flags |= FLAG_ALWAYS_UNLOCKED; - } else { - Slog.w( - TAG, - "Ignoring VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED as it requires" - + " VIRTUAL_DISPLAY_FLAG_DEVICE_DISPLAY_GROUP or" - + " VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP."); - } + mInfo.flags |= DisplayDeviceInfo.FLAG_ALWAYS_UNLOCKED; } if ((mFlags & VIRTUAL_DISPLAY_FLAG_TOUCH_FEEDBACK_DISABLED) != 0) { - mInfo.flags |= FLAG_TOUCH_FEEDBACK_DISABLED; + mInfo.flags |= DisplayDeviceInfo.FLAG_TOUCH_FEEDBACK_DISABLED; } if ((mFlags & VIRTUAL_DISPLAY_FLAG_OWN_FOCUS) != 0) { - if ((mFlags & VIRTUAL_DISPLAY_FLAG_TRUSTED) != 0) { - mInfo.flags |= DisplayDeviceInfo.FLAG_OWN_FOCUS; - } else { - Slog.w(TAG, "Ignoring VIRTUAL_DISPLAY_FLAG_OWN_FOCUS as it requires " - + "VIRTUAL_DISPLAY_FLAG_TRUSTED."); - } + mInfo.flags |= DisplayDeviceInfo.FLAG_OWN_FOCUS; } if ((mFlags & VIRTUAL_DISPLAY_FLAG_STEAL_TOP_FOCUS_DISABLED) != 0) { - if ((mFlags & VIRTUAL_DISPLAY_FLAG_TRUSTED) != 0 - && (mFlags & VIRTUAL_DISPLAY_FLAG_OWN_FOCUS) != 0) { - mInfo.flags |= FLAG_STEAL_TOP_FOCUS_DISABLED; - } else { - Slog.w(TAG, - "Ignoring VIRTUAL_DISPLAY_FLAG_STEAL_TOP_FOCUS_DISABLED as it " - + "requires VIRTUAL_DISPLAY_FLAG_OWN_FOCUS which requires " - + "VIRTUAL_DISPLAY_FLAG_TRUSTED."); - } + mInfo.flags |= DisplayDeviceInfo.FLAG_STEAL_TOP_FOCUS_DISABLED; } mInfo.type = Display.TYPE_VIRTUAL; 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 d435144b28c6..b9ce8c93dbde 100644 --- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java +++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java @@ -21,6 +21,7 @@ import android.os.Build; import android.os.SystemProperties; import android.text.TextUtils; import android.util.Slog; +import android.window.DesktopExperienceFlags; import com.android.server.display.feature.flags.Flags; import com.android.server.display.utils.DebugUtils; @@ -250,7 +251,7 @@ public class DisplayManagerFlags { ); private final FlagState mEnableDisplayContentModeManagementFlagState = new FlagState( Flags.FLAG_ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT, - Flags::enableDisplayContentModeManagement + DesktopExperienceFlags.ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT::isTrue ); private final FlagState mSubscribeGranularDisplayEvents = new FlagState( diff --git a/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java b/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java index 550f68fbc94c..733448aa0ffc 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java @@ -278,7 +278,8 @@ public class HdmiCecNetwork { } private void invokeDeviceEventListener(HdmiDeviceInfo info, int event) { - if (!hideDevicesBehindLegacySwitch(info)) { + if (event == HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE || + !hideDevicesBehindLegacySwitch(info)) { mHdmiControlService.invokeDeviceEventListeners(info, event); } } diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index bd8b67b9d626..89f0d0edbf2b 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -710,7 +710,9 @@ public class HdmiControlService extends SystemService { // Register ContentObserver to monitor the settings change. registerContentObserver(); } - mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, ENABLED); + if (mMhlController != null) { + mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, ENABLED); + } } @VisibleForTesting @@ -1721,6 +1723,8 @@ public class HdmiControlService extends SystemService { if (result != SendMessageResult.SUCCESS) { localDevice.addAndStartAction(new ResendCecCommandAction(localDevice, command, callback)); + } else if (callback != null) { + callback.onSendCompleted(result); } } }); diff --git a/services/core/java/com/android/server/input/InputGestureManager.java b/services/core/java/com/android/server/input/InputGestureManager.java index fd755e3cefe2..32b36bfb50e5 100644 --- a/services/core/java/com/android/server/input/InputGestureManager.java +++ b/services/core/java/com/android/server/input/InputGestureManager.java @@ -23,7 +23,6 @@ import static com.android.hardware.input.Flags.enableVoiceAccessKeyGestures; import static com.android.hardware.input.Flags.keyboardA11yShortcutControl; import static com.android.server.flags.Flags.newBugreportKeyboardShortcut; import static com.android.window.flags.Flags.enableMoveToNextDisplayShortcut; -import static com.android.window.flags.Flags.enableTaskResizingKeyboardShortcuts; import android.annotation.NonNull; import android.annotation.Nullable; @@ -37,6 +36,7 @@ import android.os.SystemProperties; import android.util.IndentingPrintWriter; import android.util.SparseArray; import android.view.KeyEvent; +import android.window.DesktopModeFlags; import com.android.internal.annotations.GuardedBy; @@ -186,21 +186,11 @@ final class InputGestureManager { KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT ), createKeyGesture( - KeyEvent.KEYCODE_DPAD_LEFT, - KeyEvent.META_CTRL_ON | KeyEvent.META_ALT_ON, - KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT - ), - createKeyGesture( KeyEvent.KEYCODE_DPAD_RIGHT, KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON, KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT ), createKeyGesture( - KeyEvent.KEYCODE_DPAD_RIGHT, - KeyEvent.META_CTRL_ON | KeyEvent.META_ALT_ON, - KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT - ), - createKeyGesture( KeyEvent.KEYCODE_SLASH, KeyEvent.META_META_ON, KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER @@ -243,7 +233,7 @@ final class InputGestureManager { KeyEvent.META_META_ON | KeyEvent.META_ALT_ON, KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS)); } - if (enableTaskResizingKeyboardShortcuts()) { + if (DesktopModeFlags.ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS.isTrue()) { systemShortcuts.add(createKeyGesture( KeyEvent.KEYCODE_LEFT_BRACKET, KeyEvent.META_META_ON, diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java index 1e54beeb2d64..b9352edf9230 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java @@ -372,25 +372,8 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub /* package */ void onEndpointSessionOpenRequest( int sessionId, HubEndpointInfo initiator, String serviceDescriptor) { - if (!hasEndpointPermissions(initiator)) { - halCloseEndpointSessionNoThrow(sessionId, Reason.PERMISSION_DENIED); - return; - } - - synchronized (mOpenSessionLock) { - if (hasSessionId(sessionId)) { - Log.e(TAG, "Existing session in onEndpointSessionOpenRequest: id=" + sessionId); - halCloseEndpointSessionNoThrow(sessionId, Reason.UNSPECIFIED); - return; - } - mSessionInfoMap.put(sessionId, new SessionInfo(initiator, true)); - } - boolean success = - invokeCallback( - (consumer) -> - consumer.onSessionOpenRequest( - sessionId, initiator, serviceDescriptor)); + onEndpointSessionOpenRequestInternal(sessionId, initiator, serviceDescriptor); if (!success) { cleanupSessionResources(sessionId); } @@ -439,6 +422,33 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub } } + private boolean onEndpointSessionOpenRequestInternal( + int sessionId, HubEndpointInfo initiator, String serviceDescriptor) { + if (!hasEndpointPermissions(initiator)) { + Log.e( + TAG, + "onEndpointSessionOpenRequest: " + + initiator + + " doesn't have permission for " + + mEndpointInfo); + halCloseEndpointSessionNoThrow(sessionId, Reason.PERMISSION_DENIED); + return false; + } + + synchronized (mOpenSessionLock) { + if (hasSessionId(sessionId)) { + Log.e(TAG, "Existing session in onEndpointSessionOpenRequest: id=" + sessionId); + halCloseEndpointSessionNoThrow(sessionId, Reason.UNSPECIFIED); + return false; + } + mSessionInfoMap.put(sessionId, new SessionInfo(initiator, true)); + } + + return invokeCallback( + (consumer) -> + consumer.onSessionOpenRequest(sessionId, initiator, serviceDescriptor)); + } + private byte onMessageReceivedInternal(int sessionId, HubMessage message) { HubEndpointInfo remote; synchronized (mOpenSessionLock) { diff --git a/services/core/java/com/android/server/media/AudioManagerRouteController.java b/services/core/java/com/android/server/media/AudioManagerRouteController.java index 2686f2b30d27..14f870bd4e87 100644 --- a/services/core/java/com/android/server/media/AudioManagerRouteController.java +++ b/services/core/java/com/android/server/media/AudioManagerRouteController.java @@ -51,6 +51,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.concurrent.CopyOnWriteArrayList; /** * Maintains a list of all available routes and supports transfers to any of them. @@ -90,7 +91,11 @@ import java.util.Objects; @NonNull private final Context mContext; @NonNull private final AudioManager mAudioManager; @NonNull private final Handler mHandler; - @NonNull private final OnDeviceRouteChangedListener mOnDeviceRouteChangedListener; + + @NonNull + private final CopyOnWriteArrayList<OnDeviceRouteChangedListener> + mOnDeviceRouteChangedListeners = new CopyOnWriteArrayList<>(); + @NonNull private final BluetoothDeviceRoutesManager mBluetoothRouteController; @NonNull private final AudioProductStrategy mStrategyForMedia; @@ -112,6 +117,35 @@ import java.util.Objects; @NonNull private MediaRoute2Info mSelectedRoute; + // A singleton AudioManagerRouteController. + private static AudioManagerRouteController mInstance; + + // A flag indicating if the start function has been called. + private boolean mStarted = false; + + // Get the singleton AudioManagerRouteController. Create a new one if it's not available yet. + public static AudioManagerRouteController getInstance( + @NonNull Context context, + @NonNull AudioManager audioManager, + @NonNull Looper looper, + @NonNull AudioProductStrategy strategyForMedia, + @NonNull BluetoothAdapter btAdapter) { + if (!com.android.media.flags.Flags.enableUseOfSingletonAudioManagerRouteController()) { + return new AudioManagerRouteController( + context, audioManager, looper, strategyForMedia, btAdapter); + } + + synchronized (AudioManagerRouteController.class) { + if (mInstance == null) { + mInstance = + new AudioManagerRouteController( + context, audioManager, looper, strategyForMedia, btAdapter); + } + + return mInstance; + } + } + // TODO: b/305199571 - Support nullable btAdapter and strategyForMedia which, when null, means // no support for transferring to inactive bluetooth routes and transferring to any routes // respectively. @@ -125,13 +159,11 @@ import java.util.Objects; @NonNull AudioManager audioManager, @NonNull Looper looper, @NonNull AudioProductStrategy strategyForMedia, - @NonNull BluetoothAdapter btAdapter, - @NonNull OnDeviceRouteChangedListener onDeviceRouteChangedListener) { + @NonNull BluetoothAdapter btAdapter) { mContext = Objects.requireNonNull(context); mAudioManager = Objects.requireNonNull(audioManager); mHandler = new Handler(Objects.requireNonNull(looper)); mStrategyForMedia = Objects.requireNonNull(strategyForMedia); - mOnDeviceRouteChangedListener = Objects.requireNonNull(onDeviceRouteChangedListener); mBuiltInSpeakerSuitabilityStatus = DeviceRouteController.getBuiltInSpeakerSuitabilityStatus(mContext); @@ -144,6 +176,16 @@ import java.util.Objects; rebuildAvailableRoutes(); } + public void registerRouteChangeListener( + @NonNull OnDeviceRouteChangedListener onDeviceRouteChangedListener) { + mOnDeviceRouteChangedListeners.add(onDeviceRouteChangedListener); + } + + public void unregisterRouteChangeListener( + @NonNull OnDeviceRouteChangedListener onDeviceRouteChangedListener) { + mOnDeviceRouteChangedListeners.remove(onDeviceRouteChangedListener); + } + @RequiresPermission( anyOf = { Manifest.permission.MODIFY_AUDIO_ROUTING, @@ -151,7 +193,18 @@ import java.util.Objects; }) @Override public void start(UserHandle mUser) { - mBluetoothRouteController.start(mUser); + // When AudioManagerRouteController is singleton, only need to call this function once. + if (com.android.media.flags.Flags.enableUseOfSingletonAudioManagerRouteController()) { + if (mStarted) { + return; + } + mStarted = true; + } + + mBluetoothRouteController.start( + com.android.media.flags.Flags.enableUseOfSingletonAudioManagerRouteController() + ? UserHandle.SYSTEM + : mUser); mAudioManager.registerAudioDeviceCallback(mAudioDeviceCallback, mHandler); mAudioManager.addOnDevicesForAttributesChangedListener( AudioRoutingUtils.ATTRIBUTES_MEDIA, @@ -166,6 +219,11 @@ import java.util.Objects; }) @Override public void stop() { + // Singleton AudioManagerRouteController doesn't need to call stop function. + if (com.android.media.flags.Flags.enableUseOfSingletonAudioManagerRouteController()) { + return; + } + mAudioManager.removeOnDevicesForAttributesChangedListener( mOnDevicesForAttributesChangedListener); mAudioManager.unregisterAudioDeviceCallback(mAudioDeviceCallback); @@ -281,7 +339,9 @@ import java.util.Objects; }) private void rebuildAvailableRoutesAndNotify() { rebuildAvailableRoutes(); - mOnDeviceRouteChangedListener.onDeviceRouteChanged(); + for (OnDeviceRouteChangedListener listener : mOnDeviceRouteChangedListeners) { + listener.onDeviceRouteChanged(); + } } @RequiresPermission( diff --git a/services/core/java/com/android/server/media/DeviceRouteController.java b/services/core/java/com/android/server/media/DeviceRouteController.java index dff0adfca370..0a4cbcd1ea31 100644 --- a/services/core/java/com/android/server/media/DeviceRouteController.java +++ b/services/core/java/com/android/server/media/DeviceRouteController.java @@ -65,13 +65,11 @@ import java.util.List; if (strategyForMedia != null && btAdapter != null && Flags.enableAudioPoliciesDeviceAndBluetoothController()) { - return new AudioManagerRouteController( - context, - audioManager, - looper, - strategyForMedia, - btAdapter, - onDeviceRouteChangedListener); + AudioManagerRouteController controller = + AudioManagerRouteController.getInstance( + context, audioManager, looper, strategyForMedia, btAdapter); + controller.registerRouteChangeListener(onDeviceRouteChangedListener); + return controller; } else { IAudioService audioService = IAudioService.Stub.asInterface( diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java index 011659a616d3..3eb38a7029e6 100644 --- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java +++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java @@ -17,6 +17,7 @@ package com.android.server.media; import static android.media.MediaRoute2Info.FEATURE_LIVE_AUDIO; +import static android.media.MediaRoute2Info.FEATURE_LIVE_VIDEO; import android.annotation.NonNull; import android.annotation.Nullable; @@ -217,6 +218,28 @@ import java.util.stream.Stream; } } + @Override + public void setRouteVolume(long requestId, String routeOriginalId, int volume) { + synchronized (mLock) { + var targetProviderProxyId = mOriginalRouteIdToProviderId.get(routeOriginalId); + var targetProviderProxyRecord = mProxyRecords.get(targetProviderProxyId); + // Holds the target route, if it's managed by a provider service. Holds null otherwise. + if (targetProviderProxyRecord != null) { + var serviceTargetRoute = + targetProviderProxyRecord.mNewOriginalIdToSourceOriginalIdMap.get( + routeOriginalId); + if (serviceTargetRoute != null) { + targetProviderProxyRecord.mProxy.setRouteVolume( + requestId, serviceTargetRoute, volume); + } else { + notifyRequestFailed( + requestId, MediaRoute2ProviderService.REASON_ROUTE_NOT_AVAILABLE); + } + } + } + super.setRouteVolume(requestId, routeOriginalId, volume); + } + /** * Returns the uid that corresponds to the given name and user handle, or {@link * Process#INVALID_UID} if a uid couldn't be found. @@ -463,11 +486,18 @@ import java.util.stream.Stream; } String id = asSystemRouteId(providerInfo.getUniqueId(), sourceRoute.getOriginalId()); - var newRoute = - new MediaRoute2Info.Builder(id, sourceRoute.getName()) - .addFeature(FEATURE_LIVE_AUDIO) - .build(); - routesMap.put(id, newRoute); + var newRouteBuilder = new MediaRoute2Info.Builder(id, sourceRoute); + if ((sourceRoute.getSupportedRoutingTypes() + & MediaRoute2Info.FLAG_ROUTING_TYPE_SYSTEM_AUDIO) + != 0) { + newRouteBuilder.addFeature(FEATURE_LIVE_AUDIO); + } + if ((sourceRoute.getSupportedRoutingTypes() + & MediaRoute2Info.FLAG_ROUTING_TYPE_SYSTEM_VIDEO) + != 0) { + newRouteBuilder.addFeature(FEATURE_LIVE_VIDEO); + } + routesMap.put(id, newRouteBuilder.build()); idMap.put(id, sourceRoute.getOriginalId()); } return new ProviderProxyRecord( diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java index c428f39fd9d0..34a6cb951d46 100644 --- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java +++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java @@ -58,6 +58,7 @@ import android.media.projection.IMediaProjection; import android.media.projection.IMediaProjectionCallback; import android.media.projection.IMediaProjectionManager; import android.media.projection.IMediaProjectionWatcherCallback; +import android.media.projection.MediaProjectionEvent; import android.media.projection.MediaProjectionInfo; import android.media.projection.MediaProjectionManager; import android.media.projection.ReviewGrantedConsentResult; @@ -80,6 +81,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import com.android.internal.util.DumpUtils; +import com.android.media.projection.flags.Flags; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.Watchdog; @@ -177,9 +179,31 @@ public final class MediaProjectionManagerService extends SystemService private void maybeStopMediaProjection(int reason) { synchronized (mLock) { - if (!mMediaProjectionStopController.isExemptFromStopping(mProjectionGrant, reason)) { - Slog.d(TAG, "Content Recording: Stopping MediaProjection due to " - + MediaProjectionStopController.stopReasonToString(reason)); + if (mMediaProjectionStopController.isExemptFromStopping(mProjectionGrant, reason)) { + return; + } + + if (Flags.showStopDialogPostCallEnd() + && mMediaProjectionStopController.isStopReasonCallEnd(reason)) { + MediaProjectionEvent event = + new MediaProjectionEvent( + MediaProjectionEvent + .PROJECTION_STARTED_DURING_CALL_AND_ACTIVE_POST_CALL, + System.currentTimeMillis()); + Slog.d( + TAG, + "Scheduling event: " + + event.getEventType() + + " for reason: " + + MediaProjectionStopController.stopReasonToString(reason)); + + // Post the PROJECTION_STARTED_DURING_CALL_AND_ACTIVE_POST_CALL event with a delay. + mHandler.postDelayed(() -> dispatchEvent(event), 500); + } else { + Slog.d( + TAG, + "Stopping MediaProjection due to reason: " + + MediaProjectionStopController.stopReasonToString(reason)); mProjectionGrant.stop(StopReason.STOP_DEVICE_LOCKED); } } @@ -388,6 +412,24 @@ public final class MediaProjectionManagerService extends SystemService mCallbackDelegate.dispatchSession(projectionInfo, session); } + private void dispatchEvent(@NonNull MediaProjectionEvent event) { + if (!Flags.showStopDialogPostCallEnd()) { + Slog.d( + TAG, + "Event dispatch skipped. Reason: Flag showStopDialogPostCallEnd " + + "is disabled. Event details: " + + event); + return; + } + MediaProjectionInfo projectionInfo; + ContentRecordingSession session; + synchronized (mLock) { + projectionInfo = mProjectionGrant != null ? mProjectionGrant.getProjectionInfo() : null; + session = mProjectionGrant != null ? mProjectionGrant.mSession : null; + } + mCallbackDelegate.dispatchEvent(event, projectionInfo, session); + } + /** * Returns {@code true} when updating the current mirroring session on WM succeeded, and * {@code false} otherwise. @@ -1467,6 +1509,25 @@ public final class MediaProjectionManagerService extends SystemService } } + private void dispatchEvent( + @NonNull MediaProjectionEvent event, + @Nullable MediaProjectionInfo info, + @Nullable ContentRecordingSession session) { + if (!Flags.showStopDialogPostCallEnd()) { + Slog.d( + TAG, + "Event dispatch skipped. Reason: Flag showStopDialogPostCallEnd " + + "is disabled. Event details: " + + event); + return; + } + synchronized (mLock) { + for (IMediaProjectionWatcherCallback callback : mWatcherCallbacks.values()) { + mHandler.post(new WatcherEventCallback(callback, event, info, session)); + } + } + } + public void dispatchSession( @NonNull MediaProjectionInfo projectionInfo, @Nullable ContentRecordingSession session) { @@ -1593,6 +1654,41 @@ public final class MediaProjectionManagerService extends SystemService } } + private static final class WatcherEventCallback implements Runnable { + private final IMediaProjectionWatcherCallback mCallback; + private final MediaProjectionEvent mEvent; + private final MediaProjectionInfo mProjectionInfo; + private final ContentRecordingSession mSession; + + WatcherEventCallback( + @NonNull IMediaProjectionWatcherCallback callback, + @NonNull MediaProjectionEvent event, + @Nullable MediaProjectionInfo projectionInfo, + @Nullable ContentRecordingSession session) { + mCallback = callback; + mEvent = event; + mProjectionInfo = projectionInfo; + mSession = session; + } + + @Override + public void run() { + if (!Flags.showStopDialogPostCallEnd()) { + Slog.d( + TAG, + "Not running WatcherEventCallback. Reason: Flag " + + "showStopDialogPostCallEnd is disabled. " + ); + return; + } + try { + mCallback.onMediaProjectionEvent(mEvent, mProjectionInfo, mSession); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to notify MediaProjectionEvent change", e); + } + } + } + private static final class WatcherSessionCallback implements Runnable { private final IMediaProjectionWatcherCallback mCallback; private final MediaProjectionInfo mProjectionInfo; diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionStopController.java b/services/core/java/com/android/server/media/projection/MediaProjectionStopController.java index c018e6bc1dc7..2e0bb4f88485 100644 --- a/services/core/java/com/android/server/media/projection/MediaProjectionStopController.java +++ b/services/core/java/com/android/server/media/projection/MediaProjectionStopController.java @@ -95,6 +95,11 @@ public class MediaProjectionStopController { } } + /** Checks if the given stop reason corresponds to a call ending. */ + public boolean isStopReasonCallEnd(int stopReason) { + return stopReason == STOP_REASON_CALL_END; + } + /** * Checks whether the given projection grant is exempt from stopping restrictions. */ 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 6ad7ea7b768f..f6c94a7d9a5a 100644 --- a/services/core/java/com/android/server/media/quality/MediaQualityService.java +++ b/services/core/java/com/android/server/media/quality/MediaQualityService.java @@ -29,11 +29,19 @@ import android.content.pm.PackageManager; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.hardware.tv.mediaquality.AmbientBacklightColorFormat; +import android.hardware.tv.mediaquality.DolbyAudioProcessing; +import android.hardware.tv.mediaquality.DtsVirtualX; import android.hardware.tv.mediaquality.IMediaQuality; +import android.hardware.tv.mediaquality.IPictureProfileAdjustmentListener; +import android.hardware.tv.mediaquality.IPictureProfileChangedListener; +import android.hardware.tv.mediaquality.ISoundProfileAdjustmentListener; +import android.hardware.tv.mediaquality.ISoundProfileChangedListener; +import android.hardware.tv.mediaquality.ParamCapability; import android.hardware.tv.mediaquality.PictureParameter; import android.hardware.tv.mediaquality.PictureParameters; import android.hardware.tv.mediaquality.SoundParameter; import android.hardware.tv.mediaquality.SoundParameters; +import android.hardware.tv.mediaquality.VendorParamCapability; import android.media.quality.AmbientBacklightEvent; import android.media.quality.AmbientBacklightMetadata; import android.media.quality.AmbientBacklightSettings; @@ -72,6 +80,8 @@ import org.json.JSONException; import org.json.JSONObject; import java.io.File; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -101,6 +111,10 @@ public class MediaQualityService extends SystemService { private final BiMap<Long, String> mPictureProfileTempIdMap; private final BiMap<Long, String> mSoundProfileTempIdMap; private IMediaQuality mMediaQuality; + private IPictureProfileAdjustmentListener mPpAdjustmentListener; + private ISoundProfileAdjustmentListener mSpAdjustmentListener; + private IPictureProfileChangedListener mPpChangedListener; + private ISoundProfileChangedListener mSpChangedListener; private final HalAmbientBacklightCallback mHalAmbientBacklightCallback; private final Map<String, AmbientBacklightCallbackRecord> mCallbackRecords = new HashMap<>(); private final PackageManager mPackageManager; @@ -136,18 +150,104 @@ public class MediaQualityService extends SystemService { @Override public void onStart() { IBinder binder = ServiceManager.getService(IMediaQuality.DESCRIPTOR + "/default"); - if (binder != null) { - Slogf.d(TAG, "binder is not null"); - mMediaQuality = IMediaQuality.Stub.asInterface(binder); - if (mMediaQuality != null) { - try { - mMediaQuality.setAmbientBacklightCallback(mHalAmbientBacklightCallback); - } catch (RemoteException e) { - Slog.e(TAG, "Failed to set ambient backlight detector callback", e); + if (binder == null) { + Slogf.d(TAG, "Binder is null"); + return; + } + Slogf.d(TAG, "Binder is not null"); + + mPpAdjustmentListener = new IPictureProfileAdjustmentListener.Stub() { + @Override + public void onPictureProfileAdjusted( + android.hardware.tv.mediaquality.PictureProfile pictureProfile) + throws RemoteException { + // TODO + } + + @Override + public void onParamCapabilityChanged(long pictureProfileId, ParamCapability[] caps) + throws RemoteException { + // TODO + } + + @Override + public void onVendorParamCapabilityChanged(long pictureProfileId, + VendorParamCapability[] caps) throws RemoteException { + // TODO + } + + @Override + public void requestPictureParameters(long pictureProfileId) throws RemoteException { + // TODO + } + + @Override + public void onStreamStatusChanged(long pictureProfileId, byte status) + throws RemoteException { + // TODO + } + + @Override + public int getInterfaceVersion() throws RemoteException { + return 0; + } + + @Override + public String getInterfaceHash() throws RemoteException { + return null; } + }; + mSpAdjustmentListener = new ISoundProfileAdjustmentListener.Stub() { + + @Override + public void onSoundProfileAdjusted( + android.hardware.tv.mediaquality.SoundProfile soundProfile) + throws RemoteException { + // TODO + } + + @Override + public void onParamCapabilityChanged(long soundProfileId, ParamCapability[] caps) + throws RemoteException { + // TODO + } + + @Override + public void onVendorParamCapabilityChanged(long soundProfileId, + VendorParamCapability[] caps) throws RemoteException { + // TODO + } + + @Override + public void requestSoundParameters(long soundProfileId) throws RemoteException { + // TODO + } + + @Override + public int getInterfaceVersion() throws RemoteException { + return 0; + } + + @Override + public String getInterfaceHash() throws RemoteException { + return null; + } + }; + + mMediaQuality = IMediaQuality.Stub.asInterface(binder); + if (mMediaQuality != null) { + try { + mMediaQuality.setAmbientBacklightCallback(mHalAmbientBacklightCallback); + mMediaQuality.setPictureProfileAdjustmentListener(mPpAdjustmentListener); + mMediaQuality.setSoundProfileAdjustmentListener(mSpAdjustmentListener); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to set ambient backlight detector callback", e); } } + mPpChangedListener = IPictureProfileChangedListener.Stub.asInterface(binder); + mSpChangedListener = ISoundProfileChangedListener.Stub.asInterface(binder); + publishBinderService(Context.MEDIA_QUALITY_SERVICE, new BinderService()); } @@ -183,6 +283,30 @@ public class MediaQualityService extends SystemService { return pp; } + private void notifyHalOnPictureProfileChange(Long dbId, PersistableBundle params) { + // TODO: only notify HAL when the profile is active / being used + try { + mPpChangedListener.onPictureProfileChanged(convertToHalPictureProfile(dbId, + params)); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to notify HAL on picture profile change.", e); + } + } + + private android.hardware.tv.mediaquality.PictureProfile convertToHalPictureProfile(Long id, + PersistableBundle params) { + PictureParameters pictureParameters = new PictureParameters(); + pictureParameters.pictureParameters = convertPersistableBundleToPictureParameterList( + params); + + android.hardware.tv.mediaquality.PictureProfile toReturn = + new android.hardware.tv.mediaquality.PictureProfile(); + toReturn.pictureProfileId = id; + toReturn.parameters = pictureParameters; + + return toReturn; + } + @Override public void updatePictureProfile(String id, PictureProfile pp, UserHandle user) { Long dbId = mPictureProfileTempIdMap.getKey(id); @@ -203,6 +327,7 @@ public class MediaQualityService extends SystemService { null, values); notifyOnPictureProfileUpdated(mPictureProfileTempIdMap.getValue(dbId), getPictureProfile(dbId), Binder.getCallingUid(), Binder.getCallingPid()); + notifyHalOnPictureProfileChange(dbId, pp.getParameters()); } private boolean hasPermissionToUpdatePictureProfile(Long dbId, PictureProfile toUpdate) { @@ -236,6 +361,7 @@ public class MediaQualityService extends SystemService { notifyOnPictureProfileRemoved(mPictureProfileTempIdMap.getValue(dbId), toDelete, Binder.getCallingUid(), Binder.getCallingPid()); mPictureProfileTempIdMap.remove(dbId); + notifyHalOnPictureProfileChange(dbId, null); } } @@ -355,6 +481,10 @@ public class MediaQualityService extends SystemService { private PictureParameter[] convertPersistableBundleToPictureParameterList( PersistableBundle params) { + if (params == null) { + return null; + } + List<PictureParameter> pictureParams = new ArrayList<>(); if (params.containsKey(PictureQuality.PARAMETER_BRIGHTNESS)) { pictureParams.add(PictureParameter.brightness(params.getLong( @@ -459,7 +589,7 @@ public class MediaQualityService extends SystemService { } if (params.containsKey(PictureQuality.PARAMETER_AUTO_SUPER_RESOLUTION_ENABLED)) { pictureParams.add(PictureParameter.autoSuperResolutionEnabled(params.getBoolean( - PictureQuality.PARAMETER_AUTO_SUPER_RESOLUTION_ENABLED))); + PictureQuality.PARAMETER_AUTO_SUPER_RESOLUTION_ENABLED))); } if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_RED_GAIN)) { pictureParams.add(PictureParameter.colorTemperatureRedGain(params.getInt( @@ -473,63 +603,210 @@ public class MediaQualityService extends SystemService { pictureParams.add(PictureParameter.colorTemperatureBlueGain(params.getInt( PictureQuality.PARAMETER_COLOR_TUNER_BLUE_GAIN))); } - - /** - * TODO: add conversion for following after adding to MediaQualityContract - * - * PictureParameter.levelRange - * PictureParameter.gamutMapping - * PictureParameter.pcMode - * PictureParameter.lowLatency - * PictureParameter.vrr - * PictureParameter.cvrr - * PictureParameter.hdmiRgbRange - * PictureParameter.colorSpace - * PictureParameter.panelInitMaxLuminceNits - * PictureParameter.panelInitMaxLuminceValid - * PictureParameter.gamma - * PictureParameter.colorTemperatureRedOffset - * PictureParameter.colorTemperatureGreenOffset - * PictureParameter.colorTemperatureBlueOffset - * PictureParameter.elevenPointRed - * PictureParameter.elevenPointGreen - * PictureParameter.elevenPointBlue - * PictureParameter.lowBlueLight - * PictureParameter.LdMode - * PictureParameter.osdRedGain - * PictureParameter.osdGreenGain - * PictureParameter.osdBlueGain - * PictureParameter.osdRedOffset - * PictureParameter.osdGreenOffset - * PictureParameter.osdBlueOffset - * PictureParameter.osdHue - * PictureParameter.osdSaturation - * PictureParameter.osdContrast - * PictureParameter.colorTunerSwitch - * PictureParameter.colorTunerHueRed - * PictureParameter.colorTunerHueGreen - * PictureParameter.colorTunerHueBlue - * PictureParameter.colorTunerHueCyan - * PictureParameter.colorTunerHueMagenta - * PictureParameter.colorTunerHueYellow - * PictureParameter.colorTunerHueFlesh - * PictureParameter.colorTunerSaturationRed - * PictureParameter.colorTunerSaturationGreen - * PictureParameter.colorTunerSaturationBlue - * PictureParameter.colorTunerSaturationCyan - * PictureParameter.colorTunerSaturationMagenta - * PictureParameter.colorTunerSaturationYellow - * PictureParameter.colorTunerSaturationFlesh - * PictureParameter.colorTunerLuminanceRed - * PictureParameter.colorTunerLuminanceGreen - * PictureParameter.colorTunerLuminanceBlue - * PictureParameter.colorTunerLuminanceCyan - * PictureParameter.colorTunerLuminanceMagenta - * PictureParameter.colorTunerLuminanceYellow - * PictureParameter.colorTunerLuminanceFlesh - * PictureParameter.activeProfile - * PictureParameter.pictureQualityEventType - */ + if (params.containsKey(PictureQuality.PARAMETER_LEVEL_RANGE)) { + pictureParams.add(PictureParameter.levelRange( + (byte) params.getInt(PictureQuality.PARAMETER_LEVEL_RANGE))); + } + if (params.containsKey(PictureQuality.PARAMETER_GAMUT_MAPPING)) { + pictureParams.add(PictureParameter.gamutMapping(params.getBoolean( + PictureQuality.PARAMETER_GAMUT_MAPPING))); + } + if (params.containsKey(PictureQuality.PARAMETER_PC_MODE)) { + pictureParams.add(PictureParameter.pcMode(params.getBoolean( + PictureQuality.PARAMETER_PC_MODE))); + } + if (params.containsKey(PictureQuality.PARAMETER_LOW_LATENCY)) { + pictureParams.add(PictureParameter.lowLatency(params.getBoolean( + PictureQuality.PARAMETER_LOW_LATENCY))); + } + if (params.containsKey(PictureQuality.PARAMETER_VRR)) { + pictureParams.add(PictureParameter.vrr(params.getBoolean( + PictureQuality.PARAMETER_VRR))); + } + if (params.containsKey(PictureQuality.PARAMETER_CVRR)) { + pictureParams.add(PictureParameter.cvrr(params.getBoolean( + PictureQuality.PARAMETER_CVRR))); + } + if (params.containsKey(PictureQuality.PARAMETER_HDMI_RGB_RANGE)) { + pictureParams.add(PictureParameter.hdmiRgbRange( + (byte) params.getInt(PictureQuality.PARAMETER_HDMI_RGB_RANGE))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_SPACE)) { + pictureParams.add(PictureParameter.colorSpace( + (byte) params.getInt(PictureQuality.PARAMETER_COLOR_SPACE))); + } + if (params.containsKey(PictureQuality.PARAMETER_PANEL_INIT_MAX_LUMINCE_NITS)) { + pictureParams.add(PictureParameter.panelInitMaxLuminceNits( + params.getInt(PictureQuality.PARAMETER_PANEL_INIT_MAX_LUMINCE_NITS))); + } + if (params.containsKey(PictureQuality.PARAMETER_PANEL_INIT_MAX_LUMINCE_VALID)) { + pictureParams.add(PictureParameter.panelInitMaxLuminceValid( + params.getBoolean(PictureQuality.PARAMETER_PANEL_INIT_MAX_LUMINCE_VALID))); + } + if (params.containsKey(PictureQuality.PARAMETER_GAMMA)) { + pictureParams.add(PictureParameter.gamma( + (byte) params.getInt(PictureQuality.PARAMETER_GAMMA))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TEMPERATURE_RED_OFFSET)) { + pictureParams.add(PictureParameter.colorTemperatureRedOffset(params.getInt( + PictureQuality.PARAMETER_COLOR_TEMPERATURE_RED_OFFSET))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TEMPERATURE_GREEN_OFFSET)) { + pictureParams.add(PictureParameter.colorTemperatureGreenOffset(params.getInt( + PictureQuality.PARAMETER_COLOR_TEMPERATURE_GREEN_OFFSET))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TEMPERATURE_BLUE_OFFSET)) { + pictureParams.add(PictureParameter.colorTemperatureBlueOffset(params.getInt( + PictureQuality.PARAMETER_COLOR_TEMPERATURE_BLUE_OFFSET))); + } + if (params.containsKey(PictureQuality.PARAMETER_ELEVEN_POINT_RED)) { + pictureParams.add(PictureParameter.elevenPointRed(params.getIntArray( + PictureQuality.PARAMETER_ELEVEN_POINT_RED))); + } + if (params.containsKey(PictureQuality.PARAMETER_ELEVEN_POINT_GREEN)) { + pictureParams.add(PictureParameter.elevenPointGreen(params.getIntArray( + PictureQuality.PARAMETER_ELEVEN_POINT_GREEN))); + } + if (params.containsKey(PictureQuality.PARAMETER_ELEVEN_POINT_BLUE)) { + pictureParams.add(PictureParameter.elevenPointBlue(params.getIntArray( + PictureQuality.PARAMETER_ELEVEN_POINT_BLUE))); + } + if (params.containsKey(PictureQuality.PARAMETER_LOW_BLUE_LIGHT)) { + pictureParams.add(PictureParameter.lowBlueLight( + (byte) params.getInt(PictureQuality.PARAMETER_LOW_BLUE_LIGHT))); + } + if (params.containsKey(PictureQuality.PARAMETER_LD_MODE)) { + pictureParams.add(PictureParameter.LdMode( + (byte) params.getInt(PictureQuality.PARAMETER_LD_MODE))); + } + if (params.containsKey(PictureQuality.PARAMETER_OSD_RED_GAIN)) { + pictureParams.add(PictureParameter.osdRedGain(params.getInt( + PictureQuality.PARAMETER_OSD_RED_GAIN))); + } + if (params.containsKey(PictureQuality.PARAMETER_OSD_GREEN_GAIN)) { + pictureParams.add(PictureParameter.osdGreenGain(params.getInt( + PictureQuality.PARAMETER_OSD_GREEN_GAIN))); + } + if (params.containsKey(PictureQuality.PARAMETER_OSD_BLUE_GAIN)) { + pictureParams.add(PictureParameter.osdBlueGain(params.getInt( + PictureQuality.PARAMETER_OSD_BLUE_GAIN))); + } + if (params.containsKey(PictureQuality.PARAMETER_OSD_RED_OFFSET)) { + pictureParams.add(PictureParameter.osdRedOffset(params.getInt( + PictureQuality.PARAMETER_OSD_RED_OFFSET))); + } + if (params.containsKey(PictureQuality.PARAMETER_OSD_GREEN_OFFSET)) { + pictureParams.add(PictureParameter.osdGreenOffset(params.getInt( + PictureQuality.PARAMETER_OSD_GREEN_OFFSET))); + } + if (params.containsKey(PictureQuality.PARAMETER_OSD_BLUE_OFFSET)) { + pictureParams.add(PictureParameter.osdBlueOffset(params.getInt( + PictureQuality.PARAMETER_OSD_BLUE_OFFSET))); + } + if (params.containsKey(PictureQuality.PARAMETER_OSD_HUE)) { + pictureParams.add(PictureParameter.osdHue(params.getInt( + PictureQuality.PARAMETER_OSD_HUE))); + } + if (params.containsKey(PictureQuality.PARAMETER_OSD_SATURATION)) { + pictureParams.add(PictureParameter.osdSaturation(params.getInt( + PictureQuality.PARAMETER_OSD_SATURATION))); + } + if (params.containsKey(PictureQuality.PARAMETER_OSD_CONTRAST)) { + pictureParams.add(PictureParameter.osdContrast(params.getInt( + PictureQuality.PARAMETER_OSD_CONTRAST))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_SWITCH)) { + pictureParams.add(PictureParameter.colorTunerSwitch(params.getBoolean( + PictureQuality.PARAMETER_COLOR_TUNER_SWITCH))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_HUE_RED)) { + pictureParams.add(PictureParameter.colorTunerHueRed(params.getInt( + PictureQuality.PARAMETER_COLOR_TUNER_HUE_RED))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_HUE_GREEN)) { + pictureParams.add(PictureParameter.colorTunerHueGreen(params.getInt( + PictureQuality.PARAMETER_COLOR_TUNER_HUE_GREEN))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_HUE_BLUE)) { + pictureParams.add(PictureParameter.colorTunerHueBlue(params.getInt( + PictureQuality.PARAMETER_COLOR_TUNER_HUE_BLUE))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_HUE_CYAN)) { + pictureParams.add(PictureParameter.colorTunerHueCyan(params.getInt( + PictureQuality.PARAMETER_COLOR_TUNER_HUE_CYAN))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_HUE_MAGENTA)) { + pictureParams.add(PictureParameter.colorTunerHueMagenta(params.getInt( + PictureQuality.PARAMETER_COLOR_TUNER_HUE_MAGENTA))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_HUE_YELLOW)) { + pictureParams.add(PictureParameter.colorTunerHueYellow(params.getInt( + PictureQuality.PARAMETER_COLOR_TUNER_HUE_YELLOW))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_HUE_FLESH)) { + pictureParams.add(PictureParameter.colorTunerHueFlesh(params.getInt( + PictureQuality.PARAMETER_COLOR_TUNER_HUE_FLESH))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_RED)) { + pictureParams.add(PictureParameter.colorTunerSaturationRed(params.getInt( + PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_RED))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_GREEN)) { + pictureParams.add(PictureParameter.colorTunerSaturationGreen(params.getInt( + PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_GREEN))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_BLUE)) { + pictureParams.add(PictureParameter.colorTunerSaturationBlue(params.getInt( + PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_BLUE))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_CYAN)) { + pictureParams.add(PictureParameter.colorTunerSaturationCyan(params.getInt( + PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_CYAN))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_MAGENTA)) { + pictureParams.add(PictureParameter.colorTunerSaturationMagenta(params.getInt( + PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_MAGENTA))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_YELLOW)) { + pictureParams.add(PictureParameter.colorTunerSaturationYellow(params.getInt( + PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_YELLOW))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_FLESH)) { + pictureParams.add(PictureParameter.colorTunerSaturationFlesh(params.getInt( + PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_FLESH))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_RED)) { + pictureParams.add(PictureParameter.colorTunerLuminanceRed(params.getInt( + PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_RED))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_GREEN)) { + pictureParams.add(PictureParameter.colorTunerLuminanceGreen(params.getInt( + PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_GREEN))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_BLUE)) { + pictureParams.add(PictureParameter.colorTunerLuminanceBlue(params.getInt( + PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_BLUE))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_CYAN)) { + pictureParams.add(PictureParameter.colorTunerLuminanceCyan(params.getInt( + PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_CYAN))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_MAGENTA)) { + pictureParams.add(PictureParameter.colorTunerLuminanceMagenta(params.getInt( + PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_MAGENTA))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_YELLOW)) { + pictureParams.add(PictureParameter.colorTunerLuminanceYellow(params.getInt( + PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_YELLOW))); + } + if (params.containsKey(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_FLESH)) { + pictureParams.add(PictureParameter.colorTunerLuminanceFlesh(params.getInt( + PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_FLESH))); + } + if (params.containsKey(PictureQuality.PARAMETER_PICTURE_QUALITY_EVENT_TYPE)) { + pictureParams.add(PictureParameter.pictureQualityEventType( + (byte) params.getInt(PictureQuality.PARAMETER_PICTURE_QUALITY_EVENT_TYPE))); + } return (PictureParameter[]) pictureParams.toArray(); } @@ -604,6 +881,28 @@ public class MediaQualityService extends SystemService { return sp; } + private void notifyHalOnSoundProfileChange(Long dbId, PersistableBundle params) { + // TODO: only notify HAL when the profile is active / being used + try { + mSpChangedListener.onSoundProfileChanged(convertToHalSoundProfile(dbId, params)); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to notify HAL on sound profile change.", e); + } + } + + private android.hardware.tv.mediaquality.SoundProfile convertToHalSoundProfile(Long id, + PersistableBundle params) { + SoundParameters soundParameters = new SoundParameters(); + soundParameters.soundParameters = convertPersistableBundleToSoundParameterList(params); + + android.hardware.tv.mediaquality.SoundProfile toReturn = + new android.hardware.tv.mediaquality.SoundProfile(); + toReturn.soundProfileId = id; + toReturn.parameters = soundParameters; + + return toReturn; + } + @Override public void updateSoundProfile(String id, SoundProfile sp, UserHandle user) { Long dbId = mSoundProfileTempIdMap.getKey(id); @@ -623,6 +922,7 @@ public class MediaQualityService extends SystemService { db.replace(mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME, null, values); notifyOnSoundProfileUpdated(mSoundProfileTempIdMap.getValue(dbId), getSoundProfile(dbId), Binder.getCallingUid(), Binder.getCallingPid()); + notifyHalOnSoundProfileChange(dbId, sp.getParameters()); } private boolean hasPermissionToUpdateSoundProfile(Long dbId, SoundProfile sp) { @@ -655,6 +955,7 @@ public class MediaQualityService extends SystemService { notifyOnSoundProfileRemoved(mSoundProfileTempIdMap.getValue(dbId), toDelete, Binder.getCallingUid(), Binder.getCallingPid()); mSoundProfileTempIdMap.remove(dbId); + notifyHalOnSoundProfileChange(dbId, null); } } @@ -773,6 +1074,10 @@ public class MediaQualityService extends SystemService { private SoundParameter[] convertPersistableBundleToSoundParameterList( PersistableBundle params) { + //TODO: set EqualizerDetail + if (params == null) { + return null; + } List<SoundParameter> soundParams = new ArrayList<>(); if (params.containsKey(SoundQuality.PARAMETER_BALANCE)) { soundParams.add(SoundParameter.balance(params.getInt( @@ -809,15 +1114,50 @@ public class MediaQualityService extends SystemService { soundParams.add(SoundParameter.surroundSoundEnabled(params.getBoolean( SoundQuality.PARAMETER_DIGITAL_OUTPUT_DELAY_MILLIS))); } - //TODO: equalizerDetail - //TODO: downmixMode - //TODO: enhancedAudioReturnChannelEnabled - //TODO: dolbyAudioProcessing - //TODO: dolbyDialogueEnhancer - //TODO: dtsVirtualX - //TODO: digitalOutput - //TODO: activeProfile - //TODO: soundStyle + if (params.containsKey(SoundQuality.PARAMETER_EARC)) { + soundParams.add(SoundParameter.enhancedAudioReturnChannelEnabled(params.getBoolean( + SoundQuality.PARAMETER_EARC))); + } + if (params.containsKey(SoundQuality.PARAMETER_DOWN_MIX_MODE)) { + soundParams.add(SoundParameter.downmixMode((byte) params.getInt( + SoundQuality.PARAMETER_DOWN_MIX_MODE))); + } + if (params.containsKey(SoundQuality.PARAMETER_SOUND_STYLE)) { + soundParams.add(SoundParameter.soundStyle((byte) params.getInt( + SoundQuality.PARAMETER_SOUND_STYLE))); + } + if (params.containsKey(SoundQuality.PARAMETER_DIGITAL_OUTPUT_MODE)) { + soundParams.add(SoundParameter.digitalOutput((byte) params.getInt( + SoundQuality.PARAMETER_DIGITAL_OUTPUT_MODE))); + } + if (params.containsKey(SoundQuality.PARAMETER_DIALOGUE_ENHANCER)) { + soundParams.add(SoundParameter.dolbyDialogueEnhancer((byte) params.getInt( + SoundQuality.PARAMETER_DIALOGUE_ENHANCER))); + } + + DolbyAudioProcessing dab = new DolbyAudioProcessing(); + dab.soundMode = + (byte) params.getInt(SoundQuality.PARAMETER_DOLBY_AUDIO_PROCESSING_SOUND_MODE); + dab.volumeLeveler = + params.getBoolean(SoundQuality.PARAMETER_DOLBY_AUDIO_PROCESSING_VOLUME_LEVELER); + dab.surroundVirtualizer = params.getBoolean( + SoundQuality.PARAMETER_DOLBY_AUDIO_PROCESSING_SURROUND_VIRTUALIZER); + dab.dolbyAtmos = + params.getBoolean(SoundQuality.PARAMETER_DOLBY_AUDIO_PROCESSING_DOLBY_ATMOS); + soundParams.add(SoundParameter.dolbyAudioProcessing(dab)); + + DtsVirtualX dts = new DtsVirtualX(); + dts.tbHdx = params.getBoolean(SoundQuality.PARAMETER_DTS_VIRTUAL_X_TBHDX); + dts.limiter = params.getBoolean(SoundQuality.PARAMETER_DTS_VIRTUAL_X_LIMITER); + dts.truSurroundX = params.getBoolean( + SoundQuality.PARAMETER_DTS_VIRTUAL_X_TRU_SURROUND_X); + dts.truVolumeHd = params.getBoolean(SoundQuality.PARAMETER_DTS_VIRTUAL_X_TRU_VOLUME_HD); + dts.dialogClarity = params.getBoolean( + SoundQuality.PARAMETER_DTS_VIRTUAL_X_DIALOG_CLARITY); + dts.definition = params.getBoolean(SoundQuality.PARAMETER_DTS_VIRTUAL_X_DEFINITION); + dts.height = params.getBoolean(SoundQuality.PARAMETER_DTS_VIRTUAL_X_HEIGHT); + soundParams.add(SoundParameter.dtsVirtualX(dts)); + return (SoundParameter[]) soundParams.toArray(); } @@ -1067,35 +1407,37 @@ public class MediaQualityService extends SystemService { } } - enum Mode { - ADD, - UPDATE, - REMOVE, - ERROR + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + private @interface ProfileModes { + int ADD = 1; + int UPDATE = 2; + int REMOVE = 3; + int ERROR = 4; } private void notifyOnPictureProfileAdded(String profileId, PictureProfile profile, int uid, int pid) { - notifyPictureProfileHelper(Mode.ADD, profileId, profile, null, uid, pid); + notifyPictureProfileHelper(ProfileModes.ADD, profileId, profile, null, uid, pid); } private void notifyOnPictureProfileUpdated(String profileId, PictureProfile profile, int uid, int pid) { - notifyPictureProfileHelper(Mode.UPDATE, profileId, profile, null, uid, pid); + notifyPictureProfileHelper(ProfileModes.UPDATE, profileId, profile, null, uid, pid); } private void notifyOnPictureProfileRemoved(String profileId, PictureProfile profile, int uid, int pid) { - notifyPictureProfileHelper(Mode.REMOVE, profileId, profile, null, uid, pid); + notifyPictureProfileHelper(ProfileModes.REMOVE, profileId, profile, null, uid, pid); } private void notifyOnPictureProfileError(String profileId, int errorCode, int uid, int pid) { - notifyPictureProfileHelper(Mode.ERROR, profileId, null, errorCode, uid, pid); + notifyPictureProfileHelper(ProfileModes.ERROR, profileId, null, errorCode, uid, pid); } - private void notifyPictureProfileHelper(Mode mode, String profileId, PictureProfile profile, - Integer errorCode, int uid, int pid) { + private void notifyPictureProfileHelper(int mode, String profileId, + PictureProfile profile, Integer errorCode, int uid, int pid) { UserState userState = getOrCreateUserStateLocked(UserHandle.USER_SYSTEM); int n = userState.mPictureProfileCallbacks.beginBroadcast(); @@ -1107,28 +1449,28 @@ public class MediaQualityService extends SystemService { .get(callback); if (pidUid.first == pid && pidUid.second == uid) { - if (mode == Mode.ADD) { + if (mode == ProfileModes.ADD) { userState.mPictureProfileCallbacks.getBroadcastItem(i) .onPictureProfileAdded(profileId, profile); - } else if (mode == Mode.UPDATE) { + } else if (mode == ProfileModes.UPDATE) { userState.mPictureProfileCallbacks.getBroadcastItem(i) .onPictureProfileUpdated(profileId, profile); - } else if (mode == Mode.REMOVE) { + } else if (mode == ProfileModes.REMOVE) { userState.mPictureProfileCallbacks.getBroadcastItem(i) .onPictureProfileRemoved(profileId, profile); - } else if (mode == Mode.ERROR) { + } else if (mode == ProfileModes.ERROR) { userState.mPictureProfileCallbacks.getBroadcastItem(i) .onError(profileId, errorCode); } } } catch (RemoteException e) { - if (mode == Mode.ADD) { + if (mode == ProfileModes.ADD) { Slog.e(TAG, "Failed to report added picture profile to callback", e); - } else if (mode == Mode.UPDATE) { + } else if (mode == ProfileModes.UPDATE) { Slog.e(TAG, "Failed to report updated picture profile to callback", e); - } else if (mode == Mode.REMOVE) { + } else if (mode == ProfileModes.REMOVE) { Slog.e(TAG, "Failed to report removed picture profile to callback", e); - } else if (mode == Mode.ERROR) { + } else if (mode == ProfileModes.ERROR) { Slog.e(TAG, "Failed to report picture profile error to callback", e); } } @@ -1138,25 +1480,25 @@ public class MediaQualityService extends SystemService { private void notifyOnSoundProfileAdded(String profileId, SoundProfile profile, int uid, int pid) { - notifySoundProfileHelper(Mode.ADD, profileId, profile, null, uid, pid); + notifySoundProfileHelper(ProfileModes.ADD, profileId, profile, null, uid, pid); } private void notifyOnSoundProfileUpdated(String profileId, SoundProfile profile, int uid, int pid) { - notifySoundProfileHelper(Mode.UPDATE, profileId, profile, null, uid, pid); + notifySoundProfileHelper(ProfileModes.UPDATE, profileId, profile, null, uid, pid); } private void notifyOnSoundProfileRemoved(String profileId, SoundProfile profile, int uid, int pid) { - notifySoundProfileHelper(Mode.REMOVE, profileId, profile, null, uid, pid); + notifySoundProfileHelper(ProfileModes.REMOVE, profileId, profile, null, uid, pid); } private void notifyOnSoundProfileError(String profileId, int errorCode, int uid, int pid) { - notifySoundProfileHelper(Mode.ERROR, profileId, null, errorCode, uid, pid); + notifySoundProfileHelper(ProfileModes.ERROR, profileId, null, errorCode, uid, pid); } - private void notifySoundProfileHelper(Mode mode, String profileId, SoundProfile profile, - Integer errorCode, int uid, int pid) { + private void notifySoundProfileHelper(int mode, String profileId, + SoundProfile profile, Integer errorCode, int uid, int pid) { UserState userState = getOrCreateUserStateLocked(UserHandle.USER_SYSTEM); int n = userState.mSoundProfileCallbacks.beginBroadcast(); @@ -1168,28 +1510,28 @@ public class MediaQualityService extends SystemService { .get(callback); if (pidUid.first == pid && pidUid.second == uid) { - if (mode == Mode.ADD) { + if (mode == ProfileModes.ADD) { userState.mSoundProfileCallbacks.getBroadcastItem(i) .onSoundProfileAdded(profileId, profile); - } else if (mode == Mode.UPDATE) { + } else if (mode == ProfileModes.UPDATE) { userState.mSoundProfileCallbacks.getBroadcastItem(i) .onSoundProfileUpdated(profileId, profile); - } else if (mode == Mode.REMOVE) { + } else if (mode == ProfileModes.REMOVE) { userState.mSoundProfileCallbacks.getBroadcastItem(i) .onSoundProfileRemoved(profileId, profile); - } else if (mode == Mode.ERROR) { + } else if (mode == ProfileModes.ERROR) { userState.mSoundProfileCallbacks.getBroadcastItem(i) .onError(profileId, errorCode); } } } catch (RemoteException e) { - if (mode == Mode.ADD) { + if (mode == ProfileModes.ADD) { Slog.e(TAG, "Failed to report added sound profile to callback", e); - } else if (mode == Mode.UPDATE) { + } else if (mode == ProfileModes.UPDATE) { Slog.e(TAG, "Failed to report updated sound profile to callback", e); - } else if (mode == Mode.REMOVE) { + } else if (mode == ProfileModes.REMOVE) { Slog.e(TAG, "Failed to report removed sound profile to callback", e); - } else if (mode == Mode.ERROR) { + } else if (mode == ProfileModes.ERROR) { Slog.e(TAG, "Failed to report sound profile error to callback", e); } } @@ -1468,7 +1810,13 @@ public class MediaQualityService extends SystemService { RemoteCallbackList<IPictureProfileCallback> { @Override public void onCallbackDied(IPictureProfileCallback callback) { - //todo + synchronized ("mPictureProfileLock") { //TODO: Change to lock + for (int i = 0; i < mUserStates.size(); i++) { + int userId = mUserStates.keyAt(i); + UserState userState = getOrCreateUserStateLocked(userId); + userState.mPictureProfileCallbackPidUidMap.remove(callback); + } + } } } @@ -1476,7 +1824,13 @@ public class MediaQualityService extends SystemService { RemoteCallbackList<ISoundProfileCallback> { @Override public void onCallbackDied(ISoundProfileCallback callback) { - //todo + synchronized ("mSoundProfileLock") { //TODO: Change to lock + for (int i = 0; i < mUserStates.size(); i++) { + int userId = mUserStates.keyAt(i); + UserState userState = getOrCreateUserStateLocked(userId); + userState.mSoundProfileCallbackPidUidMap.remove(callback); + } + } } } diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index 7de2815eba6b..1d376b4bdfd5 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -3612,13 +3612,16 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { final long token = Binder.clearCallingIdentity(); try { config = mCarrierConfigManager.getConfigForSubId(subId); - tm = mContext.getSystemService(TelephonyManager.class); + tm = mContext.getSystemService(TelephonyManager.class).createForSubscriptionId(subId); } finally { Binder.restoreCallingIdentity(token); } - // First check: does caller have carrier privilege? - if (tm != null && tm.hasCarrierPrivileges(subId)) { + // First check: does callingPackage have carrier privilege? + // Note that we can't call TelephonyManager.hasCarrierPrivileges() which will check if + // ourself has carrier privileges + if (tm != null && (tm.checkCarrierPrivilegesForPackage(callingPackage) + == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS)) { return; } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 588e87924b7d..0d3c18ac339f 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -258,7 +258,6 @@ import android.metrics.LogMaker; import android.net.ConnectivityManager; import android.net.Network; import android.net.NetworkCapabilities; -import android.net.NetworkRequest; import android.net.Uri; import android.os.Binder; import android.os.Build; @@ -482,7 +481,8 @@ public class NotificationManagerService extends SystemService { Adjustment.KEY_SENSITIVE_CONTENT, Adjustment.KEY_RANKING_SCORE, Adjustment.KEY_NOT_CONVERSATION, - Adjustment.KEY_TYPE + Adjustment.KEY_TYPE, + Adjustment.KEY_SUMMARIZATION }; static final Integer[] DEFAULT_ALLOWED_ADJUSTMENT_KEY_TYPES = new Integer[] { @@ -632,6 +632,8 @@ public class NotificationManagerService extends SystemService { // Minium number of sparse groups for a package before autogrouping them private static final int AUTOGROUP_SPARSE_GROUPS_AT_COUNT = 3; + private static final Duration ZEN_BROADCAST_DELAY = Duration.ofMillis(250); + private IActivityManager mAm; private ActivityTaskManagerInternal mAtm; private ActivityManager mActivityManager; @@ -2834,33 +2836,25 @@ public class NotificationManagerService extends SystemService { } private void registerNetworkCallback() { - NetworkRequest request = new NetworkRequest.Builder().addTransportType( - NetworkCapabilities.TRANSPORT_WIFI).build(); - mConnectivityManager.registerNetworkCallback(request, + mConnectivityManager.registerDefaultNetworkCallback( new ConnectivityManager.NetworkCallback() { - // Need to post to another thread, as we can't call synchronous ConnectivityManager - // methods from the callback itself, due to potential race conditions. - @Override - public void onAvailable(@NonNull Network network) { - mHandler.post(() -> updateWifiConnectionState()); - } - @Override - public void onLost(@NonNull Network network) { - mHandler.post(() -> updateWifiConnectionState()); - } - }); - updateWifiConnectionState(); + @Override + public void onCapabilitiesChanged(@NonNull Network network, + @NonNull NetworkCapabilities capabilities) { + updateWifiConnectionState(capabilities); + } + @Override + public void onLost(@NonNull Network network) { + mConnectedToWifi = false; + } + }, mHandler); } @VisibleForTesting() - void updateWifiConnectionState() { - Network current = mConnectivityManager.getActiveNetwork(); - NetworkCapabilities capabilities = mConnectivityManager.getNetworkCapabilities(current); - if (current == null || capabilities == null) { - mConnectedToWifi = false; - return; - } - mConnectedToWifi = capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI); + void updateWifiConnectionState(NetworkCapabilities capabilities) { + mConnectedToWifi = capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) + && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) + && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED); } /** @@ -3178,6 +3172,24 @@ public class NotificationManagerService extends SystemService { sendRegisteredOnlyBroadcast(new Intent(action)); } + /** + * Schedules a broadcast to be sent to runtime receivers and DND-policy-access packages. The + * broadcast will be sent after {@link #ZEN_BROADCAST_DELAY}, unless a new broadcast is + * scheduled in the interim, in which case the previous one is dropped and the waiting period + * is <em>restarted</em>. + * + * <p>Note that this uses <em>equality of the {@link Intent#getAction}</em> as the criteria for + * deduplicating pending broadcasts, ignoring the extras and anything else. This is intentional + * so that e.g. rapidly changing some value A -> B -> C will only produce a broadcast for C + * (instead of every time because the extras are different). + */ + private void sendZenBroadcastWithDelay(Intent intent) { + String token = "zen_broadcast:" + intent.getAction(); + mHandler.removeCallbacksAndEqualMessages(token); + mHandler.postDelayed(() -> sendRegisteredOnlyBroadcast(intent), token, + ZEN_BROADCAST_DELAY.toMillis()); + } + private void sendRegisteredOnlyBroadcast(Intent baseIntent) { int[] userIds = mUmInternal.getProfileIds(mAmi.getCurrentUserId(), true); if (Flags.nmBinderPerfReduceZenBroadcasts()) { @@ -3371,14 +3383,25 @@ public class NotificationManagerService extends SystemService { @GuardedBy("mNotificationLock") private void updateEffectsSuppressorLocked() { + final long oldSuppressedEffects = mZenModeHelper.getSuppressedEffects(); final long updatedSuppressedEffects = calculateSuppressedEffects(); - if (updatedSuppressedEffects == mZenModeHelper.getSuppressedEffects()) return; + if (updatedSuppressedEffects == oldSuppressedEffects) return; + final List<ComponentName> suppressors = getSuppressors(); ZenLog.traceEffectsSuppressorChanged( - mEffectsSuppressors, suppressors, updatedSuppressedEffects); - mEffectsSuppressors = suppressors; + mEffectsSuppressors, suppressors, oldSuppressedEffects, updatedSuppressedEffects); mZenModeHelper.setSuppressedEffects(updatedSuppressedEffects); - sendRegisteredOnlyBroadcast(NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED); + + if (Flags.nmBinderPerfThrottleEffectsSuppressorBroadcast()) { + if (!suppressors.equals(mEffectsSuppressors)) { + mEffectsSuppressors = suppressors; + sendZenBroadcastWithDelay( + new Intent(NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED)); + } + } else { + mEffectsSuppressors = suppressors; + sendRegisteredOnlyBroadcast(NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED); + } } private void exitIdle() { @@ -3500,12 +3523,18 @@ public class NotificationManagerService extends SystemService { } private ArrayList<ComponentName> getSuppressors() { - ArrayList<ComponentName> names = new ArrayList<ComponentName>(); + ArrayList<ComponentName> names = new ArrayList<>(); for (int i = mListenersDisablingEffects.size() - 1; i >= 0; --i) { ArraySet<ComponentName> serviceInfoList = mListenersDisablingEffects.valueAt(i); for (ComponentName info : serviceInfoList) { - names.add(info); + if (Flags.nmBinderPerfThrottleEffectsSuppressorBroadcast()) { + if (!names.contains(info)) { + names.add(info); + } + } else { + names.add(info); + } } } @@ -7221,7 +7250,7 @@ public class NotificationManagerService extends SystemService { if (!mAssistants.isAdjustmentAllowed(potentialKey)) { toRemove.add(potentialKey); } - if (notificationClassification() && adjustments.containsKey(KEY_TYPE)) { + if (notificationClassification() && potentialKey.equals(KEY_TYPE)) { mAssistants.setNasUnsupportedDefaults(r.getSbn().getNormalizedUserId()); if (!mAssistants.isAdjustmentKeyTypeAllowed(adjustments.getInt(KEY_TYPE))) { toRemove.add(potentialKey); @@ -10404,7 +10433,8 @@ public class NotificationManagerService extends SystemService { r.getRankingScore(), r.isConversation(), r.getProposedImportance(), - r.hasSensitiveContent()); + r.hasSensitiveContent(), + r.getSummarization()); extractorDataBefore.put(r.getKey(), extractorData); mRankingHelper.extractSignals(r); } @@ -11724,7 +11754,8 @@ public class NotificationManagerService extends SystemService { : (record.getRankingScore() > 0 ? RANKING_PROMOTED : RANKING_DEMOTED), record.getNotification().isBubbleNotification(), record.getProposedImportance(), - hasSensitiveContent + hasSensitiveContent, + record.getSummarization() ); rankings.add(ranking); } diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java index 81af0d8a6d80..52101e336920 100644 --- a/services/core/java/com/android/server/notification/NotificationRecord.java +++ b/services/core/java/com/android/server/notification/NotificationRecord.java @@ -25,6 +25,7 @@ import static android.app.NotificationManager.IMPORTANCE_HIGH; import static android.app.NotificationManager.IMPORTANCE_LOW; import static android.app.NotificationManager.IMPORTANCE_MIN; import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; +import static android.service.notification.Adjustment.KEY_SUMMARIZATION; import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL; import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_POSITIVE; @@ -225,6 +226,8 @@ public final class NotificationRecord { // type of the bundle if the notification was classified private @Adjustment.Types int mBundleType = Adjustment.TYPE_OTHER; + private String mSummarization = null; + public NotificationRecord(Context context, StatusBarNotification sbn, NotificationChannel channel) { this.sbn = sbn; @@ -589,6 +592,7 @@ public final class NotificationRecord { pw.println(prefix + "shortcut=" + notification.getShortcutId() + " found valid? " + (mShortcutInfo != null)); pw.println(prefix + "mUserVisOverride=" + getPackageVisibilityOverride()); + pw.println(prefix + "hasSummarization=" + (mSummarization != null)); } private void dumpNotification(PrintWriter pw, String prefix, Notification notification, @@ -811,6 +815,12 @@ public final class NotificationRecord { Adjustment.KEY_TYPE, mChannel.getId()); } + if ((android.app.Flags.nmSummarizationUi() || android.app.Flags.nmSummarization()) + && signals.containsKey(KEY_SUMMARIZATION)) { + mSummarization = signals.getString(KEY_SUMMARIZATION); + EventLogTags.writeNotificationAdjusted(getKey(), + KEY_SUMMARIZATION, Boolean.toString(mSummarization != null)); + } if (!signals.isEmpty() && adjustment.getIssuer() != null) { mAdjustmentIssuer = adjustment.getIssuer(); } @@ -983,6 +993,13 @@ public final class NotificationRecord { return null; } + public String getSummarization() { + if ((android.app.Flags.nmSummarizationUi() || android.app.Flags.nmSummarization())) { + return mSummarization; + } + return null; + } + public boolean setIntercepted(boolean intercept) { mIntercept = intercept; mInterceptSet = true; diff --git a/services/core/java/com/android/server/notification/NotificationRecordExtractorData.java b/services/core/java/com/android/server/notification/NotificationRecordExtractorData.java index 3f4f7d3bbc38..9315ddc0d5b0 100644 --- a/services/core/java/com/android/server/notification/NotificationRecordExtractorData.java +++ b/services/core/java/com/android/server/notification/NotificationRecordExtractorData.java @@ -47,6 +47,7 @@ public final class NotificationRecordExtractorData { private final boolean mIsConversation; private final int mProposedImportance; private final boolean mSensitiveContent; + private final String mSummarization; NotificationRecordExtractorData(int position, int visibility, boolean showBadge, boolean allowBubble, boolean isBubble, NotificationChannel channel, String groupKey, @@ -54,7 +55,8 @@ public final class NotificationRecordExtractorData { Integer userSentiment, Integer suppressVisually, ArrayList<Notification.Action> systemSmartActions, ArrayList<CharSequence> smartReplies, int importance, float rankingScore, - boolean isConversation, int proposedImportance, boolean sensitiveContent) { + boolean isConversation, int proposedImportance, boolean sensitiveContent, + String summarization) { mPosition = position; mVisibility = visibility; mShowBadge = showBadge; @@ -73,6 +75,7 @@ public final class NotificationRecordExtractorData { mIsConversation = isConversation; mProposedImportance = proposedImportance; mSensitiveContent = sensitiveContent; + mSummarization = summarization; } // Returns whether the provided NotificationRecord differs from the cached data in any way. @@ -93,7 +96,8 @@ public final class NotificationRecordExtractorData { || !Objects.equals(mSmartReplies, r.getSmartReplies()) || mImportance != r.getImportance() || mProposedImportance != r.getProposedImportance() - || mSensitiveContent != r.hasSensitiveContent(); + || mSensitiveContent != r.hasSensitiveContent() + || !Objects.equals(mSummarization, r.getSummarization()); } // Returns whether the NotificationRecord has a change from this data for which we should @@ -117,6 +121,7 @@ public final class NotificationRecordExtractorData { || !r.rankingScoreMatches(mRankingScore) || mIsConversation != r.isConversation() || mProposedImportance != r.getProposedImportance() - || mSensitiveContent != r.hasSensitiveContent(); + || mSensitiveContent != r.hasSensitiveContent() + || !Objects.equals(mSummarization, r.getSummarization()); } } diff --git a/services/core/java/com/android/server/notification/ZenLog.java b/services/core/java/com/android/server/notification/ZenLog.java index 7e853d9d2d0b..49f93b8b7c16 100644 --- a/services/core/java/com/android/server/notification/ZenLog.java +++ b/services/core/java/com/android/server/notification/ZenLog.java @@ -140,8 +140,9 @@ public class ZenLog { } public static void traceEffectsSuppressorChanged(List<ComponentName> oldSuppressors, - List<ComponentName> newSuppressors, long suppressedEffects) { - append(TYPE_SUPPRESSOR_CHANGED, "suppressed effects:" + suppressedEffects + "," + List<ComponentName> newSuppressors, long oldSuppressedEffects, long suppressedEffects) { + append(TYPE_SUPPRESSOR_CHANGED, "suppressed effects:" + + oldSuppressedEffects + "->" + suppressedEffects + "," + componentListToString(oldSuppressors) + "->" + componentListToString(newSuppressors)); } diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig index 822ff48c831c..048f2b6b0cbc 100644 --- a/services/core/java/com/android/server/notification/flags.aconfig +++ b/services/core/java/com/android/server/notification/flags.aconfig @@ -182,6 +182,16 @@ flag { } flag { + name: "nm_binder_perf_throttle_effects_suppressor_broadcast" + namespace: "systemui" + description: "Delay sending the ACTION_EFFECTS_SUPPRESSOR_CHANGED broadcast if it changes too often" + bug: "371776935" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "fix_calling_uid_from_cps" namespace: "systemui" description: "Correctly checks zen rule ownership when a CPS notifies with a Condition" diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java index 6303ecd53dbb..8710438d76b3 100644 --- a/services/core/java/com/android/server/om/OverlayManagerService.java +++ b/services/core/java/com/android/server/om/OverlayManagerService.java @@ -1064,10 +1064,12 @@ public final class OverlayManagerService extends SystemService { return; } try { - dumpState.setUserId(Integer.parseInt(args[opti])); + final int userId = UserHandle.parseUserArg(args[opti]); + final int realUserId = handleIncomingUser(userId, "dump"); + dumpState.setUserId(realUserId); opti++; - } catch (NumberFormatException e) { - pw.println("Error: user argument is not a number: " + args[opti]); + } catch (Exception e) { + pw.println("Error: " + e.getMessage()); return; } } else if ("--verbose".equals(opt)) { diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java index 0b58c759b284..f011d283c8bb 100644 --- a/services/core/java/com/android/server/pm/DexOptHelper.java +++ b/services/core/java/com/android/server/pm/DexOptHelper.java @@ -96,6 +96,9 @@ import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.function.Predicate; @@ -105,6 +108,11 @@ import java.util.function.Predicate; public final class DexOptHelper { private static final long SEVEN_DAYS_IN_MILLISECONDS = 7 * 24 * 60 * 60 * 1000; + @NonNull + private static final ThreadPoolExecutor sDexoptExecutor = + new ThreadPoolExecutor(1 /* corePoolSize */, 1 /* maximumPoolSize */, + 60 /* keepAliveTime */, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>()); + private static boolean sArtManagerLocalIsInitialized = false; private final PackageManagerService mPm; @@ -113,6 +121,11 @@ public final class DexOptHelper { // used, to make it available to the onDexoptDone callback. private volatile long mBootDexoptStartTime; + static { + // Recycle the thread if it's not used for `keepAliveTime`. + sDexoptExecutor.allowsCoreThreadTimeOut(); + } + DexOptHelper(PackageManagerService pm) { mPm = pm; } @@ -746,43 +759,11 @@ public final class DexOptHelper { */ static void performDexoptIfNeeded(InstallRequest installRequest, DexManager dexManager, Context context, PackageManagerTracedLock.RawLock installLock) { - // Construct the DexoptOptions early to see if we should skip running dexopt. - // - // Do not run PackageDexOptimizer through the local performDexOpt - // method because `pkg` may not be in `mPackages` yet. - // - // Also, don't fail application installs if the dexopt step fails. DexoptOptions dexoptOptions = getDexoptOptionsByInstallRequest(installRequest, dexManager); - // Check whether we need to dexopt the app. - // - // NOTE: it is IMPORTANT to call dexopt: - // - after doRename which will sync the package data from AndroidPackage and - // its corresponding ApplicationInfo. - // - after installNewPackageLIF or replacePackageLIF which will update result with the - // uid of the application (pkg.applicationInfo.uid). - // This update happens in place! - // - // We only need to dexopt if the package meets ALL of the following conditions: - // 1) it is not an instant app or if it is then dexopt is enabled via gservices. - // 2) it is not debuggable. - // 3) it is not on Incremental File System. - // - // Note that we do not dexopt instant apps by default. dexopt can take some time to - // complete, so we skip this step during installation. Instead, we'll take extra time - // the first time the instant app starts. It's preferred to do it this way to provide - // continuous progress to the useur instead of mysteriously blocking somewhere in the - // middle of running an instant app. The default behaviour can be overridden - // via gservices. - // - // Furthermore, dexopt may be skipped, depending on the install scenario and current - // state of the device. - // - // TODO(b/174695087): instantApp and onIncremental should be removed and their install - // path moved to SCENARIO_FAST. + boolean performDexopt = + DexOptHelper.shouldPerformDexopt(installRequest, dexoptOptions, context); - final boolean performDexopt = DexOptHelper.shouldPerformDexopt(installRequest, - dexoptOptions, context); if (performDexopt) { // dexopt can take long, and ArtService doesn't require installd, so we release // the lock here and re-acquire the lock after dexopt is finished. @@ -791,6 +772,7 @@ public final class DexOptHelper { } try { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt"); + // Don't fail application installs if the dexopt step fails. DexoptResult dexOptResult = DexOptHelper.dexoptPackageUsingArtService( installRequest, dexoptOptions); installRequest.onDexoptFinished(dexOptResult); @@ -804,6 +786,41 @@ public final class DexOptHelper { } /** + * Same as above, but runs asynchronously. + */ + static CompletableFuture<Void> performDexoptIfNeededAsync(InstallRequest installRequest, + DexManager dexManager, Context context) { + // Construct the DexoptOptions early to see if we should skip running dexopt. + DexoptOptions dexoptOptions = getDexoptOptionsByInstallRequest(installRequest, dexManager); + boolean performDexopt = + DexOptHelper.shouldPerformDexopt(installRequest, dexoptOptions, context); + + if (performDexopt) { + return CompletableFuture + .runAsync(() -> { + try { + Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt"); + // Don't fail application installs if the dexopt step fails. + // TODO(jiakaiz): Make this async in ART Service. + DexoptResult dexOptResult = DexOptHelper.dexoptPackageUsingArtService( + installRequest, dexoptOptions); + installRequest.onDexoptFinished(dexOptResult); + } finally { + Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); + } + }, sDexoptExecutor) + .exceptionally((t) -> { + // This should never happen. A normal dexopt failure should result in a + // DexoptResult.DEXOPT_FAILED, not an exception. + Slog.wtf(TAG, "Dexopt encountered a fatal error", t); + return null; + }); + } else { + return CompletableFuture.completedFuture(null); + } + } + + /** * Use ArtService to perform dexopt by the given InstallRequest. */ static DexoptResult dexoptPackageUsingArtService(InstallRequest installRequest, @@ -840,6 +857,20 @@ public final class DexOptHelper { */ static boolean shouldPerformDexopt(InstallRequest installRequest, DexoptOptions dexoptOptions, Context context) { + // We only need to dexopt if the package meets ALL of the following conditions: + // 1) it is not an instant app or if it is then dexopt is enabled via gservices. + // 2) it is not debuggable. + // 3) it is not on Incremental File System. + // + // Note that we do not dexopt instant apps by default. dexopt can take some time to + // complete, so we skip this step during installation. Instead, we'll take extra time + // the first time the instant app starts. It's preferred to do it this way to provide + // continuous progress to the user instead of mysteriously blocking somewhere in the + // middle of running an instant app. The default behaviour can be overridden + // via gservices. + // + // Furthermore, dexopt may be skipped, depending on the install scenario and current + // state of the device. final boolean isApex = ((installRequest.getScanFlags() & SCAN_AS_APEX) != 0); final boolean instantApp = ((installRequest.getScanFlags() & SCAN_AS_INSTANT_APP) != 0); final PackageSetting ps = installRequest.getScannedPackageSetting(); diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 8eb5b6f11cb2..0c2782393879 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -1158,6 +1158,7 @@ final class InstallPackageHelper { return; } request.setKeepArtProfile(true); + // TODO(b/388159696): Use performDexoptIfNeededAsync. DexOptHelper.performDexoptIfNeeded(request, mDexManager, mContext, null); } } diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java index 14b0fc81fdd2..c62aaebf673b 100644 --- a/services/core/java/com/android/server/pm/UserManagerInternal.java +++ b/services/core/java/com/android/server/pm/UserManagerInternal.java @@ -584,6 +584,12 @@ public abstract class UserManagerInternal { * Returns the user id of the main user, or {@link android.os.UserHandle#USER_NULL} if there is * no main user. * + * <p>NB: Features should ideally not limit functionality to the main user. Ideally, they + * should either work for all users or for all admin users. If a feature should only work for + * select users, its determination of which user should be done intelligently or be + * customizable. Not all devices support a main user, and the idea of singling out one user as + * special is contrary to overall multiuser goals. + * * @see UserManager#isMainUser() */ public abstract @UserIdInt int getMainUserId(); diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index b85e6894b910..8cbccf5feead 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -57,6 +57,10 @@ import android.annotation.DrawableRes; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; +import android.annotation.SpecialUsers.CanBeALL; +import android.annotation.SpecialUsers.CanBeCURRENT; +import android.annotation.SpecialUsers.CanBeNULL; +import android.annotation.SpecialUsers.CannotBeSpecialUser; import android.annotation.StringRes; import android.annotation.UserIdInt; import android.app.ActivityManager; @@ -952,7 +956,7 @@ public class UserManagerService extends IUserManager.Stub { private final UserVisibilityMediator mUserVisibilityMediator; @GuardedBy("mUsersLock") - private @UserIdInt int mBootUser = UserHandle.USER_NULL; + private @CanBeNULL @UserIdInt int mBootUser = UserHandle.USER_NULL; private static UserManagerService sInstance; @@ -1333,12 +1337,12 @@ public class UserManagerService extends IUserManager.Stub { } @Override - public @UserIdInt int getMainUserId() { + public @CanBeNULL @UserIdInt int getMainUserId() { checkQueryOrCreateUsersPermission("get main user id"); return getMainUserIdUnchecked(); } - private @UserIdInt int getMainUserIdUnchecked() { + private @CanBeNULL @UserIdInt int getMainUserIdUnchecked() { synchronized (mUsersLock) { final int userSize = mUsers.size(); for (int i = 0; i < userSize; i++) { @@ -1351,7 +1355,7 @@ public class UserManagerService extends IUserManager.Stub { return UserHandle.USER_NULL; } - private @UserIdInt int getPrivateProfileUserId() { + private @CanBeNULL @UserIdInt int getPrivateProfileUserId() { synchronized (mUsersLock) { for (int userId : getUserIds()) { UserInfo userInfo = getUserInfoLU(userId); @@ -1455,7 +1459,7 @@ public class UserManagerService extends IUserManager.Stub { "No switchable users found", USER_OPERATION_ERROR_UNKNOWN); } - private @UserIdInt int getFirstSwitchableUser(boolean fullUserOnly) { + private @CanBeNULL @UserIdInt int getFirstSwitchableUser(boolean fullUserOnly) { synchronized (mUsersLock) { final int userSize = mUsers.size(); for (int i = 0; i < userSize; i++) { @@ -1472,7 +1476,7 @@ public class UserManagerService extends IUserManager.Stub { @Override - public int getPreviousFullUserToEnterForeground() { + public @CanBeNULL @UserIdInt int getPreviousFullUserToEnterForeground() { checkQueryOrCreateUsersPermission("get previous user"); int previousUser = UserHandle.USER_NULL; long latestEnteredTime = 0; @@ -1496,13 +1500,13 @@ public class UserManagerService extends IUserManager.Stub { } @Override - public @UserIdInt int getCommunalProfileId() { + public @CanBeNULL @UserIdInt int getCommunalProfileId() { checkQueryOrCreateUsersPermission("get communal profile user id"); return getCommunalProfileIdUnchecked(); } /** Returns the currently-designated communal profile, or USER_NULL if not present. */ - private @UserIdInt int getCommunalProfileIdUnchecked() { + private @CanBeNULL @UserIdInt int getCommunalProfileIdUnchecked() { synchronized (mUsersLock) { final int userSize = mUsers.size(); for (int i = 0; i < userSize; i++) { @@ -2675,7 +2679,7 @@ public class UserManagerService extends IUserManager.Stub { * {@link ActivityManagerInternal} is not available yet. */ @VisibleForTesting - int getCurrentUserId() { + @CanBeNULL @UserIdInt int getCurrentUserId() { ActivityManagerInternal activityManagerInternal = getActivityManagerInternal(); if (activityManagerInternal == null) { Slog.w(LOG_TAG, "getCurrentUserId() called too early, ActivityManagerInternal" @@ -2918,7 +2922,16 @@ public class UserManagerService extends IUserManager.Stub { * switchable. */ public @UserManager.UserSwitchabilityResult int getUserSwitchability(int userId) { - checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId, "getUserSwitchability"); + if (Flags.getUserSwitchabilityPermission()) { + if (!hasManageUsersOrPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)) { + throw new SecurityException( + "You need MANAGE_USERS or INTERACT_ACROSS_USERS permission to " + + "getUserSwitchability"); + } + } else { + checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId, + "getUserSwitchability"); + } final TimingsTraceAndSlog t = new TimingsTraceAndSlog(); t.traceBegin("getUserSwitchability-" + userId); @@ -2974,27 +2987,27 @@ public class UserManagerService extends IUserManager.Stub { } @VisibleForTesting - boolean isUserSwitcherEnabled(@UserIdInt int mUserId) { + boolean isUserSwitcherEnabled(@UserIdInt int userId) { boolean multiUserSettingOn = Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.USER_SWITCHER_ENABLED, Resources.getSystem().getBoolean(com.android.internal .R.bool.config_showUserSwitcherByDefault) ? 1 : 0) != 0; return UserManager.supportsMultipleUsers() - && !hasUserRestriction(DISALLOW_USER_SWITCH, mUserId) + && !hasUserRestriction(DISALLOW_USER_SWITCH, userId) && !UserManager.isDeviceInDemoMode(mContext) && multiUserSettingOn; } @Override public boolean isUserSwitcherEnabled(boolean showEvenIfNotActionable, - @UserIdInt int mUserId) { - if (!isUserSwitcherEnabled(mUserId)) { + @UserIdInt int userId) { + if (!isUserSwitcherEnabled(userId)) { return false; } // The feature is enabled. But is it worth showing? return showEvenIfNotActionable - || !hasUserRestriction(UserManager.DISALLOW_ADD_USER, mUserId) // Can add new user + || !hasUserRestriction(UserManager.DISALLOW_ADD_USER, userId) // Can add new user || areThereMultipleSwitchableUsers(); // There are switchable users } @@ -3427,7 +3440,8 @@ public class UserManagerService extends IUserManager.Stub { } @VisibleForTesting - void setUserRestrictionInner(int userId, @NonNull String key, boolean value) { + void setUserRestrictionInner( + @CanBeALL @UserIdInt int userId, @NonNull String key, boolean value) { if (!UserRestrictionsUtils.isValidRestriction(key)) { Slog.e(LOG_TAG, "Setting invalid restriction " + key); return; @@ -3520,10 +3534,11 @@ public class UserManagerService extends IUserManager.Stub { /** @return a specific user restriction that's in effect currently. */ @Override public boolean hasUserRestriction(String restrictionKey, @UserIdInt int userId) { + checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId, "hasUserRestriction"); + // TODO(b/390455855): Should this be (!userExists(userId) && userId != UserHandle.USER_ALL)? if (!userExists(userId)) { return false; } - checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId, "hasUserRestriction"); return mLocalService.hasUserRestriction(restrictionKey, userId); } @@ -3575,8 +3590,6 @@ public class UserManagerService extends IUserManager.Stub { } /** - * @hide - * * Returns who set a user restriction on a user. * Requires {@link android.Manifest.permission#MANAGE_USERS} permission. * @param restrictionKey the string key representing the restriction @@ -5610,7 +5623,7 @@ public class UserManagerService extends IUserManager.Stub { private @NonNull UserInfo createUserInternal( @Nullable String name, @NonNull String userType, - @UserInfoFlag int flags, @UserIdInt int parentId, + @UserInfoFlag int flags, @CanBeNULL @UserIdInt int parentId, @Nullable String[] disallowedPackages) throws UserManager.CheckedUserOperationException { @@ -5643,8 +5656,8 @@ public class UserManagerService extends IUserManager.Stub { private @NonNull UserInfo createUserInternalUnchecked( @Nullable String name, @NonNull String userType, @UserInfoFlag int flags, - @UserIdInt int parentId, boolean preCreate, @Nullable String[] disallowedPackages, - @Nullable Object token) + @CanBeNULL @UserIdInt int parentId, boolean preCreate, + @Nullable String[] disallowedPackages, @Nullable Object token) throws UserManager.CheckedUserOperationException { final int noneUserId = -1; @@ -6227,9 +6240,11 @@ public class UserManagerService extends IUserManager.Stub { if (UserManager.getMaxSupportedUsers() > 1) { data.add(FrameworkStatsLog.buildStatsEvent(FrameworkStatsLog.MULTI_USER_INFO, UserManager.getMaxSupportedUsers(), + // TODO(b/390455855): is USER_ALL really allowed here? isUserSwitcherEnabled(UserHandle.USER_ALL), UserManager.supportsMultipleUsers() && !hasUserRestriction(UserManager.DISALLOW_ADD_USER, + // TODO(b/390455855): is USER_ALL really allowed here? UserHandle.USER_ALL))); } } else { @@ -6258,9 +6273,6 @@ public class UserManagerService extends IUserManager.Stub { } } - /** - * @hide - */ @Override public @NonNull UserInfo createRestrictedProfileWithThrow( @Nullable String name, @UserIdInt int parentUserId) @@ -7580,8 +7592,8 @@ public class UserManagerService extends IUserManager.Stub { } } - private void dumpUser(PrintWriter pw, @UserIdInt int userId, StringBuilder sb, long now, - long nowRealtime) { + private void dumpUser(PrintWriter pw, @CanBeCURRENT @UserIdInt int userId, StringBuilder sb, + long now, long nowRealtime) { if (userId == UserHandle.USER_CURRENT) { final int currentUserId = getCurrentUserId(); pw.print("Current user: "); @@ -7755,7 +7767,7 @@ public class UserManagerService extends IUserManager.Stub { } @Override - public void setUserRestriction(int userId, @NonNull String key, boolean value) { + public void setUserRestriction(@UserIdInt int userId, @NonNull String key, boolean value) { UserManagerService.this.setUserRestrictionInner(userId, key, value); } @@ -8487,7 +8499,6 @@ public class UserManagerService extends IUserManager.Stub { } /** - * @hide * Checks whether to show a notification for sounds (e.g., alarms, timers, etc.) from * background users. */ diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 7f511e1e2aa1..283979483e73 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -3801,10 +3801,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { true /* leftOrTop */); notifyKeyGestureCompleted(event, KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT); - } else if (event.isAltPressed()) { - setSplitscreenFocus(true /* leftOrTop */); - notifyKeyGestureCompleted(event, - KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT); } else { notifyKeyGestureCompleted(event, KeyGestureEvent.KEY_GESTURE_TYPE_BACK); @@ -3821,11 +3817,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { notifyKeyGestureCompleted(event, KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT); return true; - } else if (event.isAltPressed()) { - setSplitscreenFocus(false /* leftOrTop */); - notifyKeyGestureCompleted(event, - KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT); - return true; } } break; @@ -4241,9 +4232,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { case KeyGestureEvent.KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION: case KeyGestureEvent.KEY_GESTURE_TYPE_DESKTOP_MODE: case KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT: - case KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT: case KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT: - case KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT: case KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER: case KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_UP: case KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_DOWN: @@ -4379,22 +4368,12 @@ public class PhoneWindowManager implements WindowManagerPolicy { true /* leftOrTop */); } return true; - case KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT: - if (complete) { - setSplitscreenFocus(true /* leftOrTop */); - } - return true; case KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT: if (complete) { moveFocusedTaskToStageSplit(getTargetDisplayIdForKeyGestureEvent(event), false /* leftOrTop */); } return true; - case KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT: - if (complete) { - setSplitscreenFocus(false /* leftOrTop */); - } - return true; case KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER: if (complete) { toggleKeyboardShortcutsMenu(deviceId); @@ -5084,13 +5063,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } - private void setSplitscreenFocus(boolean leftOrTop) { - StatusBarManagerInternal statusbar = getStatusBarManagerInternal(); - if (statusbar != null) { - statusbar.setSplitscreenFocus(leftOrTop); - } - } - void launchHomeFromHotKey(int displayId) { launchHomeFromHotKey(displayId, true /* awakenFromDreams */, true /*respectKeyguard*/); } diff --git a/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java b/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java index 441d3eaf2348..142d919da455 100644 --- a/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java +++ b/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java @@ -24,6 +24,8 @@ import android.util.Log; import android.view.KeyEvent; import android.view.ViewConfiguration; +import com.android.hardware.input.Flags; + import java.io.PrintWriter; import java.util.ArrayList; @@ -355,6 +357,19 @@ public final class SingleKeyGestureDetector { } if (event.getKeyCode() == mActiveRule.mKeyCode) { + if (Flags.abortSlowMultiPress() + && (event.getEventTime() - mLastDownTime + >= mActiveRule.getLongPressTimeoutMs())) { + // In this case, we are either on a first long press (but long press behavior is not + // supported for this rule), or, on a non-first press that is at least as long as + // the long-press duration. Thus, we will cancel the multipress gesture. + if (DEBUG) { + Log.d(TAG, "The duration of the press is too slow. Resetting."); + } + reset(); + return false; + } + // key-up action should always be triggered if not processed by long press. MessageObject object = new MessageObject(mActiveRule, mActiveRule.mKeyCode, mKeyPressCounter, event); diff --git a/services/core/java/com/android/server/security/FileIntegrityService.java b/services/core/java/com/android/server/security/FileIntegrityService.java index 587be0746149..bfd86d724583 100644 --- a/services/core/java/com/android/server/security/FileIntegrityService.java +++ b/services/core/java/com/android/server/security/FileIntegrityService.java @@ -17,47 +17,24 @@ package com.android.server.security; import android.annotation.EnforcePermission; -import android.annotation.NonNull; -import android.annotation.Nullable; import android.app.AppOpsManager; import android.content.Context; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.os.Binder; -import android.os.Build; -import android.os.Environment; import android.os.IBinder; import android.os.ParcelFileDescriptor; import android.os.PermissionEnforcer; import android.os.RemoteException; -import android.os.ResultReceiver; -import android.os.ShellCallback; -import android.os.ShellCommand; import android.os.UserHandle; import android.os.storage.StorageManagerInternal; import android.security.IFileIntegrityService; -import android.util.Slog; -import com.android.internal.annotations.GuardedBy; import com.android.internal.security.VerityUtils; import com.android.server.LocalServices; import com.android.server.SystemService; -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.FileDescriptor; import java.io.IOException; -import java.io.InputStream; -import java.io.PrintWriter; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.security.cert.Certificate; -import java.security.cert.CertificateEncodingException; -import java.security.cert.CertificateException; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; -import java.util.ArrayList; import java.util.Objects; /** @@ -67,15 +44,6 @@ import java.util.Objects; public class FileIntegrityService extends SystemService { private static final String TAG = "FileIntegrityService"; - /** The maximum size of signature file. This is just to avoid potential abuse. */ - private static final int MAX_SIGNATURE_FILE_SIZE_BYTES = 8192; - - private static CertificateFactory sCertFactory; - - @GuardedBy("mTrustedCertificates") - private final ArrayList<X509Certificate> mTrustedCertificates = - new ArrayList<X509Certificate>(); - /** Gets the instance of the service */ public static FileIntegrityService getService() { return LocalServices.getService(FileIntegrityService.class); @@ -91,43 +59,6 @@ public class FileIntegrityService extends SystemService { return VerityUtils.isFsVeritySupported(); } - @Override - public boolean isAppSourceCertificateTrusted(@Nullable byte[] certificateBytes, - @NonNull String packageName) { - checkCallerPermission(packageName); - - if (android.security.Flags.deprecateFsvSig()) { - // When deprecated, stop telling the caller that any app source certificate is - // trusted on the current device. This behavior is also consistent with devices - // without this feature support. - return false; - } - - try { - if (!VerityUtils.isFsVeritySupported()) { - return false; - } - if (certificateBytes == null) { - Slog.w(TAG, "Received a null certificate"); - return false; - } - synchronized (mTrustedCertificates) { - return mTrustedCertificates.contains(toCertificate(certificateBytes)); - } - } catch (CertificateException e) { - Slog.e(TAG, "Failed to convert the certificate: " + e); - return false; - } - } - - @Override - public void onShellCommand(FileDescriptor in, FileDescriptor out, - FileDescriptor err, String[] args, ShellCallback callback, - ResultReceiver resultReceiver) { - new FileIntegrityServiceShellCommand() - .exec(this, in, out, err, args, callback, resultReceiver); - } - private void checkCallerPackageName(String packageName) { final int callingUid = Binder.getCallingUid(); final int callingUserId = UserHandle.getUserId(callingUid); @@ -195,12 +126,6 @@ public class FileIntegrityService extends SystemService { public FileIntegrityService(final Context context) { super(context); mService = new BinderService(context); - try { - sCertFactory = CertificateFactory.getInstance("X.509"); - } catch (CertificateException e) { - Slog.wtf(TAG, "Cannot get an instance of X.509 certificate factory"); - } - LocalServices.addService(FileIntegrityService.class, this); } @@ -215,155 +140,6 @@ public class FileIntegrityService extends SystemService { @Override public void onStart() { - loadAllCertificates(); publishBinderService(Context.FILE_INTEGRITY_SERVICE, mService); } - - /** - * Returns whether the signature over the file's fs-verity digest can be verified by one of the - * known certiticates. - */ - public boolean verifyPkcs7DetachedSignature(String signaturePath, String filePath) - throws IOException { - if (Files.size(Paths.get(signaturePath)) > MAX_SIGNATURE_FILE_SIZE_BYTES) { - throw new SecurityException("Signature file is unexpectedly large: " - + signaturePath); - } - byte[] signatureBytes = Files.readAllBytes(Paths.get(signaturePath)); - byte[] digest = VerityUtils.getFsverityDigest(filePath); - synchronized (mTrustedCertificates) { - for (var cert : mTrustedCertificates) { - try { - byte[] derEncoded = cert.getEncoded(); - if (VerityUtils.verifyPkcs7DetachedSignature(signatureBytes, digest, - new ByteArrayInputStream(derEncoded))) { - return true; - } - } catch (CertificateEncodingException e) { - Slog.w(TAG, "Ignoring ill-formed certificate: " + e); - } - } - } - return false; - } - - private void loadAllCertificates() { - // Load certificates trusted by the device manufacturer. - final String relativeDir = "etc/security/fsverity"; - loadCertificatesFromDirectory(Environment.getRootDirectory().toPath() - .resolve(relativeDir)); - loadCertificatesFromDirectory(Environment.getProductDirectory().toPath() - .resolve(relativeDir)); - } - - private void loadCertificatesFromDirectory(Path path) { - try { - File[] files = path.toFile().listFiles(); - if (files == null) { - return; - } - - for (File cert : files) { - byte[] certificateBytes = Files.readAllBytes(cert.toPath()); - collectCertificate(certificateBytes); - } - } catch (IOException e) { - Slog.wtf(TAG, "Failed to load fs-verity certificate from " + path, e); - } - } - - /** - * Tries to convert {@code bytes} into an X.509 certificate and store in memory. - * Errors need to be surpressed in order fo the next certificates to still be collected. - */ - private void collectCertificate(@NonNull byte[] bytes) { - try { - synchronized (mTrustedCertificates) { - mTrustedCertificates.add(toCertificate(bytes)); - } - } catch (CertificateException e) { - Slog.e(TAG, "Invalid certificate, ignored: " + e); - } - } - - /** - * Converts byte array into one X.509 certificate. If multiple certificate is defined, ignore - * the rest. The rational is to make it harder to smuggle. - */ - @NonNull - private static X509Certificate toCertificate(@NonNull byte[] bytes) - throws CertificateException { - Certificate certificate = sCertFactory.generateCertificate(new ByteArrayInputStream(bytes)); - if (!(certificate instanceof X509Certificate)) { - throw new CertificateException("Expected to contain an X.509 certificate"); - } - return (X509Certificate) certificate; - } - - - private class FileIntegrityServiceShellCommand extends ShellCommand { - @Override - public int onCommand(String cmd) { - if (!Build.IS_DEBUGGABLE) { - return -1; - } - if (cmd == null) { - return handleDefaultCommands(cmd); - } - final PrintWriter pw = getOutPrintWriter(); - switch (cmd) { - case "append-cert": - String nextArg = getNextArg(); - if (nextArg == null) { - pw.println("Invalid argument"); - pw.println(""); - onHelp(); - return -1; - } - ParcelFileDescriptor pfd = openFileForSystem(nextArg, "r"); - if (pfd == null) { - pw.println("Cannot open the file"); - return -1; - } - InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(pfd); - try { - collectCertificate(is.readAllBytes()); - } catch (IOException e) { - pw.println("Failed to add certificate: " + e); - return -1; - } - pw.println("Certificate is added successfully"); - return 0; - - case "remove-last-cert": - synchronized (mTrustedCertificates) { - if (mTrustedCertificates.size() == 0) { - pw.println("Certificate list is already empty"); - return -1; - } - mTrustedCertificates.remove(mTrustedCertificates.size() - 1); - } - pw.println("Certificate is removed successfully"); - return 0; - default: - pw.println("Unknown action"); - pw.println(""); - onHelp(); - } - return -1; - } - - @Override - public void onHelp() { - final PrintWriter pw = getOutPrintWriter(); - pw.println("File integrity service commands:"); - pw.println(" help"); - pw.println(" Print this help text."); - pw.println(" append-cert path/to/cert.der"); - pw.println(" Add the DER-encoded certificate (only in debug builds)"); - pw.println(" remove-last-cert"); - pw.println(" Remove the last certificate in the key list (only in debug builds)"); - pw.println(""); - } - } } diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java index b7ce050aaadd..0ed522805bef 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java @@ -56,12 +56,12 @@ public interface StatusBarManagerInternal { void toggleKeyboardShortcutsMenu(int deviceId); /** - * Used by InputMethodManagerService to notify the IME status. + * Sets the new IME window status. * - * @param displayId The display to which the IME is bound to. - * @param vis The IME visibility. - * @param backDisposition The IME back disposition. - * @param showImeSwitcher {@code true} when the IME switcher button should be shown. + * @param displayId The id of the display to which the IME is bound. + * @param vis The IME window visibility. + * @param backDisposition The IME back disposition mode. + * @param showImeSwitcher Whether the IME Switcher button should be shown. */ void setImeWindowStatus(int displayId, @ImeWindowVisibility int vis, @BackDispositionMode int backDisposition, boolean showImeSwitcher); @@ -177,6 +177,13 @@ public interface StatusBarManagerInternal { */ void onDisplayReady(int displayId); + /** + * Notifies System UI that the system decorations should be removed from the display. + * + * @param displayId display ID + */ + void onDisplayRemoveSystemDecorations(int displayId); + /** @see com.android.internal.statusbar.IStatusBar#onSystemBarAttributesChanged */ void onSystemBarAttributesChanged(int displayId, @Appearance int appearance, AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme, @@ -275,7 +282,13 @@ public interface StatusBarManagerInternal { */ void moveFocusedTaskToDesktop(int displayId); - /** Passes through the given shell commands to SystemUI */ void passThroughShellCommand(String[] args, FileDescriptor fd); + + /** + * Set whether the display should have a navigation bar. + * + * TODO(b/390591772): Refactor this method + */ + void setHasNavigationBar(int displayId, boolean hasNavigationBar); } diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index 4ece470d9b8d..37056a4af250 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -793,6 +793,26 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D } @Override + public void onDisplayRemoveSystemDecorations(int displayId) { + if (isVisibleBackgroundUserOnDisplay(displayId)) { + if (SPEW) { + Slog.d(TAG, + "Skipping onDisplayRemoveSystemDecorations for visible background " + + "user " + + mUserManagerInternal.getUserAssignedToDisplay(displayId)); + } + return; + } + + IStatusBar bar = mBar; + if (bar != null) { + try { + bar.onDisplayRemoveSystemDecorations(displayId); + } catch (RemoteException ex) {} + } + } + + @Override public void onSystemBarAttributesChanged(int displayId, @Appearance int appearance, AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme, @Behavior int behavior, @InsetsType int requestedVisibleTypes, @@ -996,6 +1016,23 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D public void passThroughShellCommand(String[] args, FileDescriptor fd) { StatusBarManagerService.this.passThroughShellCommand(args, fd); } + + @Override + public void setHasNavigationBar(int displayId, boolean hasNavigationBar) { + if (isVisibleBackgroundUserOnDisplay(displayId)) { + if (SPEW) { + Slog.d(TAG, "Skipping setHasNavigationBar for visible background user " + + mUserManagerInternal.getUserAssignedToDisplay(displayId)); + } + return; + } + IStatusBar bar = mBar; + if (bar != null) { + try { + bar.setHasNavigationBar(displayId, hasNavigationBar); + } catch (RemoteException ex) {} + } + } }; private final GlobalActionsProvider mGlobalActionsProvider = new GlobalActionsProvider() { @@ -1542,10 +1579,13 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D getUiState(displayId).setImeWindowState(vis, backDisposition, showImeSwitcher); mHandler.post(() -> { - if (mBar == null) return; - try { - mBar.setImeWindowStatus(displayId, vis, backDisposition, showImeSwitcher); - } catch (RemoteException ex) { } + IStatusBar bar = mBar; + if (bar != null) { + try { + bar.setImeWindowStatus(displayId, vis, backDisposition, showImeSwitcher); + } catch (RemoteException ex) { + } + } }); } } diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java index 25c07500b891..872ab595994b 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java @@ -28,6 +28,9 @@ public abstract class WallpaperManagerInternal { */ public abstract void onDisplayReady(int displayId); + /** Notifies when display stop showing system decorations and wallpaper. */ + public abstract void onDisplayRemoveSystemDecorations(int displayId); + /** Notifies when the screen finished turning on and is visible to the user. */ public abstract void onScreenTurnedOn(int displayId); diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 415896b6230f..db530e728a1a 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -20,6 +20,7 @@ import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; import static android.Manifest.permission.MANAGE_EXTERNAL_STORAGE; import static android.Manifest.permission.READ_WALLPAPER_INTERNAL; import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; +import static android.app.Flags.enableConnectedDisplaysWallpaper; import static android.app.Flags.fixWallpaperChanged; import static android.app.Flags.liveWallpaperContentHandling; import static android.app.Flags.removeNextWallpaperComponent; @@ -89,6 +90,7 @@ import android.graphics.Point; import android.graphics.Rect; import android.graphics.RectF; import android.hardware.display.DisplayManager; +import android.hardware.display.DisplayManager.DisplayListener; import android.multiuser.Flags; import android.os.Binder; import android.os.Bundle; @@ -116,6 +118,7 @@ import android.service.wallpaper.WallpaperService; import android.system.ErrnoException; import android.system.Os; import android.text.TextUtils; +import android.util.ArraySet; import android.util.EventLog; import android.util.IntArray; import android.util.Slog; @@ -158,6 +161,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Objects; +import java.util.Set; import java.util.function.Consumer; import java.util.function.Predicate; @@ -655,8 +659,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub private final MyPackageMonitor mMonitor; private final AppOpsManager mAppOpsManager; - private final DisplayManager.DisplayListener mDisplayListener = - new DisplayManager.DisplayListener() { + private final DisplayListener mDisplayListener = new DisplayListener() { @Override public void onDisplayAdded(int displayId) { @@ -664,31 +667,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub @Override public void onDisplayRemoved(int displayId) { - synchronized (mLock) { - if (mLastWallpaper != null) { - WallpaperData targetWallpaper = null; - if (mLastWallpaper.connection != null && - mLastWallpaper.connection.containsDisplay(displayId)) { - targetWallpaper = mLastWallpaper; - } else if (mFallbackWallpaper != null && - mFallbackWallpaper.connection != null && - mFallbackWallpaper.connection.containsDisplay(displayId)) { - targetWallpaper = mFallbackWallpaper; - } - if (targetWallpaper == null) return; - DisplayConnector connector = - targetWallpaper.connection.getDisplayConnectorOrCreate(displayId); - if (connector == null) return; - connector.disconnectLocked(targetWallpaper.connection); - targetWallpaper.connection.removeDisplayConnector(displayId); - mWallpaperDisplayHelper.removeDisplayData(displayId); - } - for (int i = mColorsChangedListeners.size() - 1; i >= 0; i--) { - final SparseArray<RemoteCallbackList<IWallpaperManagerCallback>> callbacks = - mColorsChangedListeners.valueAt(i); - callbacks.delete(displayId); - } - } + onDisplayRemovedInternal(displayId); } @Override @@ -731,6 +710,12 @@ public class WallpaperManagerService extends IWallpaperManager.Stub private final ComponentName mImageWallpaper; /** + * Name of the component that is used when the user-selected wallpaper is incompatible with the + * display's resolution or aspect ratio. + */ + @Nullable private final ComponentName mFallbackWallpaperComponent; + + /** * Default image wallpaper shall never changed after system service started, caching it when we * first read the image file. */ @@ -758,12 +743,38 @@ public class WallpaperManagerService extends IWallpaperManager.Stub final WallpaperDisplayHelper mWallpaperDisplayHelper; final WallpaperCropper mWallpaperCropper; - private boolean supportsMultiDisplay(WallpaperConnection connection) { - if (connection != null) { - return connection.mInfo == null // This is image wallpaper - || connection.mInfo.supportsMultipleDisplays(); + // TODO(b/384519749): Remove this set after we introduce the aspect ratio check. + private final Set<Integer> mWallpaperCompatibleDisplaysForTest = new ArraySet<>(); + + private boolean isWallpaperCompatibleForDisplay(int displayId, WallpaperConnection connection) { + if (connection == null) { + return false; } - return false; + // Non image wallpaper. + if (connection.mInfo != null) { + return connection.mInfo.supportsMultipleDisplays(); + } + + // Image wallpaper + if (enableConnectedDisplaysWallpaper()) { + // TODO(b/384519749): check display's resolution and image wallpaper cropped image + // aspect ratio. + return displayId == DEFAULT_DISPLAY + || mWallpaperCompatibleDisplaysForTest.contains(displayId); + } + // When enableConnectedDisplaysWallpaper is off, we assume the image wallpaper supports all + // usable displays. + return true; + } + + @VisibleForTesting + void addWallpaperCompatibleDisplayForTest(int displayId) { + mWallpaperCompatibleDisplaysForTest.add(displayId); + } + + @VisibleForTesting + void removeWallpaperCompatibleDisplayForTest(int displayId) { + mWallpaperCompatibleDisplaysForTest.remove(displayId); } private void updateFallbackConnection() { @@ -774,7 +785,9 @@ public class WallpaperManagerService extends IWallpaperManager.Stub Slog.w(TAG, "Fallback wallpaper connection has not been created yet!!"); return; } - if (supportsMultiDisplay(systemConnection)) { + // TODO(b/384520326) Passing DEFAULT_DISPLAY temporarily before we revamp the + // multi-display supports. + if (isWallpaperCompatibleForDisplay(DEFAULT_DISPLAY, systemConnection)) { if (fallbackConnection.mDisplayConnector.size() != 0) { fallbackConnection.forEachDisplayConnector(connector -> { if (connector.mEngine != null) { @@ -949,16 +962,14 @@ public class WallpaperManagerService extends IWallpaperManager.Stub private void initDisplayState() { // Do not initialize fallback wallpaper if (!mWallpaper.equals(mFallbackWallpaper)) { - if (supportsMultiDisplay(this)) { - // The system wallpaper is image wallpaper or it can supports multiple displays. - appendConnectorWithCondition(display -> - mWallpaperDisplayHelper.isUsableDisplay(display, mClientUid)); - } else { - // The system wallpaper does not support multiple displays, so just attach it on - // default display. - mDisplayConnector.append(DEFAULT_DISPLAY, - new DisplayConnector(DEFAULT_DISPLAY)); - } + appendConnectorWithCondition(display -> { + final int displayId = display.getDisplayId(); + if (display.getDisplayId() == DEFAULT_DISPLAY) { + return true; + } + return mWallpaperDisplayHelper.isUsableDisplay(display, mClientUid) + && isWallpaperCompatibleForDisplay(displayId, /* connection= */ this); + }); } } @@ -1560,6 +1571,12 @@ public class WallpaperManagerService extends IWallpaperManager.Stub mShuttingDown = false; mImageWallpaper = ComponentName.unflattenFromString( context.getResources().getString(R.string.image_wallpaper_component)); + if (enableConnectedDisplaysWallpaper()) { + mFallbackWallpaperComponent = ComponentName.unflattenFromString( + context.getResources().getString(R.string.fallback_wallpaper_component)); + } else { + mFallbackWallpaperComponent = null; + } ComponentName defaultComponent = WallpaperManager.getCmfDefaultWallpaperComponent(context); mDefaultWallpaperComponent = defaultComponent == null ? mImageWallpaper : defaultComponent; mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class); @@ -1624,9 +1641,17 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } @Override + public void onDisplayRemoveSystemDecorations(int displayId) { + // The display mirroring starts. The handling logic is the same as when removing a + // display. + onDisplayRemovedInternal(displayId); + } + + @Override public void onScreenTurnedOn(int displayId) { notifyScreenTurnedOn(displayId); } + @Override public void onScreenTurningOn(int displayId) { notifyScreenTurningOn(displayId); @@ -3571,6 +3596,13 @@ public class WallpaperManagerService extends IWallpaperManager.Stub if (componentName != null && !componentName.equals(mImageWallpaper)) { // The requested component is not the static wallpaper service, so make sure it's // actually a wallpaper service. + if (mFallbackWallpaperComponent != null + && componentName.equals(mFallbackWallpaperComponent)) { + // The fallback wallpaper does not declare WallpaperService.SERVICE_INTERFACE + // action in its intent filter to prevent it from being listed in the wallpaper + // picker. And thus, use explicit intent to query the metadata. + intent = new Intent().setComponent(mFallbackWallpaperComponent); + } List<ResolveInfo> ris = mIPackageManager.queryIntentServices(intent, intent.resolveTypeIfNeeded(mContext.getContentResolver()), @@ -3707,6 +3739,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } if (newWallpaper.mWhich == (FLAG_SYSTEM | FLAG_LOCK)) { mLastWallpaper = newWallpaper; + mLastLockWallpaper = null; } else if (newWallpaper.mWhich == FLAG_SYSTEM) { mLastWallpaper = newWallpaper; } else if (newWallpaper.mWhich == FLAG_LOCK) { @@ -3916,13 +3949,49 @@ public class WallpaperManagerService extends IWallpaperManager.Stub if (mLastWallpaper == null) { return; } - if (supportsMultiDisplay(mLastWallpaper.connection)) { - final DisplayConnector connector = - mLastWallpaper.connection.getDisplayConnectorOrCreate(displayId); - if (connector == null) return; - connector.connectLocked(mLastWallpaper.connection, mLastWallpaper); - return; + if (enableConnectedDisplaysWallpaper()) { + int useFallbackWallpaperWhich = 0; + List<WallpaperData> wallpapers = new ArrayList<>(); + wallpapers.add(mLastWallpaper); + // If the system and the lock wallpapers are not the same, we should also + // establish a display connector to the lock wallpaper for this display. + if (mLastLockWallpaper != null && mLastWallpaper != mLastLockWallpaper) { + wallpapers.add(mLastLockWallpaper); + } + + for (int i = 0; i < wallpapers.size(); i++) { + WallpaperData wallpaper = wallpapers.get(i); + if (isWallpaperCompatibleForDisplay(displayId, wallpaper.connection)) { + final DisplayConnector connector = + wallpaper.connection.getDisplayConnectorOrCreate(displayId); + if (connector != null) { + connector.connectLocked(wallpaper.connection, wallpaper); + } else { + Slog.w(TAG, "Fail to connect to wallpaper for display id " + displayId + + " due to null connector. Use fallback wallpaper."); + useFallbackWallpaperWhich |= wallpaper.mWhich; + } + } else { + useFallbackWallpaperWhich |= wallpaper.mWhich; + } + } + + if (useFallbackWallpaperWhich == 0 + || mFallbackWallpaper == null + || mFallbackWallpaper.connection == null) { + return; + } + mFallbackWallpaper.mWhich = useFallbackWallpaperWhich; + } else { + if (isWallpaperCompatibleForDisplay(displayId, mLastWallpaper.connection)) { + final DisplayConnector connector = + mLastWallpaper.connection.getDisplayConnectorOrCreate(displayId); + if (connector == null) return; + connector.connectLocked(mLastWallpaper.connection, mLastWallpaper); + return; + } } + // System wallpaper does not support multiple displays, attach this display to // the fallback wallpaper. if (mFallbackWallpaper != null && mFallbackWallpaper @@ -3937,6 +4006,78 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } } + // This method may be called even if the display is not being removed from the system. + // This can be called when the display is removed, or when the display system decorations are + // removed to start mirroring. + private void onDisplayRemovedInternal(int displayId) { + synchronized (mLock) { + if (enableConnectedDisplaysWallpaper()) { + // There could be at most 2 wallpaper connections per display: + // 1. system & lock are the same: mLastWallpaper + // 2. system, lock are different: mLastWallpaper, mLastLockWallpaper + // 3. fallback used as both system & lock wallpaper: mFallbackWallpaper + // 4. fallback used as lock only wallpaper: mFallbackWallpaper, + // mLastWallpaper + // 5. fallback used as system only wallpaper: mFallbackWallpaper, + // mLastLockWallpaper + List<WallpaperData> pendingDisconnectWallpapers = new ArrayList<>(); + if (mLastWallpaper != null && mLastWallpaper.connection != null + && mLastWallpaper.connection.containsDisplay(displayId)) { + pendingDisconnectWallpapers.add(mLastWallpaper); + } + if (mLastLockWallpaper != null && mLastLockWallpaper.connection != null + && mLastLockWallpaper.connection.containsDisplay(displayId)) { + pendingDisconnectWallpapers.add(mLastLockWallpaper); + } + if (mFallbackWallpaper != null && mFallbackWallpaper.connection != null + && mFallbackWallpaper.connection.containsDisplay(displayId)) { + pendingDisconnectWallpapers.add(mFallbackWallpaper); + } + for (int i = 0; i < pendingDisconnectWallpapers.size(); i++) { + WallpaperData wallpaper = pendingDisconnectWallpapers.get(i); + DisplayConnector displayConnector = + wallpaper.connection.getDisplayConnectorOrCreate(displayId); + if (displayConnector == null) { + Slog.w(TAG, + "Fail to disconnect wallpaper upon display removes system " + + "decorations"); + return; + } + displayConnector.disconnectLocked(wallpaper.connection); + wallpaper.connection.removeDisplayConnector(displayId); + } + } else { + if (mLastWallpaper != null) { + WallpaperData targetWallpaper = null; + if (mLastWallpaper.connection != null + && mLastWallpaper.connection.containsDisplay(displayId)) { + targetWallpaper = mLastWallpaper; + } else if (mFallbackWallpaper != null + && mFallbackWallpaper.connection != null + && mFallbackWallpaper.connection.containsDisplay( + displayId)) { + targetWallpaper = mFallbackWallpaper; + } + if (targetWallpaper == null) return; + DisplayConnector connector = + targetWallpaper.connection.getDisplayConnectorOrCreate( + displayId); + if (connector == null) return; + connector.disconnectLocked(targetWallpaper.connection); + targetWallpaper.connection.removeDisplayConnector(displayId); + } + } + + mWallpaperDisplayHelper.removeDisplayData(displayId); + + for (int i = mColorsChangedListeners.size() - 1; i >= 0; i--) { + final SparseArray<RemoteCallbackList<IWallpaperManagerCallback>> callbacks = + mColorsChangedListeners.valueAt(i); + callbacks.delete(displayId); + } + } + } + void saveSettingsLocked(int userId) { TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG); t.traceBegin("WPMS.saveSettingsLocked-" + userId); @@ -4021,8 +4162,14 @@ public class WallpaperManagerService extends IWallpaperManager.Stub mFallbackWallpaper.allowBackup = false; mFallbackWallpaper.wallpaperId = makeWallpaperIdLocked(); mFallbackWallpaper.mBindSource = BindSource.INITIALIZE_FALLBACK; - bindWallpaperComponentLocked(mDefaultWallpaperComponent, true, false, - mFallbackWallpaper, null); + if (mFallbackWallpaperComponent == null) { + bindWallpaperComponentLocked(mDefaultWallpaperComponent, true, false, + mFallbackWallpaper, null); + } else { + mFallbackWallpaper.mWhich = FLAG_SYSTEM | FLAG_LOCK; + bindWallpaperComponentLocked(mFallbackWallpaperComponent, true, false, + mFallbackWallpaper, null); + } } } diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java index dd769173fb34..12f553426c80 100644 --- a/services/core/java/com/android/server/wm/AccessibilityController.java +++ b/services/core/java/com/android/server/wm/AccessibilityController.java @@ -58,7 +58,6 @@ import android.graphics.Matrix; import android.graphics.Path; import android.graphics.Point; import android.graphics.Rect; -import android.graphics.RectF; import android.graphics.Region; import android.os.Binder; import android.os.Build; @@ -69,7 +68,6 @@ import android.os.Looper; import android.os.Message; import android.os.Process; import android.os.SystemClock; -import android.util.ArraySet; import android.util.Pair; import android.util.Slog; import android.util.SparseArray; @@ -79,7 +77,6 @@ import android.view.Display; import android.view.MagnificationSpec; import android.view.Surface; import android.view.ViewConfiguration; -import android.view.WindowInfo; import android.view.WindowManager; import android.view.WindowManager.TransitionFlags; import android.view.WindowManager.TransitionType; @@ -557,9 +554,6 @@ final class AccessibilityController { private static final boolean DEBUG_WINDOW_TRANSITIONS = false; private static final boolean DEBUG_DISPLAY_SIZE = false; - private static final boolean DEBUG_LAYERS = false; - private static final boolean DEBUG_RECTANGLE_REQUESTED = false; - private static final boolean DEBUG_VIEWPORT_WINDOW = false; private final Rect mTempRect1 = new Rect(); private final Rect mTempRect2 = new Rect(); @@ -579,8 +573,6 @@ final class AccessibilityController { private final MagnificationCallbacks mCallbacks; private final UserContextChangedNotifier mUserContextChangedNotifier; - private final long mLongAnimationDuration; - private boolean mIsFullscreenMagnificationActivated = false; private final Region mMagnificationRegion = new Region(); private final Region mOldMagnificationRegion = new Region(); @@ -593,7 +585,6 @@ final class AccessibilityController { private final Point mScreenSize = new Point(); private final SparseArray<WindowState> mTempWindowStates = new SparseArray<WindowState>(); - private final RectF mTempRectF = new RectF(); private final Matrix mTempMatrix = new Matrix(); DisplayMagnifier(WindowManagerService windowManagerService, @@ -609,8 +600,6 @@ final class AccessibilityController { mUserContextChangedNotifier = new UserContextChangedNotifier(mHandler); mAccessibilityTracing = AccessibilityController.getAccessibilityControllerInternal(mService); - mLongAnimationDuration = mDisplayContext.getResources().getInteger( - com.android.internal.R.integer.config_longAnimTime); if (mDisplayContext.getResources().getConfiguration().isScreenRound()) { mCircularPath = new Path(); @@ -840,10 +829,6 @@ final class AccessibilityController { outMagnificationRegion.set(mMagnificationRegion); } - boolean isMagnifying() { - return mMagnificationSpec.scale > 1.0f; - } - void destroy() { if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) { mAccessibilityTracing.logTrace(LOG_TAG + ".destroy", FLAGS_MAGNIFICATION_CALLBACK); @@ -1172,12 +1157,6 @@ final class AccessibilityController { private static final boolean DEBUG = false; - private final Set<IBinder> mTempBinderSet = new ArraySet<>(); - - private final Region mTempRegion = new Region(); - - private final Region mTempRegion2 = new Region(); - private final WindowManagerService mService; private final Handler mHandler; @@ -1243,11 +1222,10 @@ final class AccessibilityController { Slog.i(LOG_TAG, "computeChangedWindows()"); } - List<WindowInfo> windows = null; final List<AccessibilityWindow> visibleWindows = new ArrayList<>(); final Point screenSize = new Point(); final int topFocusedDisplayId; - IBinder topFocusedWindowToken = null; + final IBinder topFocusedWindowToken; synchronized (mService.mGlobalLock) { final WindowState topFocusedWindowState = getTopFocusWindow(); diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 1d12c561f118..89b46bc4eba4 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -46,6 +46,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.app.WindowConfiguration.activityTypeToString; +import static android.app.WindowConfiguration.isFloating; import static android.app.admin.DevicePolicyResources.Drawables.Source.PROFILE_SWITCH_ANIMATION; import static android.app.admin.DevicePolicyResources.Drawables.Style.OUTLINE; import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON; @@ -3231,8 +3232,8 @@ final class ActivityRecord extends WindowToken { * 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) { + static boolean canBeUniversalResizeable(@NonNull ApplicationInfo appInfo, + WindowManagerService wms, boolean isLargeScreen, boolean forActivity) { if (appInfo.category == ApplicationInfo.CATEGORY_GAME) { return false; } @@ -8376,6 +8377,7 @@ final class ActivityRecord extends WindowToken { mConfigurationSeq = Math.max(++mConfigurationSeq, 1); getResolvedOverrideConfiguration().seq = mConfigurationSeq; + // TODO(b/392069771): Move to AppCompatSandboxingPolicy. // Sandbox max bounds by setting it to the activity bounds, if activity is letterboxed, or // has or will have mAppCompatDisplayInsets for size compat. Also forces an activity to be // sandboxed or not depending upon the configuration settings. @@ -8404,6 +8406,20 @@ final class ActivityRecord extends WindowToken { resolvedConfig.windowConfiguration.setMaxBounds(mTmpBounds); } + // Sandbox activity bounds in freeform to app bounds to force app to display within the + // container. This prevents UI cropping when activities can draw below insets which are + // normally excluded from appBounds before targetSDK < 35 + // (see ConfigurationContainer#applySizeOverrideIfNeeded). + if (isFloating(parentWindowingMode)) { + Rect appBounds = resolvedConfig.windowConfiguration.getAppBounds(); + if (appBounds == null || appBounds.isEmpty()) { + // When there is no override bounds, the activity will inherit the bounds from + // parent. + appBounds = mResolveConfigHint.mParentAppBoundsOverride; + } + resolvedConfig.windowConfiguration.setBounds(appBounds); + } + applySizeOverrideIfNeeded( mDisplayContent, info.applicationInfo, diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 8e4d4be693f8..c7d4467a6e98 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -103,6 +103,7 @@ import android.app.PendingIntent; import android.app.ProfilerInfo; import android.app.WaitResult; import android.app.WindowConfiguration; +import android.app.WindowConfiguration.WindowingMode; import android.app.compat.CompatChanges; import android.compat.annotation.ChangeId; import android.compat.annotation.Disabled; @@ -233,6 +234,7 @@ class ActivityStarter { // The task display area to launch the activity onto, barring any strong reason to do otherwise. private TaskDisplayArea mPreferredTaskDisplayArea; + @WindowingMode private int mPreferredWindowingMode; private Task mInTask; diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index d4f9c0901162..cf111cdbcc6a 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -2154,6 +2154,16 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } } + /** + * @return ehether the application could be universal resizeable on a large screen, + * ignoring any overrides + */ + @Override + public boolean canBeUniversalResizeable(@NonNull ApplicationInfo appInfo) { + return ActivityRecord.canBeUniversalResizeable(appInfo, mWindowManager, + /* isLargeScreen */ true, /* forActivity */ false); + } + @Override public void removeAllVisibleRecentTasks() { mAmInternal.enforceCallingPermission(REMOVE_TASKS, "removeAllVisibleRecentTasks()"); diff --git a/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java b/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java index 35fa39dab900..d994a1904a14 100644 --- a/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java +++ b/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java @@ -180,10 +180,7 @@ class AppCompatOrientationPolicy { return true; } - final AppCompatCameraPolicy cameraPolicy = AppCompatCameraPolicy - .getAppCompatCameraPolicy(mActivityRecord); - if (cameraPolicy != null - && cameraPolicy.isTreatmentEnabledForActivity(mActivityRecord)) { + if (AppCompatCameraPolicy.isTreatmentEnabledForActivity(mActivityRecord)) { Slog.w(TAG, "Ignoring orientation update to " + screenOrientationToString(requestedOrientation) + " due to camera compat treatment for " + mActivityRecord); diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 1bbae7f17308..a0d2d260b39e 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -3261,9 +3261,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } mWmService.mDisplayWindowSettings.setShouldShowSystemDecorsLocked(this, shouldShow); - if (shouldShow) { - mRootWindowContainer.startSystemDecorations(this, "onDisplayInfoChangeApplied"); - } else { + if (!shouldShow) { clearAllTasksOnDisplay(null); } } @@ -6471,7 +6469,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp return mRemoving; } - private void clearAllTasksOnDisplay(@Nullable Runnable clearTasksCallback) { + void clearAllTasksOnDisplay(@Nullable Runnable clearTasksCallback) { Task lastReparentedRootTask; mRootWindowContainer.mTaskSupervisor.beginDeferResume(); try { diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 319b48a89316..5b1619995529 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -67,6 +67,7 @@ import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED; import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ANIM; import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_SCREEN_ON; +import static com.android.server.display.feature.flags.Flags.enableDisplayContentModeManagement; import static com.android.server.policy.PhoneWindowManager.TOAST_WINDOW_TIMEOUT; import static com.android.server.policy.WindowManagerPolicy.TRANSIT_PREVIEW_DONE; import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.LID_ABSENT; @@ -746,6 +747,31 @@ public class DisplayPolicy { return mHasNavigationBar; } + void updateHasNavigationBarIfNeeded() { + if (!enableDisplayContentModeManagement()) { + Slog.e(TAG, "mHasNavigationBar shouldn't be updated when the flag is off."); + } + + if (mDisplayContent.isDefaultDisplay) { + return; + } + + final boolean hasNavigationBar = mDisplayContent.isSystemDecorationsSupported(); + if (mHasNavigationBar == hasNavigationBar) { + return; + } + + mHasNavigationBar = hasNavigationBar; + mHandler.post( + () -> { + final int displayId = getDisplayId(); + StatusBarManagerInternal statusBar = getStatusBarManagerInternal(); + if (statusBar != null) { + statusBar.setHasNavigationBar(displayId, mHasNavigationBar); + } + }); + } + public boolean hasStatusBar() { return mHasStatusBar; } @@ -1876,6 +1902,22 @@ public class DisplayPolicy { }); } + void notifyDisplayRemoveSystemDecorations() { + mHandler.post( + () -> { + final int displayId = getDisplayId(); + StatusBarManagerInternal statusBar = getStatusBarManagerInternal(); + if (statusBar != null) { + statusBar.onDisplayRemoveSystemDecorations(displayId); + } + final WallpaperManagerInternal wpMgr = + LocalServices.getService(WallpaperManagerInternal.class); + if (wpMgr != null) { + wpMgr.onDisplayRemoveSystemDecorations(displayId); + } + }); + } + /** * Return corner radius in pixels that should be used on windows in order to cover the display. * diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettings.java b/services/core/java/com/android/server/wm/DisplayWindowSettings.java index 4ae100857f55..28aa6eff911b 100644 --- a/services/core/java/com/android/server/wm/DisplayWindowSettings.java +++ b/services/core/java/com/android/server/wm/DisplayWindowSettings.java @@ -26,6 +26,8 @@ import static android.view.WindowManager.REMOVE_CONTENT_MODE_UNDEFINED; import static com.android.server.wm.DisplayContent.FORCE_SCALING_MODE_AUTO; import static com.android.server.wm.DisplayContent.FORCE_SCALING_MODE_DISABLED; +import static com.android.server.display.feature.flags.Flags.enableDisplayContentModeManagement; + import android.annotation.NonNull; import android.annotation.Nullable; import android.app.WindowConfiguration; @@ -244,11 +246,27 @@ class DisplayWindowSettings { } void setShouldShowSystemDecorsLocked(@NonNull DisplayContent dc, boolean shouldShow) { + final boolean changed = (shouldShow != shouldShowSystemDecorsLocked(dc)); + final DisplayInfo displayInfo = dc.getDisplayInfo(); final SettingsProvider.SettingsEntry overrideSettings = mSettingsProvider.getOverrideSettings(displayInfo); overrideSettings.mShouldShowSystemDecors = shouldShow; mSettingsProvider.updateOverrideSettings(displayInfo, overrideSettings); + + if (enableDisplayContentModeManagement()) { + if (dc.isDefaultDisplay || dc.isPrivate() || !changed) { + return; + } + + dc.getDisplayPolicy().updateHasNavigationBarIfNeeded(); + + if (shouldShow) { + mService.mRoot.startSystemDecorations(dc, "setShouldShowSystemDecorsLocked"); + } else { + dc.getDisplayPolicy().notifyDisplayRemoveSystemDecorations(); + } + } } boolean isHomeSupportedLocked(@NonNull DisplayContent dc) { diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java index bcfaa3947e74..59ca79c7ffc3 100644 --- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java @@ -361,7 +361,7 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { controlTarget = mDisplayContent.getImeHostOrFallback( ((InsetsControlTarget) imeInsetsTarget).getWindow()); - if (controlTarget != imeInsetsTarget) { + if (controlTarget != null && controlTarget != imeInsetsTarget) { ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_WM_SET_REMOTE_TARGET_IME_VISIBILITY); controlTarget.setImeInputTargetRequestedVisibility(imeVisible, statsToken); diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java index 4bcba13448e9..f465c95addb7 100644 --- a/services/core/java/com/android/server/wm/InsetsPolicy.java +++ b/services/core/java/com/android/server/wm/InsetsPolicy.java @@ -899,7 +899,9 @@ class InsetsPolicy { } @Override - public void notifyAnimationRunningStateChanged(boolean running) { + public void notifyAnimationRunningStateChanged(boolean running, + @InsetsController.AnimationType int animationType, + @InsetsType int insetsTypes) { mInsetsAnimationRunning = running; } } diff --git a/services/core/java/com/android/server/wm/LaunchParamsController.java b/services/core/java/com/android/server/wm/LaunchParamsController.java index ba1401ab3978..4f5c0c8ecf6e 100644 --- a/services/core/java/com/android/server/wm/LaunchParamsController.java +++ b/services/core/java/com/android/server/wm/LaunchParamsController.java @@ -28,6 +28,7 @@ import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier. import android.annotation.IntDef; import android.annotation.Nullable; import android.app.ActivityOptions; +import android.app.WindowConfiguration.WindowingMode; import android.content.pm.ActivityInfo.WindowLayout; import android.graphics.Rect; @@ -186,6 +187,7 @@ class LaunchParamsController { TaskDisplayArea mPreferredTaskDisplayArea; /** The windowing mode to be in. */ + @WindowingMode int mWindowingMode; /** Sets values back to default. {@link #isEmpty} will return {@code true} once called. */ diff --git a/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java b/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java index efc68aac0323..00e1c01bbadb 100644 --- a/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java +++ b/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java @@ -22,6 +22,7 @@ import static com.android.internal.protolog.WmProtoLogGroups.WM_ERROR; import android.media.projection.IMediaProjectionManager; import android.media.projection.IMediaProjectionWatcherCallback; +import android.media.projection.MediaProjectionEvent; import android.media.projection.MediaProjectionInfo; import android.os.Binder; import android.os.IBinder; @@ -84,6 +85,12 @@ public class ScreenRecordingCallbackController { public void onRecordingSessionSet(MediaProjectionInfo mediaProjectionInfo, ContentRecordingSession contentRecordingSession) { } + + @Override + public void onMediaProjectionEvent( + MediaProjectionEvent event, + MediaProjectionInfo mediaProjectionInfo, + ContentRecordingSession session) {} } ScreenRecordingCallbackController(WindowManagerService wms) { diff --git a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java index 5e1d7928e96d..eafc8be7bf77 100644 --- a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java +++ b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java @@ -319,7 +319,13 @@ class SnapshotPersistQueue { @Override void onQueuedLocked() { // Remove duplicate request. - mStoreQueueItems.remove(this); + mStoreQueueItems.removeIf(item -> { + if (item.equals(this) && item.mSnapshot != mSnapshot) { + item.mSnapshot.removeReference(TaskSnapshot.REFERENCE_PERSIST); + return true; + } + return false; + }); mStoreQueueItems.offer(this); } diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 37cc0d22c063..27683b2fcff2 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -1504,16 +1504,15 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { } // Update the input-sink (touch-blocking) state now that the animation is finished. - SurfaceControl.Transaction inputSinkTransaction = null; + boolean scheduleAnimation = false; for (int i = 0; i < mParticipants.size(); ++i) { final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord(); if (ar == null || !ar.isVisible() || ar.getParent() == null) continue; - if (inputSinkTransaction == null) { - inputSinkTransaction = ar.mWmService.mTransactionFactory.get(); - } - ar.mActivityRecordInputSink.applyChangesToSurfaceIfChanged(inputSinkTransaction); + scheduleAnimation = true; + ar.mActivityRecordInputSink.applyChangesToSurfaceIfChanged(ar.getPendingTransaction()); } - if (inputSinkTransaction != null) inputSinkTransaction.apply(); + // To apply pending transactions. + if (scheduleAnimation) mController.mAtm.mWindowManager.scheduleAnimationLocked(); // Always schedule stop processing when transition finishes because activities don't // stop while they are in a transition thus their stop could still be pending. diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java index 0ecd0251ca94..3b79c54f1c73 100644 --- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java +++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java @@ -89,8 +89,9 @@ class WallpaperWindowToken extends WindowToken { // Similar to Task.prepareSurfaces, outside of transitions we need to apply visibility // changes directly. In transitions the transition player will take care of applying the // visibility change. - if (!mTransitionController.inTransition(this)) { - getSyncTransaction().setVisibility(mSurfaceControl, isVisible()); + if (!mTransitionController.isCollecting(this) + && !mTransitionController.isPlayingTarget(this)) { + getPendingTransaction().setVisibility(mSurfaceControl, isVisible()); } } } diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java index c77b1d9a7bcf..7a8230f1f963 100644 --- a/services/core/java/com/android/server/wm/WindowManagerInternal.java +++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java @@ -41,6 +41,7 @@ import android.view.Display; import android.view.IInputFilter; import android.view.IRemoteAnimationFinishedCallback; import android.view.IWindow; +import android.view.InsetsController; import android.view.MagnificationSpec; import android.view.RemoteAnimationTarget; import android.view.Surface; @@ -469,6 +470,24 @@ public abstract class WindowManagerInternal { public abstract void getMagnificationRegion(int displayId, @NonNull Region magnificationRegion); /** + * Set by the autofill service to observe changes in the ime animations. + * + * @param listener The callbacks to invoke. + */ + public abstract void setImeInsetsAnimationChangeListener( + @Nullable ImeInsetsAnimationChangeListener listener); + + /** Listener for changes in ime insets animation */ + public interface ImeInsetsAnimationChangeListener { + + /** Notify on start of animation */ + void onAnimationStart(@InsetsController.AnimationType int animationType, int userId); + + /** Notify on end of animation */ + void onAnimationEnd(@InsetsController.AnimationType int animationType, int userId); + } + + /** * Sets a callback for observing which windows are touchable for the purposes * of accessibility on specified display. * diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 8410f9f313be..28ea3b0bf6ba 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static android.service.autofill.Flags.improveFillDialogAconfig; import static android.Manifest.permission.ACCESS_SURFACE_FLINGER; import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS; import static android.Manifest.permission.INPUT_CONSUMER; @@ -277,6 +278,7 @@ import android.view.InputApplicationHandle; import android.view.InputChannel; import android.view.InputDevice; import android.view.InputWindowHandle; +import android.view.InsetsController; import android.view.InsetsFrameProvider; import android.view.InsetsSourceControl; import android.view.InsetsState; @@ -309,6 +311,7 @@ import android.window.ActivityWindowInfo; import android.window.AddToSurfaceSyncGroupResult; import android.window.ClientWindowFrames; import android.window.ConfigurationChangeSetting; +import android.window.DesktopModeFlags; import android.window.IGlobalDragListener; import android.window.IScreenRecordingCallback; import android.window.ISurfaceSyncGroupCompletedListener; @@ -792,6 +795,9 @@ public class WindowManagerService extends IWindowManager.Stub final TrustedPresentationListenerController mTrustedPresentationListenerController = new TrustedPresentationListenerController(); + private WindowManagerInternal.ImeInsetsAnimationChangeListener + mImeInsetsAnimationChangeListener; + @VisibleForTesting final class SettingsObserver extends ContentObserver { private final Uri mDisplayInversionEnabledUri = @@ -820,6 +826,8 @@ public class WindowManagerService extends IWindowManager.Stub DEVELOPMENT_WM_DISPLAY_SETTINGS_PATH); private final Uri mMaximumObscuringOpacityForTouchUri = Settings.Global.getUriFor( Settings.Global.MAXIMUM_OBSCURING_OPACITY_FOR_TOUCH); + private final Uri mDevelopmentOverrideDesktopExperienceUri = Settings.Global.getUriFor( + Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_EXPERIENCE_FEATURES); public SettingsObserver() { super(new Handler()); @@ -847,6 +855,8 @@ public class WindowManagerService extends IWindowManager.Stub UserHandle.USER_ALL); resolver.registerContentObserver(mMaximumObscuringOpacityForTouchUri, false, this, UserHandle.USER_ALL); + resolver.registerContentObserver(mDevelopmentOverrideDesktopExperienceUri, false, this, + UserHandle.USER_ALL); } @Override @@ -890,6 +900,11 @@ public class WindowManagerService extends IWindowManager.Stub return; } + if (mDevelopmentOverrideDesktopExperienceUri.equals(uri)) { + updateDevelopmentOverrideDesktopExperience(); + return; + } + @UpdateAnimationScaleMode final int mode; if (mWindowAnimationScaleUri.equals(uri)) { @@ -956,6 +971,16 @@ public class WindowManagerService extends IWindowManager.Stub mAtmService.mForceResizableActivities = forceResizable; } + void updateDevelopmentOverrideDesktopExperience() { + ContentResolver resolver = mContext.getContentResolver(); + final int overrideDesktopMode = Settings.Global.getInt(resolver, + Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_EXPERIENCE_FEATURES, + DesktopModeFlags.ToggleOverride.OVERRIDE_UNSET.getSetting()); + + SystemProperties.set(DesktopModeFlags.SYSTEM_PROPERTY_NAME, + Integer.toString(overrideDesktopMode)); + } + void updateDevEnableNonResizableMultiWindow() { ContentResolver resolver = mContext.getContentResolver(); final boolean devEnableNonResizableMultiWindow = Settings.Global.getInt(resolver, @@ -4309,7 +4334,8 @@ public class WindowManagerService extends IWindowManager.Stub } } - boolean getIgnoreOrientationRequest(int displayId) { + @Override + public boolean getIgnoreOrientationRequest(int displayId) { synchronized (mGlobalLock) { final DisplayContent display = mRoot.getDisplayContent(displayId); if (display == null) { @@ -8601,6 +8627,14 @@ public class WindowManagerService extends IWindowManager.Stub // WMS.takeAssistScreenshot takes care of the locking. return WindowManagerService.this.takeAssistScreenshot(windowTypesToExclude); } + + @Override + public void setImeInsetsAnimationChangeListener( + @Nullable WindowManagerInternal.ImeInsetsAnimationChangeListener listener) { + synchronized (mGlobalLock) { + mImeInsetsAnimationChangeListener = listener; + } + } } private final class ImeTargetVisibilityPolicyImpl extends ImeTargetVisibilityPolicy { @@ -10158,6 +10192,24 @@ public class WindowManagerService extends IWindowManager.Stub } } + @Override + public void notifyImeInsetsAnimationStateChanged( + boolean running, @InsetsController.AnimationType int animationType) { + if (improveFillDialogAconfig()) { + synchronized (mGlobalLock) { + if (mImeInsetsAnimationChangeListener == null) { + return; + } + if (running) { + mImeInsetsAnimationChangeListener.onAnimationStart( + animationType, mCurrentUserId); + } else { + mImeInsetsAnimationChangeListener.onAnimationEnd(animationType, mCurrentUserId); + } + } + } + } + boolean getDisableSecureWindows() { return mDisableSecureWindows; } diff --git a/services/core/jni/com_android_server_utils_AnrTimer.cpp b/services/core/jni/com_android_server_utils_AnrTimer.cpp index 2add5b09f15b..24909ac6150b 100644 --- a/services/core/jni/com_android_server_utils_AnrTimer.cpp +++ b/services/core/jni/com_android_server_utils_AnrTimer.cpp @@ -101,6 +101,9 @@ nsecs_t now() { return systemTime(SYSTEM_TIME_MONOTONIC); } +// The current process. This is cached here on startup. +const pid_t sThisProcess = getpid(); + // Return true if the process exists and false if we cannot know. bool processExists(pid_t pid) { char path[PATH_MAX]; @@ -726,7 +729,7 @@ class AnrTimerService::Timer { uid(uid), timeout(timeout), extend(extend), - freeze(pid != 0 && freeze), + freeze(freeze), split(trace.earlyTimeout), action(trace.action), status(Running), @@ -1188,8 +1191,11 @@ const char* AnrTimerService::statusString(Status s) { } AnrTimerService::timer_id_t AnrTimerService::start(int pid, int uid, nsecs_t timeout) { + // Use the freezer only if the pid is not 0 (a nonsense value) and the pid is not self. + // Freezing the current process is a fatal error. + bool useFreezer = freeze_ && (pid != 0) && (pid != sThisProcess); AutoMutex _l(lock_); - Timer t(pid, uid, timeout, extend_, freeze_, tracer_.getConfig(pid)); + Timer t(pid, uid, timeout, extend_, useFreezer, tracer_.getConfig(pid)); insertLocked(t); t.start(); counters_.started++; diff --git a/services/core/jni/com_android_server_vibrator_VibratorController.cpp b/services/core/jni/com_android_server_vibrator_VibratorController.cpp index 0ecc0a8a9524..abd4cd25cf68 100644 --- a/services/core/jni/com_android_server_vibrator_VibratorController.cpp +++ b/services/core/jni/com_android_server_vibrator_VibratorController.cpp @@ -435,8 +435,8 @@ static jlong vibratorPerformPwleV2Effect(JNIEnv* env, jclass /* clazz */, jlong auto composePwleV2Fn = [&composite, &callback](vibrator::HalWrapper* hal) { return hal->composePwleV2(composite, callback); }; - auto result = wrapper->halCall<void>(composePwleV2Fn, "composePwleV2"); - return result.isOk(); + auto result = wrapper->halCall<std::chrono::milliseconds>(composePwleV2Fn, "composePwleV2"); + return result.isOk() ? result.value().count() : (result.isUnsupported() ? 0 : -1); } static void vibratorAlwaysOnEnable(JNIEnv* env, jclass /* clazz */, jlong ptr, jlong id, diff --git a/services/core/xsd/Android.bp b/services/core/xsd/Android.bp index 6a50d3834355..8b7bdf5cece2 100644 --- a/services/core/xsd/Android.bp +++ b/services/core/xsd/Android.bp @@ -30,6 +30,14 @@ xsd_config { } xsd_config { + name: "display-topology", + srcs: ["display-topology/display-topology.xsd"], + api_dir: "display-topology/schema", + package_name: "com.android.server.display.topology", + gen_writer: true, +} + +xsd_config { name: "display-device-config", srcs: ["display-device-config/display-device-config.xsd"], api_dir: "display-device-config/schema", diff --git a/services/core/xsd/display-topology/OWNERS b/services/core/xsd/display-topology/OWNERS new file mode 100644 index 000000000000..6ce1ee4d3de2 --- /dev/null +++ b/services/core/xsd/display-topology/OWNERS @@ -0,0 +1 @@ +include /services/core/java/com/android/server/display/OWNERS diff --git a/services/core/xsd/display-topology/display-topology.xsd b/services/core/xsd/display-topology/display-topology.xsd new file mode 100644 index 000000000000..00f766fe018c --- /dev/null +++ b/services/core/xsd/display-topology/display-topology.xsd @@ -0,0 +1,65 @@ +<?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. +--> + +<!-- + This defines the format of the XML file used to define how displays are arranged + in topologies. + It is parsed in com/android/server/display/PersistentTopologyStore.java + More information on display topology can be found in DisplayTopology.java +--> +<xs:schema version="2.0" + elementFormDefault="qualified" + xmlns:xs="http://www.w3.org/2001/XMLSchema"> + <xs:simpleType name="position"> + <xs:restriction base="xs:string"> + <xs:enumeration value="left"/> + <xs:enumeration value="top"/> + <xs:enumeration value="right"/> + <xs:enumeration value="bottom"/> + </xs:restriction> + </xs:simpleType> + <xs:complexType name="children"> + <xs:sequence> + <xs:element type="display" name="display" maxOccurs="unbounded" minOccurs="0"/> + </xs:sequence> + </xs:complexType> + <xs:complexType name="display"> + <xs:sequence> + <xs:element type="position" name="position" /> + <xs:element type="xs:float" name="offset"/> + <xs:element type="children" name="children" /> + </xs:sequence> + <xs:attribute type="xs:string" name="id" use="required"/> + <xs:attribute type="xs:boolean" name="primary"/> + </xs:complexType> + <xs:complexType name="topology"> + <xs:sequence> + <xs:element type="display" name="display"/> + </xs:sequence> + <xs:attribute type="xs:string" name="id" use="required"/> + <xs:attribute type="xs:int" name="order" use="required"/> + <xs:attribute type="xs:boolean" name="immutable"/> + </xs:complexType> + <xs:element name="displayTopologyState"> + <xs:complexType> + <xs:sequence> + <xs:element type="topology" name="topology" maxOccurs="1000" minOccurs="0"/> + </xs:sequence> + <xs:attribute type="xs:int" name="version" use="required"/> + </xs:complexType> + </xs:element> +</xs:schema> diff --git a/services/core/xsd/display-topology/schema/current.txt b/services/core/xsd/display-topology/schema/current.txt new file mode 100644 index 000000000000..eb59e9ec5f7b --- /dev/null +++ b/services/core/xsd/display-topology/schema/current.txt @@ -0,0 +1,64 @@ +// Signature format: 2.0 +package com.android.server.display.topology { + + public class Children { + ctor public Children(); + method public java.util.List<com.android.server.display.topology.Display> getDisplay(); + } + + public class Display { + ctor public Display(); + method public com.android.server.display.topology.Children getChildren(); + method public String getId(); + method public float getOffset(); + method public com.android.server.display.topology.Position getPosition(); + method public boolean getPrimary(); + method public void setChildren(com.android.server.display.topology.Children); + method public void setId(String); + method public void setOffset(float); + method public void setPosition(com.android.server.display.topology.Position); + method public void setPrimary(boolean); + } + + public class DisplayTopologyState { + ctor public DisplayTopologyState(); + method public java.util.List<com.android.server.display.topology.Topology> getTopology(); + method public int getVersion(); + method public void setVersion(int); + } + + public enum Position { + method public String getRawName(); + enum_constant public static final com.android.server.display.topology.Position bottom; + enum_constant public static final com.android.server.display.topology.Position left; + enum_constant public static final com.android.server.display.topology.Position right; + enum_constant public static final com.android.server.display.topology.Position top; + } + + public class Topology { + ctor public Topology(); + method public com.android.server.display.topology.Display getDisplay(); + method public String getId(); + method public boolean getImmutable(); + method public int getOrder(); + method public void setDisplay(com.android.server.display.topology.Display); + method public void setId(String); + method public void setImmutable(boolean); + method public void setOrder(int); + } + + public class XmlParser { + ctor public XmlParser(); + method public static com.android.server.display.topology.DisplayTopologyState read(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException; + method public static String readText(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; + method public static void skip(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; + } + + public class XmlWriter implements java.io.Closeable { + ctor public XmlWriter(java.io.PrintWriter); + method public void close(); + method public static void write(com.android.server.display.topology.XmlWriter, com.android.server.display.topology.DisplayTopologyState) throws java.io.IOException; + } + +} + diff --git a/services/core/xsd/display-topology/schema/last_current.txt b/services/core/xsd/display-topology/schema/last_current.txt new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/services/core/xsd/display-topology/schema/last_current.txt diff --git a/services/core/xsd/display-topology/schema/last_removed.txt b/services/core/xsd/display-topology/schema/last_removed.txt new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/services/core/xsd/display-topology/schema/last_removed.txt diff --git a/services/core/xsd/display-topology/schema/removed.txt b/services/core/xsd/display-topology/schema/removed.txt new file mode 100644 index 000000000000..d802177e249b --- /dev/null +++ b/services/core/xsd/display-topology/schema/removed.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/feature/Android.bp b/services/foldables/devicestateprovider/src/com/android/server/policy/feature/Android.bp index 6393e11b7432..1db9e8d545e4 100644 --- a/services/foldables/devicestateprovider/src/com/android/server/policy/feature/Android.bp +++ b/services/foldables/devicestateprovider/src/com/android/server/policy/feature/Android.bp @@ -1,7 +1,7 @@ aconfig_declarations { name: "device_state_flags", package: "com.android.server.policy.feature.flags", - container: "system", + container: "system_ext", srcs: [ "device_state_flags.aconfig", ], diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig b/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig index da2e5ee37c1a..bbbbe5db91dd 100644 --- a/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig +++ b/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig @@ -1,5 +1,5 @@ package: "com.android.server.policy.feature.flags" -container: "system" +container: "system_ext" flag { name: "enable_dual_display_blocking" diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 25e9f8a38f89..c974d9e1dc87 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -1799,7 +1799,7 @@ public final class SystemServer implements Dumpable { mSystemServiceManager.startService(LogcatManagerService.class); t.traceEnd(); - if (!isWatch && !isTv && !isAutomotive + if (!isWatch && !isTv && !isAutomotive && !isDesktop && android.security.Flags.aflApi()) { t.traceBegin("StartIntrusionDetectionService"); mSystemServiceManager.startService(IntrusionDetectionService.class); diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayBackupHelperTest.kt b/services/tests/displayservicetests/src/com/android/server/display/DisplayBackupHelperTest.kt new file mode 100644 index 000000000000..26060a406aa0 --- /dev/null +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayBackupHelperTest.kt @@ -0,0 +1,86 @@ +/* + * Copyright 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display + +import androidx.test.filters.SmallTest +import android.hardware.display.DisplayManagerInternal +import android.util.AtomicFileOutputStream +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever +import org.mockito.kotlin.verify +import org.mockito.kotlin.never + +@SmallTest +class DisplayBackupHelperTest { + private val mockInjector = mock<DisplayBackupHelper.Injector>() + private val mockDmsInternal = mock<DisplayManagerInternal>() + private val mockWriteTopologyFile = mock<AtomicFileOutputStream>() + private val byteArray = byteArrayOf(0b00000001, 0b00000010, 0b00000011) + private val helper = createBackupHelper(0, byteArray) + + @Test + fun testBackupDisplayReturnsBytes() { + assertThat(helper.getBackupPayload("display")).isEqualTo(byteArray) + } + + @Test + fun testBackupSomethingReturnsNull() { + assertThat(helper.getBackupPayload("something")).isNull() + } + + @Test + fun testBackupDisplayReturnsNullWhenFlagDisabled() { + whenever(mockInjector.isDisplayTopologyFlagEnabled()).thenReturn(false) + assertThat(helper.getBackupPayload("display")).isNull() + } + + @Test + fun testRestoreDisplay() { + helper.applyRestoredPayload("display", byteArray) + verify(mockWriteTopologyFile).write(byteArray) + verify(mockWriteTopologyFile).markSuccess() + verify(mockDmsInternal).reloadTopologies(0) + } + + @Test + fun testRestoreSomethingDoesNothing() { + helper.applyRestoredPayload("something", byteArray) + verify(mockWriteTopologyFile, never()).write(byteArray) + verify(mockWriteTopologyFile, never()).markSuccess() + verify(mockDmsInternal, never()).reloadTopologies(0) + } + + @Test + fun testRestoreDisplayDoesNothingWhenFlagDisabled() { + whenever(mockInjector.isDisplayTopologyFlagEnabled()).thenReturn(false) + helper.applyRestoredPayload("display", byteArray) + verify(mockWriteTopologyFile, never()).write(byteArray) + verify(mockWriteTopologyFile, never()).markSuccess() + verify(mockDmsInternal, never()).reloadTopologies(0) + } + + fun createBackupHelper(userId: Int, topologyToBackup: ByteArray): DisplayBackupHelper { + whenever(mockInjector.getDisplayManagerInternal()).thenReturn(mockDmsInternal) + whenever(mockInjector.readTopologyFile(userId)).thenReturn(topologyToBackup) + whenever(mockInjector.writeTopologyFile(userId)).thenReturn(mockWriteTopologyFile) + whenever(mockInjector.isDisplayTopologyFlagEnabled()).thenReturn(true) + + return DisplayBackupHelper(userId, mockInjector) + } +}
\ No newline at end of file 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 e8b28ac19a85..a0e18ff15770 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -31,6 +31,7 @@ import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_C import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED; +import static android.hardware.display.DisplayManagerGlobal.INTERNAL_EVENT_FLAG_TOPOLOGY_UPDATED; import static android.hardware.display.HdrConversionMode.HDR_CONVERSION_SYSTEM; import static android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS; import static android.provider.Settings.Secure.MIRROR_BUILT_IN_DISPLAY; @@ -413,6 +414,9 @@ public class DisplayManagerServiceTest { .setStrictness(Strictness.LENIENT) .spyStatic(SystemProperties.class) .build(); + + private int mUniqueIdCount = 0; + @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); @@ -759,6 +763,83 @@ public class DisplayManagerServiceTest { } @Test + public void testCreateVirtualDisplayStealTopFocusDisabled() throws RemoteException { + DisplayManagerService displayManager = + new DisplayManagerService(mContext, mBasicInjector); + registerDefaultDisplays(displayManager); + + // This is effectively the DisplayManager service published to ServiceManager. + DisplayManagerService.BinderService bs = displayManager.new BinderService(); + + String uniqueId = "uniqueId --- Steal Top Focus Test"; + int width = 600; + int height = 800; + int dpi = 320; + int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_STEAL_TOP_FOCUS_DISABLED + | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_FOCUS + | DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED; + + when(mContext.checkCallingPermission(ADD_TRUSTED_DISPLAY)).thenReturn( + PackageManager.PERMISSION_GRANTED); + when(mMockAppToken.asBinder()).thenReturn(mMockAppToken); + final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder( + VIRTUAL_DISPLAY_NAME, width, height, dpi); + builder.setFlags(flags); + builder.setUniqueId(uniqueId); + int displayId = bs.createVirtualDisplay(builder.build(), /* callback= */ mMockAppToken, + /* projection= */ null, PACKAGE_NAME); + verify(mMockProjectionService, never()).setContentRecordingSession(any(), + nullable(IMediaProjection.class)); + + performTraversalInternal(displayManager); + + // flush the handler + displayManager.getDisplayHandler().runWithScissors(() -> {}, /* now= */ 0); + + DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId); + assertNotNull(ddi); + assertTrue((ddi.flags & DisplayDeviceInfo.FLAG_STEAL_TOP_FOCUS_DISABLED) != 0); + } + + @Test + public void testCreateVirtualDisplayOwnFocus_nonOwnFocusDisplay() throws RemoteException { + DisplayManagerService displayManager = + new DisplayManagerService(mContext, mBasicInjector); + registerDefaultDisplays(displayManager); + + // This is effectively the DisplayManager service published to ServiceManager. + DisplayManagerService.BinderService bs = displayManager.new BinderService(); + + String uniqueId = "uniqueId --- Steal Top Focus Test -- nonOwnFocusDisplay"; + int width = 600; + int height = 800; + int dpi = 320; + int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_STEAL_TOP_FOCUS_DISABLED + | DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED; + + when(mContext.checkCallingPermission(ADD_TRUSTED_DISPLAY)).thenReturn( + PackageManager.PERMISSION_GRANTED); + when(mMockAppToken.asBinder()).thenReturn(mMockAppToken); + final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder( + VIRTUAL_DISPLAY_NAME, width, height, dpi); + builder.setFlags(flags); + builder.setUniqueId(uniqueId); + int displayId = bs.createVirtualDisplay(builder.build(), /* callback= */ mMockAppToken, + /* projection= */ null, PACKAGE_NAME); + verify(mMockProjectionService, never()).setContentRecordingSession(any(), + nullable(IMediaProjection.class)); + + performTraversalInternal(displayManager); + + // flush the handler + displayManager.getDisplayHandler().runWithScissors(() -> {}, /* now= */ 0); + + DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId); + assertNotNull(ddi); + assertTrue((ddi.flags & DisplayDeviceInfo.FLAG_STEAL_TOP_FOCUS_DISABLED) == 0); + } + + @Test public void testCreateVirtualDisplayOwnFocus_checkDisplayDeviceInfo() throws RemoteException { DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector); @@ -2126,11 +2207,12 @@ public class DisplayManagerServiceTest { @Test public void test_displayChangedNotified_displayInfoFramerateOverridden() { + when(mMockFlags.isFramerateOverrideTriggersRrCallbacksEnabled()).thenReturn(false); + DisplayManagerService displayManager = - new DisplayManagerService(mContext, mShortMockedInjector); + new DisplayManagerService(mContext, mBasicInjector); DisplayManagerService.BinderService displayManagerBinderService = displayManager.new BinderService(); - when(mMockFlags.isFramerateOverrideTriggersRrCallbacksEnabled()).thenReturn(false); registerDefaultDisplays(displayManager); displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY); @@ -3786,6 +3868,7 @@ public class DisplayManagerServiceTest { public void testSetDisplayTopology() { manageDisplaysPermission(/* granted= */ true); when(mMockFlags.isDisplayTopologyEnabled()).thenReturn(true); + when(mMockFlags.isDisplayContentModeManagementEnabled()).thenReturn(true); DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector); DisplayManagerInternal localService = displayManager.new LocalService(); DisplayManagerService.BinderService displayManagerBinderService = @@ -3814,6 +3897,7 @@ public class DisplayManagerServiceTest { public void testShouldNotifyTopologyChanged() { manageDisplaysPermission(/* granted= */ true); when(mMockFlags.isDisplayTopologyEnabled()).thenReturn(true); + when(mMockFlags.isDisplayContentModeManagementEnabled()).thenReturn(true); DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector); DisplayManagerService.BinderService displayManagerBinderService = displayManager.new BinderService(); @@ -3822,19 +3906,22 @@ public class DisplayManagerServiceTest { FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback(); displayManagerBinderService.registerCallbackWithEventMask(callback, - DisplayManagerGlobal.INTERNAL_EVENT_FLAG_TOPOLOGY_UPDATED); + INTERNAL_EVENT_FLAG_TOPOLOGY_UPDATED); waitForIdleHandler(handler); - displayManagerBinderService.setDisplayTopology(new DisplayTopology()); - waitForIdleHandler(handler); - - assertThat(callback.receivedEvents()).containsExactly(TOPOLOGY_CHANGED_EVENT); + var topology = initDisplayTopology(displayManager, displayManagerBinderService, callback, + handler, /*shouldEmitTopologyChangeEvent=*/ true); + callback.clear(); + callback.expectsEvent(TOPOLOGY_CHANGED_EVENT); + displayManagerBinderService.setDisplayTopology(topology); + callback.waitForExpectedEvent(); } @Test public void testShouldNotNotifyTopologyChanged_WhenClientIsNotSubscribed() { manageDisplaysPermission(/* granted= */ true); when(mMockFlags.isDisplayTopologyEnabled()).thenReturn(true); + when(mMockFlags.isDisplayContentModeManagementEnabled()).thenReturn(true); DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector); DisplayManagerService.BinderService displayManagerBinderService = displayManager.new BinderService(); @@ -3847,9 +3934,13 @@ public class DisplayManagerServiceTest { STANDARD_DISPLAY_EVENTS); waitForIdleHandler(handler); - displayManagerBinderService.setDisplayTopology(new DisplayTopology()); + var topology = initDisplayTopology(displayManager, displayManagerBinderService, callback, + handler, /*shouldEmitTopologyChangeEvent=*/ false); + callback.clear(); + callback.expectsEvent(TOPOLOGY_CHANGED_EVENT); // should not happen + displayManagerBinderService.setDisplayTopology(topology); + callback.waitForNonExpectedEvent(); // checks that event did not happen waitForIdleHandler(handler); - assertThat(callback.receivedEvents()).isEmpty(); } @@ -4527,6 +4618,7 @@ public class DisplayManagerServiceTest { new float[0], new int[0]); } displayDeviceInfo.name = "" + displayType; + displayDeviceInfo.uniqueId = "uniqueId" + mUniqueIdCount++; displayDeviceInfo.modeId = 1; displayDeviceInfo.type = displayType; displayDeviceInfo.renderFrameRate = displayDeviceInfo.supportedModes[0].getRefreshRate(); @@ -4599,6 +4691,42 @@ public class DisplayManagerServiceTest { } } + private DisplayTopology initDisplayTopology(DisplayManagerService displayManager, + DisplayManagerService.BinderService displayManagerBinderService, + FakeDisplayManagerCallback callback, + Handler handler, boolean shouldEmitTopologyChangeEvent) { + Settings.Global.putInt(mContext.getContentResolver(), + DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, 1); + callback.expectsEvent(TOPOLOGY_CHANGED_EVENT); + FakeDisplayDevice displayDevice0 = + createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_INTERNAL); + int displayId0 = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService, + displayDevice0); + if (shouldEmitTopologyChangeEvent) { + callback.waitForExpectedEvent(); + } else { + callback.waitForNonExpectedEvent(); + } + callback.clear(); + + callback.expectsEvent(TOPOLOGY_CHANGED_EVENT); + FakeDisplayDevice displayDevice1 = createFakeDisplayDevice(displayManager, + new float[]{60f}, Display.TYPE_OVERLAY); + int displayId1 = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService, + displayDevice1); + waitForIdleHandler(handler); + if (shouldEmitTopologyChangeEvent) { + callback.waitForExpectedEvent(); + } else { + callback.waitForNonExpectedEvent(); + } + + var topology = new DisplayTopology(); + topology.addDisplay(displayId0, 2048, 800); + topology.addDisplay(displayId1, 1920, 1080); + return topology; + } + private static class FakeDisplayManagerCallback extends IDisplayManagerCallback.Stub implements DisplayManagerInternal.DisplayGroupListener { int mDisplayId; 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 b09947aaf2ad..04dff1d36495 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt @@ -18,6 +18,7 @@ package com.android.server.display import android.hardware.display.DisplayTopology import android.hardware.display.DisplayTopology.pxToDp +import android.util.SparseArray import android.view.Display import android.view.DisplayInfo import com.google.common.truth.Truth.assertThat @@ -37,9 +38,11 @@ class DisplayTopologyCoordinatorTest { private val displayInfo = DisplayInfo() private val topologyChangeExecutor = Runnable::run + private val mockTopologyStore = mock<DisplayTopologyStore>() private val mockTopology = mock<DisplayTopology>() private val mockTopologyCopy = mock<DisplayTopology>() private val mockIsExtendedDisplayEnabled = mock<() -> Boolean>() + private val mockTopologySavedCallback = mock<() -> Unit>() private val mockTopologyChangedCallback = mock<(DisplayTopology) -> Unit>() @Before @@ -51,11 +54,17 @@ class DisplayTopologyCoordinatorTest { val injector = object : DisplayTopologyCoordinator.Injector() { override fun getTopology() = mockTopology + override fun createTopologyStore( + displayIdToUniqueId: SparseArray<String>, + uniqueIdToDisplayId: MutableMap<String, Int> + ) = + mockTopologyStore } whenever(mockIsExtendedDisplayEnabled()).thenReturn(true) whenever(mockTopology.copy()).thenReturn(mockTopologyCopy) coordinator = DisplayTopologyCoordinator(injector, mockIsExtendedDisplayEnabled, - mockTopologyChangedCallback, topologyChangeExecutor, DisplayManagerService.SyncRoot()) + mockTopologyChangedCallback, topologyChangeExecutor, DisplayManagerService.SyncRoot(), + mockTopologySavedCallback) } @Test @@ -66,6 +75,7 @@ class DisplayTopologyCoordinatorTest { val heightDp = pxToDp(displayInfo.logicalHeight.toFloat(), displayInfo.logicalDensityDpi) verify(mockTopology).addDisplay(displayInfo.displayId, widthDp, heightDp) verify(mockTopologyChangedCallback).invoke(mockTopologyCopy) + verify(mockTopologyStore).restoreTopology(mockTopology) } @Test @@ -76,6 +86,7 @@ class DisplayTopologyCoordinatorTest { verify(mockTopology, never()).addDisplay(anyInt(), anyFloat(), anyFloat()) verify(mockTopologyChangedCallback, never()).invoke(any()) + verify(mockTopologyStore, never()).restoreTopology(any()) } @Test @@ -86,6 +97,7 @@ class DisplayTopologyCoordinatorTest { verify(mockTopology, never()).addDisplay(anyInt(), anyFloat(), anyFloat()) verify(mockTopologyChangedCallback, never()).invoke(any()) + verify(mockTopologyStore, never()).restoreTopology(any()) } @Test @@ -115,6 +127,7 @@ class DisplayTopologyCoordinatorTest { coordinator.onDisplayRemoved(Display.DEFAULT_DISPLAY) verify(mockTopologyChangedCallback).invoke(mockTopologyCopy) + verify(mockTopologyStore).restoreTopology(mockTopology) } @Test @@ -124,6 +137,7 @@ class DisplayTopologyCoordinatorTest { coordinator.onDisplayRemoved(Display.DEFAULT_DISPLAY) verify(mockTopologyChangedCallback, never()).invoke(any()) + verify(mockTopologyStore, never()).restoreTopology(any()) } @Test @@ -136,10 +150,12 @@ class DisplayTopologyCoordinatorTest { val topology = mock<DisplayTopology>() val topologyCopy = mock<DisplayTopology>() whenever(topology.copy()).thenReturn(topologyCopy) - + whenever(mockTopologyStore.saveTopology(topology)).thenReturn(true) coordinator.topology = topology verify(topology).normalize() verify(mockTopologyChangedCallback).invoke(topologyCopy) + verify(mockTopologyStore).saveTopology(topology) + verify(mockTopologySavedCallback).invoke() } }
\ No newline at end of file diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyXmlStoreTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyXmlStoreTest.java new file mode 100644 index 000000000000..48822821ba01 --- /dev/null +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyXmlStoreTest.java @@ -0,0 +1,388 @@ +/* + * Copyright 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display; + + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import android.graphics.PointF; +import android.hardware.display.DisplayTopology; +import android.util.AtomicFilePrintWriter; +import android.util.SparseArray; + +import androidx.test.filters.SmallTest; + +import com.google.common.io.CharSource; +import com.google.testing.junit.testparameterinjector.TestParameterInjector; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; + +/** + * Tests for {@link DisplayTopologyXmlStore} + * Run: atest PersistentTopologyStoreTest + */ +@SmallTest +@RunWith(TestParameterInjector.class) +public class DisplayTopologyXmlStoreTest { + private static final String NO_TOPOLOGIES = """ + <?xml version="1.0" encoding="utf-8"?> + <displayTopologyState version="1"/> + """; + + private static final String SIMPLE_TOPOLOGY = """ + <?xml version="1.0" encoding="utf-8"?> + <displayTopologyState version="1"> + <topology id="uniqueid0|uniqueid1" order="0"> + <display id="uniqueid0" primary="true"> + <position>left</position> + <offset>0.0</offset> + <children> + <display id="uniqueid1" primary="false"> + <position>top</position> + <offset>-560.0</offset> + <children> + </children> + </display> + </children> + </display> + </topology> + </displayTopologyState> + """; + + private static final String IMMUTABLE_TOPOLOGY = """ + <?xml version="1.0" encoding="utf-8"?> + <displayTopologyState version="1"> + <topology id="uniqueid10|uniqueid11" order="0" immutable="true"> + <display id="uniqueid10" primary="true"> + <position>left</position> + <offset>0.0</offset> + <children> + <display id="uniqueid11" primary="false"> + <position>top</position> + <offset>-560.0</offset> + <children> + </children> + </display> + </children> + </display> + </topology> + </displayTopologyState> + """; + + private static final String MULTIPLE_TOPOLOGIES = """ + <?xml version="1.0" encoding="utf-8"?> + <displayTopologyState version="1"> + <topology id="uniqueid0|uniqueid1" order="0"> + <display id="uniqueid0" primary="true"> + <position>left</position> + <offset>0.0</offset> + <children> + <display id="uniqueid1" primary="false"> + <position>top</position> + <offset>-560.0</offset> + <children> + </children> + </display> + </children> + </display> + </topology> + <topology id="uniqueid0|uniqueid1|uniqueid2|uniqueid3" order="0"> + <display id="uniqueid1" primary="false"> + <position>left</position> + <offset>0.0</offset> + <children> + <display id="uniqueid0" primary="false"> + <position>top</position> + <offset>-50</offset> + <children> + </children> + </display> + <display id="uniqueid2" primary="true"> + <position>right</position> + <offset>-100</offset> + <children> + <display id="uniqueid3" primary="false"> + <position>bottom</position> + <offset>-300</offset> + <children> + </children> + </display> + </children> + </display> + </children> + </display> + </topology> + </displayTopologyState> + """; + + private static InputStream asInputStream(String value) throws IOException { + return CharSource.wrap(value).asByteSource(UTF_8).openStream(); + } + + private static SparseArray<String> generateIdToUniqueId() { + var res = new SparseArray<String>(); + for (int i = 0; i < 200; i++) { + res.put(i, "uniqueid" + i); + } + return res; + } + + private static Map<String, Integer> generateUniqueIdToId() { + var res = new HashMap<String, Integer>(); + for (int i = 0; i < 200; i++) { + res.put("uniqueid" + i, i); + } + return res; + } + + private static DisplayTopology generateTopology(int displayId1, int displayId2) { + var topology = new DisplayTopology(); + topology.addDisplay(displayId1, 800f, 600f); + topology.addDisplay(displayId2, 1920f, 1080f); + return topology; + } + + @Mock + private DisplayTopologyXmlStore.Injector mInjector; + @Mock + private AtomicFilePrintWriter mPrintWriter0; + @Mock + private AtomicFilePrintWriter mPrintWriter1; + + private DisplayTopology mTopology; + + /** Setup tests. */ + @Before + public void setup() throws IOException { + MockitoAnnotations.initMocks(this); + configureTopologyFile(/*userId=*/ 0, NO_TOPOLOGIES, mPrintWriter0); + configureTopologyFile(/*userId=*/ 1, SIMPLE_TOPOLOGY, mPrintWriter1); + configureTopologyFile(/*userId=*/ 2, MULTIPLE_TOPOLOGIES, mPrintWriter1); + + when(mInjector.getDisplayIdToUniqueIdMapping()).thenReturn(generateIdToUniqueId()); + when(mInjector.getUniqueIdToDisplayIdMapping()).thenReturn(generateUniqueIdToId()); + + mTopology = generateTopology(0, 1); + } + + @Test + public void testSaveAndRestoreTopologyWithoutFileStreams() throws IOException { + final float initialOffset = -560f; + final float newOffset = -300f; + + var store = new DisplayTopologyXmlStore(mInjector); + assertThat(store.saveTopology(mTopology)).isTrue(); + assertThat(mTopology.getRoot().getChildren().getFirst().getOffset()) + .isEqualTo(initialOffset); + assertThat(mTopology.getRoot().getWidth()).isEqualTo(800f); + assertThat(mTopology.getRoot().getHeight()).isEqualTo(600f); + + // Change display size + assertThat(mTopology.updateDisplay(0, 640f, 480f)).isTrue(); + assertThat(mTopology.getRoot().getWidth()).isEqualTo(640f); + assertThat(mTopology.getRoot().getHeight()).isEqualTo(480f); + + // Move display#1. + mTopology.rearrange(Map.of(0, new PointF(0, 0), + 1, new PointF(newOffset, -1080f))); + assertThat(mTopology.getRoot().getChildren().getFirst().getOffset()).isEqualTo(newOffset); + + // Restore the topology, should apply saved offset, while keeping the current display sizes + mTopology = store.restoreTopology(mTopology); + + // Offset is taken from the persisted topology. + assertThat(mTopology.getRoot().getChildren().getFirst().getOffset()) + .isEqualTo(initialOffset); + + // Size is taken from the current topology + assertThat(mTopology.getRoot().getWidth()).isEqualTo(640f); + assertThat(mTopology.getRoot().getHeight()).isEqualTo(480f); + + // reloadTopologies was never called so, no file operations should have been performed. + verify(mInjector, never()).readUserTopologies(anyInt()); + verify(mInjector, never()).getTopologyFilePrintWriter(anyInt()); + } + + @Test + public void testSaveTopologyInPrintWriter() throws IOException { + var store = new DisplayTopologyXmlStore(mInjector); + store.reloadTopologies(/*userId=*/ 0); + assertThat(store.saveTopology(mTopology)).isTrue(); + verify(mPrintWriter0).print(eq(SIMPLE_TOPOLOGY)); + verify(mPrintWriter0).markSuccess(); + verify(mPrintWriter0).close(); + } + + @Test + public void testRestoreTopology() { + var store = new DisplayTopologyXmlStore(mInjector); + store.reloadTopologies(/*userId=*/ 0); + var restoredTopology = store.restoreTopology(mTopology); + + // Should return null because there was nothing persisted before. + assertThat(restoredTopology).isNull(); + + // Persist topology + assertThat(store.saveTopology(mTopology)).isTrue(); + + // Should return new instance (restored), but equal. + var restoredTopologyAfterSave = store.restoreTopology(mTopology); + assertThat(restoredTopologyAfterSave).isNotSameInstanceAs(mTopology); + assertThat(restoredTopologyAfterSave).isEqualTo(mTopology); + } + + @Test + public void testChangeUser() { + var store = new DisplayTopologyXmlStore(mInjector); + // Move display#1. + mTopology.rearrange(Map.of(0, new PointF(0, 0), + 1, new PointF(-10f, -1080f))); + assertThat(mTopology.getRoot().getChildren().getFirst().getOffset()).isEqualTo(-10f); + + store.reloadTopologies(/*userId=*/ 1); + // Should return new instance (restored), with new offset. + var restoredTopology = store.restoreTopology(mTopology); + assertThat(restoredTopology).isNotSameInstanceAs(mTopology); + assertThat(restoredTopology.getRoot().getChildren().getFirst().getOffset()) + .isEqualTo(-560f); + + // Change user. + store.reloadTopologies(/*userId=*/ 0); + // Should return null because the topology is not found for user 0. + assertThat(store.restoreTopology(mTopology)).isNull(); + } + + @Test + public void testMultipleUserTopologies() { + var store = new DisplayTopologyXmlStore(mInjector); + store.reloadTopologies(/*userId=*/ 2); + + var topology4Displays = new DisplayTopology(); + topology4Displays.addDisplay(0, 800f, 600f); + topology4Displays.addDisplay(1, 1920f, 1080f); + topology4Displays.addDisplay(2, 480f, 640f); + topology4Displays.addDisplay(3, 768f, 1024f); + + var restored = store.restoreTopology(topology4Displays); + assertThat(restored).isNotNull(); + assertThat(restored.getPrimaryDisplayId()).isEqualTo(2); + assertThat(restored.getRoot()).isNotNull(); + assertThat(restored.getRoot().getDisplayId()).isEqualTo(1); + assertThat(restored.getRoot().getWidth()).isEqualTo(1920f); + assertThat(restored.getRoot().getHeight()).isEqualTo(1080f); + assertThat(restored.getRoot().getOffset()).isEqualTo(0); + assertThat(restored.getRoot().getChildren().size()).isEqualTo(2); + assertThat(restored.getRoot().getChildren().getFirst().getDisplayId()).isEqualTo(0); + assertThat(restored.getRoot().getChildren().getFirst().getWidth()).isEqualTo(800f); + assertThat(restored.getRoot().getChildren().getFirst().getHeight()).isEqualTo(600f); + assertThat(restored.getRoot().getChildren().getFirst().getOffset()).isEqualTo(-50); + assertThat(restored.getRoot().getChildren().getFirst().getChildren().size()) + .isEqualTo(0); + assertThat(restored.getRoot().getChildren().getLast().getDisplayId()).isEqualTo(2); + assertThat(restored.getRoot().getChildren().getLast().getWidth()).isEqualTo(480f); + assertThat(restored.getRoot().getChildren().getLast().getHeight()).isEqualTo(640f); + assertThat(restored.getRoot().getChildren().getLast().getOffset()).isEqualTo(-100); + assertThat(restored.getRoot().getChildren().getLast().getChildren().size()) + .isEqualTo(1); + + assertThat(restored.getRoot().getChildren().getLast().getChildren().getFirst() + .getDisplayId()).isEqualTo(3); + assertThat(restored.getRoot().getChildren().getLast().getChildren().getFirst() + .getWidth()).isEqualTo(768f); + assertThat(restored.getRoot().getChildren().getLast().getChildren().getFirst() + .getHeight()).isEqualTo(1024f); + assertThat(restored.getRoot().getChildren().getLast().getChildren().getFirst() + .getOffset()).isEqualTo(-300); + } + + @Test + public void testLimitNumberOfTopologies() { + var store = new DisplayTopologyXmlStore(mInjector); + store.reloadTopologies(/*userId=*/ 0); + for (int i = 0; i < 110; i++) { + assertThat(store.saveTopology(generateTopology(i, i + 1))).isTrue(); + } + + assertThat(store.restoreTopology(generateTopology(110, 111))).isNull(); + assertThat(store.restoreTopology(generateTopology(109, 110))).isNotNull(); + assertThat(store.restoreTopology(generateTopology(10, 11))).isNotNull(); + assertThat(store.restoreTopology(generateTopology(9, 10))).isNull(); + for (int i = 0; i < 100; i++) { + assertThat(store.restoreTopology(generateTopology(i + 10, i + 11))).isNotNull(); + } + } + + @Test + public void testVendorTopology() throws IOException { + configureVendorTopologyFile(IMMUTABLE_TOPOLOGY); + var store = new DisplayTopologyXmlStore(mInjector); + store.reloadTopologies(/*userId=*/ 0); + var restored = store.restoreTopology(generateTopology(10, 11)); + assertThat(restored).isNotNull(); + assertThat(store.saveTopology(restored)).isFalse(); + + var userTopology = generateTopology(0, 1); + assertThat(store.restoreTopology(userTopology)).isNull(); + assertThat(store.saveTopology(userTopology)).isTrue(); + } + + @Test + public void testProductTopology() throws IOException { + configureProductTopologyFile(IMMUTABLE_TOPOLOGY); + var store = new DisplayTopologyXmlStore(mInjector); + store.reloadTopologies(/*userId=*/ 0); + var restored = store.restoreTopology(generateTopology(10, 11)); + assertThat(restored).isNotNull(); + assertThat(store.saveTopology(restored)).isFalse(); + + var userTopology = generateTopology(0, 1); + assertThat(store.restoreTopology(userTopology)).isNull(); + assertThat(store.saveTopology(userTopology)).isTrue(); + } + + private void configureTopologyFile(int userId, String initialFileContent, + AtomicFilePrintWriter printWriter) throws IOException { + doReturn(asInputStream(initialFileContent)).when(mInjector).readUserTopologies(eq(userId)); + doReturn(printWriter).when(mInjector).getTopologyFilePrintWriter(eq(userId)); + } + + private void configureVendorTopologyFile(String initialFileContent) throws IOException { + doReturn(asInputStream(initialFileContent)).when(mInjector).readVendorTopologies(); + } + + private void configureProductTopologyFile(String initialFileContent) throws IOException { + doReturn(asInputStream(initialFileContent)).when(mInjector).readProductTopologies(); + } +} diff --git a/services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioManagerRouteControllerTest.java b/services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioManagerRouteControllerTest.java index 439243e85e75..6e9a28f344e3 100644 --- a/services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioManagerRouteControllerTest.java +++ b/services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioManagerRouteControllerTest.java @@ -139,8 +139,8 @@ public class AudioManagerRouteControllerTest { mMockAudioManager, Looper.getMainLooper(), mMediaAudioProductStrategy, - btAdapter, - mOnDeviceRouteChangedListener); + btAdapter); + mControllerUnderTest.registerRouteChangeListener(mOnDeviceRouteChangedListener); mControllerUnderTest.start(UserHandle.CURRENT_OR_SELF); ArgumentCaptor<AudioDeviceCallback> deviceCallbackCaptor = diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java index 8dc8c14f8948..cb52f1849b5b 100644 --- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java @@ -151,6 +151,7 @@ import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; import android.platform.test.flag.junit.SetFlagsRule; import android.platform.test.flag.util.FlagSetException; @@ -192,7 +193,9 @@ import org.junit.runner.RunWith; import org.mockito.Answers; import org.mockito.ArgumentCaptor; import org.mockito.ArgumentMatchers; +import org.mockito.InOrder; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.quality.Strictness; import org.mockito.stubbing.Answer; @@ -435,6 +438,7 @@ public final class AlarmManagerServiceTest { private void disableFlagsNotSetByAnnotation() { try { mSetFlagsRule.disableFlags(Flags.FLAG_START_USER_BEFORE_SCHEDULED_ALARMS); + mSetFlagsRule.disableFlags(Flags.FLAG_ACQUIRE_WAKELOCK_BEFORE_SEND); } catch (FlagSetException fse) { // Expected if the test about to be run requires this enabled. } @@ -948,6 +952,28 @@ public final class AlarmManagerServiceTest { } @Test + @EnableFlags(Flags.FLAG_ACQUIRE_WAKELOCK_BEFORE_SEND) + public void testWakelockOrdering() throws Exception { + final long triggerTime = mNowElapsedTest + 5000; + final PendingIntent alarmPi = getNewMockPendingIntent(); + setTestAlarm(ELAPSED_REALTIME_WAKEUP, triggerTime, alarmPi); + + mNowElapsedTest = mTestTimer.getElapsed(); + mTestTimer.expire(); + + final InOrder inOrder = Mockito.inOrder(alarmPi, mWakeLock); + inOrder.verify(mWakeLock).acquire(); + + final ArgumentCaptor<PendingIntent.OnFinished> onFinishedCaptor = + ArgumentCaptor.forClass(PendingIntent.OnFinished.class); + inOrder.verify(alarmPi).send(eq(mMockContext), eq(0), any(Intent.class), + onFinishedCaptor.capture(), any(Handler.class), isNull(), any()); + onFinishedCaptor.getValue().onSendFinished(alarmPi, null, 0, null, null); + + inOrder.verify(mWakeLock).release(); + } + + @Test public void testMinFuturityCoreUid() { setDeviceConfigLong(KEY_MIN_FUTURITY, 10L); assertEquals(10, mService.mConstants.MIN_FUTURITY); diff --git a/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java index 27eada013642..89b48bad2358 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java @@ -16,8 +16,6 @@ package com.android.server.am; -import static android.os.PowerWhitelistManager.REASON_NOTIFICATION_SERVICE; -import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED; import static android.os.Process.INVALID_UID; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; @@ -29,11 +27,9 @@ import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_OWNER_CANC import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_OWNER_FORCE_STOPPED; import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_SUPERSEDED; import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_USER_STOPPED; -import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER; import static com.android.server.am.PendingIntentRecord.cancelReasonToString; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; @@ -43,11 +39,9 @@ import static org.mockito.Mockito.when; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.AppGlobals; -import android.app.BackgroundStartPrivileges; import android.app.PendingIntent; import android.content.Intent; import android.content.pm.IPackageManager; -import android.os.Binder; import android.os.Looper; import android.os.UserHandle; @@ -185,34 +179,6 @@ public class PendingIntentControllerTest { } } - @Test - public void testClearAllowBgActivityStartsClearsToken() { - final PendingIntentRecord pir = createPendingIntentRecord(0); - Binder token = new Binder(); - pir.setAllowBgActivityStarts(token, FLAG_ACTIVITY_SENDER); - assertEquals(BackgroundStartPrivileges.allowBackgroundActivityStarts(token), - pir.getBackgroundStartPrivilegesForActivitySender(token)); - pir.clearAllowBgActivityStarts(token); - assertEquals(BackgroundStartPrivileges.NONE, - pir.getBackgroundStartPrivilegesForActivitySender(token)); - } - - @Test - public void testClearAllowBgActivityStartsClearsDuration() { - final PendingIntentRecord pir = createPendingIntentRecord(0); - Binder token = new Binder(); - pir.setAllowlistDurationLocked(token, 1000, - TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, REASON_NOTIFICATION_SERVICE, - "NotificationManagerService"); - PendingIntentRecord.TempAllowListDuration allowlistDurationLocked = - pir.getAllowlistDurationLocked(token); - assertEquals(1000, allowlistDurationLocked.duration); - pir.clearAllowBgActivityStarts(token); - PendingIntentRecord.TempAllowListDuration allowlistDurationLockedAfterClear = - pir.getAllowlistDurationLocked(token); - assertNull(allowlistDurationLockedAfterClear); - } - private void assertCancelReason(int expectedReason, int actualReason) { final String errMsg = "Expected: " + cancelReasonToString(expectedReason) + "; Actual: " + cancelReasonToString(actualReason); diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/SystemBackupAgentTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/SystemBackupAgentTest.java index 7e179095d99b..86bf203771ba 100644 --- a/services/tests/mockingservicestests/src/com/android/server/backup/SystemBackupAgentTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/backup/SystemBackupAgentTest.java @@ -93,7 +93,8 @@ public class SystemBackupAgentTest { "app_locales", "app_gender", "companion", - "system_gender"); + "system_gender", + "display"); } @Test @@ -118,7 +119,8 @@ public class SystemBackupAgentTest { "app_locales", "app_gender", "companion", - "system_gender"); + "system_gender", + "display"); } @Test @@ -136,7 +138,8 @@ public class SystemBackupAgentTest { "app_locales", "companion", "app_gender", - "system_gender"); + "system_gender", + "display"); } @Test @@ -158,7 +161,8 @@ public class SystemBackupAgentTest { "shortcut_manager", "companion", "app_gender", - "system_gender"); + "system_gender", + "display"); } @Test diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java index 8a10040f986f..8dc657ed75a6 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java @@ -49,6 +49,7 @@ import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.KeyguardManager; +import android.app.PropertyInvalidatedCache; import android.content.Context; import android.content.pm.PackageManagerInternal; import android.content.pm.UserInfo; @@ -181,8 +182,8 @@ public final class UserManagerServiceTest { MockitoAnnotations.initMocks(this); mSpiedContext = spy(mRealContext); - // Called when WatchedUserStates is constructed - doNothing().when(() -> UserManager.invalidateIsUserUnlockedCache()); + // Disable binder caches in this process. + PropertyInvalidatedCache.disableForTestMode(); // Called when creating new users when(mDeviceStorageMonitorInternal.isMemoryLow()).thenReturn(false); diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java index db323f1b68e7..b92afc5c0ca7 100644 --- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java @@ -33,6 +33,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.server.wallpaper.WallpaperUtils.WALLPAPER; import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; import static org.hamcrest.core.IsNot.not; import static org.junit.Assert.assertEquals; @@ -65,6 +66,7 @@ import android.content.pm.ParceledListSlice; import android.content.pm.ServiceInfo; import android.graphics.Color; import android.hardware.display.DisplayManager; +import android.hardware.display.DisplayManager.DisplayListener; import android.os.ParcelFileDescriptor; import android.os.PersistableBundle; import android.os.RemoteException; @@ -75,6 +77,7 @@ import android.platform.test.annotations.Presubmit; import android.platform.test.flag.junit.SetFlagsRule; import android.service.wallpaper.IWallpaperConnection; import android.service.wallpaper.IWallpaperEngine; +import android.service.wallpaper.IWallpaperService; import android.service.wallpaper.WallpaperService; import android.testing.TestableContext; import android.util.Log; @@ -105,6 +108,7 @@ import org.junit.Test; import org.junit.rules.RuleChain; import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.quality.Strictness; @@ -140,6 +144,8 @@ public class WallpaperManagerServiceTests { private static ComponentName sImageWallpaperComponentName; private static ComponentName sDefaultWallpaperComponent; + private static ComponentName sFallbackWallpaperComponentName; + private IPackageManager mIpm = AppGlobals.getPackageManager(); @Mock @@ -154,6 +160,8 @@ public class WallpaperManagerServiceTests { private WallpaperManagerService mService; private static IWallpaperConnection.Stub sWallpaperService; + private static WindowManagerInternal sWindowManagerInternal; + @BeforeClass public static void setUpClass() { sMockitoSession = mockitoSession() @@ -163,8 +171,8 @@ public class WallpaperManagerServiceTests { .spyStatic(WallpaperManager.class) .startMocking(); - final WindowManagerInternal dmi = mock(WindowManagerInternal.class); - LocalServices.addService(WindowManagerInternal.class, dmi); + sWindowManagerInternal = mock(WindowManagerInternal.class); + LocalServices.addService(WindowManagerInternal.class, sWindowManagerInternal); sContext.addMockSystemService(Context.APP_OPS_SERVICE, mock(AppOpsManager.class)); @@ -189,6 +197,8 @@ public class WallpaperManagerServiceTests { sContext.getResources().getString(R.string.image_wallpaper_component)); // Mock default wallpaper as image wallpaper if there is no pre-defined default wallpaper. sDefaultWallpaperComponent = WallpaperManager.getCmfDefaultWallpaperComponent(sContext); + sFallbackWallpaperComponentName = ComponentName.unflattenFromString( + sContext.getResources().getString(R.string.fallback_wallpaper_component)); if (sDefaultWallpaperComponent == null) { sDefaultWallpaperComponent = sImageWallpaperComponentName; @@ -199,6 +209,9 @@ public class WallpaperManagerServiceTests { } sContext.addMockService(sImageWallpaperComponentName, sWallpaperService); + if (sFallbackWallpaperComponentName != null) { + sContext.addMockService(sFallbackWallpaperComponentName, sWallpaperService); + } } @AfterClass @@ -210,6 +223,7 @@ public class WallpaperManagerServiceTests { LocalServices.removeServiceForTest(WindowManagerInternal.class); sImageWallpaperComponentName = null; sDefaultWallpaperComponent = null; + sFallbackWallpaperComponentName = null; reset(sContext); } @@ -300,6 +314,7 @@ public class WallpaperManagerServiceTests { * Tests that internal basic data should be correct after boot up. */ @Test + @DisableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WALLPAPER) public void testDataCorrectAfterBoot() { mService.switchUser(USER_SYSTEM, null); @@ -336,6 +351,43 @@ public class WallpaperManagerServiceTests { } /** + * Test setWallpaperComponent with FLAG_LOCK should update the mLastLockWallpaper. + */ + @Test + public void testSetLockWallpaper() { + final int testUserId = USER_SYSTEM; + mService.switchUser(testUserId, null); + verifyLastWallpaperData(testUserId, sDefaultWallpaperComponent); + verifyCurrentSystemData(testUserId); + + mService.setWallpaperComponent(sImageWallpaperComponentName, sContext.getOpPackageName(), + FLAG_LOCK, testUserId); + verifyLastLockWallpaperData(testUserId, sImageWallpaperComponentName); + verifyCurrentSystemData(testUserId); + } + + /** + * Test setWallpaperComponent with FLAG_LOCK and then setWallpaperComponent with + * FLAG_LOCK | FLAG_SYSTEM should set mLastLockWallpaper to null. + */ + @Test + public void testSetLockWallpaperThenSetLockAndSystemWallpaper_setLastLockWallpaperToNull() { + final int testUserId = USER_SYSTEM; + mService.switchUser(testUserId, null); + verifyLastWallpaperData(testUserId, sDefaultWallpaperComponent); + verifyCurrentSystemData(testUserId); + + mService.setWallpaperComponent(sImageWallpaperComponentName, sContext.getOpPackageName(), + FLAG_LOCK, testUserId); + mService.setWallpaperComponent(sImageWallpaperComponentName, sContext.getOpPackageName(), + FLAG_LOCK | FLAG_SYSTEM, testUserId); + + verifyLastWallpaperData(testUserId, sImageWallpaperComponentName); + assertThat(mService.mLastLockWallpaper).isNull(); + verifyCurrentSystemData(testUserId); + } + + /** * Tests that when setWallpaperComponent is called with the currently set component, a command * is issued to the wallpaper. */ @@ -661,6 +713,321 @@ public class WallpaperManagerServiceTests { assertPfdAndFileContentsEqual(pfd, originalSystemWallpaperFile); } + // Verify a secondary display added started + @Test + @EnableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WALLPAPER) + public void displayAdded_sameSystemAndLockWallpaper_shouldAttachWallpaperServiceOnce() + throws Exception { + // GIVEN the same wallpaper used for the lock and system. + final int testUserId = USER_SYSTEM; + mService.switchUser(testUserId, null); + final WallpaperData wallpaper = mService.getCurrentWallpaperData(FLAG_SYSTEM, testUserId); + IWallpaperService mockIWallpaperService = mock(IWallpaperService.class); + wallpaper.connection.mService = mockIWallpaperService; + // GIVEN there are two displays: DEFAULT_DISPLAY, 2 + final int testDisplayId = 2; + setUpDisplays(List.of(DEFAULT_DISPLAY, testDisplayId)); + + // WHEN display ID, 2, is ready. + WallpaperManagerInternal wallpaperManagerInternal = LocalServices.getService( + WallpaperManagerInternal.class); + wallpaperManagerInternal.onDisplayReady(testDisplayId); + + // Then there is a connection established for the system & lock wallpaper for display ID, 2. + verify(mockIWallpaperService).attach( + /* connection= */ eq(wallpaper.connection), + /* windowToken= */ any(), + /* windowType= */ anyInt(), + /* isPreview= */ anyBoolean(), + /* reqWidth= */ anyInt(), + /* reqHeight= */ anyInt(), + /* padding= */ any(), + /* displayId= */ eq(testDisplayId), + /* which= */ eq(FLAG_SYSTEM | FLAG_LOCK), + /* info= */ any(), + /* description= */ any()); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WALLPAPER) + public void displayAdded_differentSystemAndLockWallpapers_shouldAttachWallpaperServiceTwice() + throws Exception { + // GIVEN different wallpapers used for the lock and system. + final int testUserId = USER_SYSTEM; + mService.switchUser(testUserId, null); + mService.setWallpaperComponent(sImageWallpaperComponentName, sContext.getOpPackageName(), + FLAG_LOCK, testUserId); + final WallpaperData systemWallpaper = mService.getCurrentWallpaperData(FLAG_SYSTEM, + testUserId); + final WallpaperData lockWallpaper = mService.getCurrentWallpaperData(FLAG_LOCK, + testUserId); + IWallpaperService mockIWallpaperService = mock(IWallpaperService.class); + systemWallpaper.connection.mService = mockIWallpaperService; + lockWallpaper.connection.mService = mockIWallpaperService; + // GIVEN there are two displays: DEFAULT_DISPLAY, 2 + final int testDisplayId = 2; + setUpDisplays(List.of(DEFAULT_DISPLAY, testDisplayId)); + + // WHEN display ID, 2, is ready. + WallpaperManagerInternal wallpaperManagerInternal = LocalServices.getService( + WallpaperManagerInternal.class); + wallpaperManagerInternal.onDisplayReady(testDisplayId); + + // Then there is a connection established for the system wallpaper for display ID, 2. + verify(mockIWallpaperService).attach( + /* connection= */ eq(systemWallpaper.connection), + /* windowToken= */ any(), + /* windowType= */ anyInt(), + /* isPreview= */ anyBoolean(), + /* reqWidth= */ anyInt(), + /* reqHeight= */ anyInt(), + /* padding= */ any(), + /* displayId= */ eq(testDisplayId), + /* which= */ eq(FLAG_SYSTEM), + /* info= */ any(), + /* description= */ any()); + // Then there is a connection established for the lock wallpaper for display ID, 2. + verify(mockIWallpaperService).attach( + /* connection= */ eq(lockWallpaper.connection), + /* windowToken= */ any(), + /* windowType= */ anyInt(), + /* isPreview= */ anyBoolean(), + /* reqWidth= */ anyInt(), + /* reqHeight= */ anyInt(), + /* padding= */ any(), + /* displayId= */ eq(testDisplayId), + /* which= */ eq(FLAG_LOCK), + /* info= */ any(), + /* description= */ any()); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WALLPAPER) + public void displayAdded_wallpaperIncompatibleForDisplay_shouldAttachFallbackWallpaperService() + throws Exception { + final int testUserId = USER_SYSTEM; + mService.switchUser(testUserId, null); + IWallpaperService mockIWallpaperService = mock(IWallpaperService.class); + mService.mFallbackWallpaper.connection.mService = mockIWallpaperService; + // GIVEN there are two displays: DEFAULT_DISPLAY, 2 + final int testDisplayId = 2; + setUpDisplays(List.of(DEFAULT_DISPLAY, testDisplayId)); + // GIVEN the wallpaper isn't compatible with display ID, 2 + mService.removeWallpaperCompatibleDisplayForTest(testDisplayId); + + // WHEN display ID, 2, is ready. + WallpaperManagerInternal wallpaperManagerInternal = LocalServices.getService( + WallpaperManagerInternal.class); + wallpaperManagerInternal.onDisplayReady(testDisplayId); + + // Then there is a connection established for the fallback wallpaper for display ID, 2. + verify(mockIWallpaperService).attach( + /* connection= */ eq(mService.mFallbackWallpaper.connection), + /* windowToken= */ any(), + /* windowType= */ anyInt(), + /* isPreview= */ anyBoolean(), + /* reqWidth= */ anyInt(), + /* reqHeight= */ anyInt(), + /* padding= */ any(), + /* displayId= */ eq(testDisplayId), + /* which= */ eq(FLAG_SYSTEM | FLAG_LOCK), + /* info= */ any(), + /* description= */ any()); + } + // Verify a secondary display added end + + // Verify a secondary display removed started + @Test + @EnableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WALLPAPER) + public void displayRemoved_sameSystemAndLockWallpaper_shouldDetachWallpaperServiceOnce() + throws Exception { + ArgumentCaptor<DisplayListener> displayListenerCaptor = ArgumentCaptor.forClass( + DisplayListener.class); + verify(mDisplayManager).registerDisplayListener(displayListenerCaptor.capture(), eq(null)); + // GIVEN the same wallpaper used for the lock and system. + final int testUserId = USER_SYSTEM; + mService.switchUser(testUserId, null); + final WallpaperData wallpaper = mService.getCurrentWallpaperData(FLAG_SYSTEM, testUserId); + IWallpaperService mockIWallpaperService = mock(IWallpaperService.class); + wallpaper.connection.mService = mockIWallpaperService; + // GIVEN there are two displays: DEFAULT_DISPLAY, 2 + final int testDisplayId = 2; + setUpDisplays(List.of(DEFAULT_DISPLAY, testDisplayId)); + // GIVEN wallpaper connections have been established for display ID, 2. + WallpaperManagerInternal wallpaperManagerInternal = LocalServices.getService( + WallpaperManagerInternal.class); + wallpaperManagerInternal.onDisplayReady(testDisplayId); + // Save displayConnector for displayId 2 before display removal. + WallpaperManagerService.DisplayConnector displayConnector = + wallpaper.connection.getDisplayConnectorOrCreate(testDisplayId); + + // WHEN display ID, 2, is removed. + DisplayListener displayListener = displayListenerCaptor.getValue(); + displayListener.onDisplayRemoved(testDisplayId); + + // Then the wallpaper connection for display ID, 2, is detached. + verify(mockIWallpaperService).detach(eq(displayConnector.mToken)); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WALLPAPER) + public void displayRemoved_differentSystemAndLockWallpapers_shouldDetachWallpaperServiceTwice() + throws Exception { + ArgumentCaptor<DisplayListener> displayListenerCaptor = ArgumentCaptor.forClass( + DisplayListener.class); + verify(mDisplayManager).registerDisplayListener(displayListenerCaptor.capture(), eq(null)); + // GIVEN different wallpapers used for the lock and system. + final int testUserId = USER_SYSTEM; + mService.switchUser(testUserId, null); + mService.setWallpaperComponent(sImageWallpaperComponentName, sContext.getOpPackageName(), + FLAG_LOCK, testUserId); + final WallpaperData systemWallpaper = mService.getCurrentWallpaperData(FLAG_SYSTEM, + testUserId); + final WallpaperData lockWallpaper = mService.getCurrentWallpaperData(FLAG_LOCK, + testUserId); + IWallpaperService mockIWallpaperService = mock(IWallpaperService.class); + systemWallpaper.connection.mService = mockIWallpaperService; + lockWallpaper.connection.mService = mockIWallpaperService; + // GIVEN there are two displays: DEFAULT_DISPLAY, 2 + final int testDisplayId = 2; + setUpDisplays(List.of(DEFAULT_DISPLAY, testDisplayId)); + // GIVEN wallpaper connections have been established for display ID, 2. + WallpaperManagerInternal wallpaperManagerInternal = LocalServices.getService( + WallpaperManagerInternal.class); + wallpaperManagerInternal.onDisplayReady(testDisplayId); + // Save displayConnectors for display ID, 2, before display removal. + WallpaperManagerService.DisplayConnector systemWallpaperDisplayConnector = + systemWallpaper.connection.getDisplayConnectorOrCreate(testDisplayId); + WallpaperManagerService.DisplayConnector lockWallpaperDisplayConnector = + lockWallpaper.connection.getDisplayConnectorOrCreate(testDisplayId); + + // WHEN display ID, 2, is removed. + DisplayListener displayListener = displayListenerCaptor.getValue(); + displayListener.onDisplayRemoved(testDisplayId); + + // Then the system wallpaper connection for display ID, 2, is detached. + verify(mockIWallpaperService).detach(eq(systemWallpaperDisplayConnector.mToken)); + // Then the lock wallpaper connection for display ID, 2, is detached. + verify(mockIWallpaperService).detach(eq(lockWallpaperDisplayConnector.mToken)); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WALLPAPER) + public void displayRemoved_fallbackWallpaper_shouldDetachFallbackWallpaperService() + throws Exception { + ArgumentCaptor<DisplayListener> displayListenerCaptor = ArgumentCaptor.forClass( + DisplayListener.class); + verify(mDisplayManager).registerDisplayListener(displayListenerCaptor.capture(), eq(null)); + final int testUserId = USER_SYSTEM; + mService.switchUser(testUserId, null); + IWallpaperService mockIWallpaperService = mock(IWallpaperService.class); + mService.mFallbackWallpaper.connection.mService = mockIWallpaperService; + // GIVEN there are two displays: DEFAULT_DISPLAY, 2 + final int testDisplayId = 2; + setUpDisplays(List.of(DEFAULT_DISPLAY, testDisplayId)); + // GIVEN display ID, 2, is incompatible with the wallpaper. + mService.removeWallpaperCompatibleDisplayForTest(testDisplayId); + // GIVEN wallpaper connections have been established for display ID, 2. + WallpaperManagerInternal wallpaperManagerInternal = LocalServices.getService( + WallpaperManagerInternal.class); + wallpaperManagerInternal.onDisplayReady(testDisplayId); + // Save fallback wallpaper displayConnector for display ID, 2, before display removal. + WallpaperManagerService.DisplayConnector fallbackWallpaperConnector = + mService.mFallbackWallpaper.connection.getDisplayConnectorOrCreate(testDisplayId); + + // WHEN displayId, 2, is removed. + DisplayListener displayListener = displayListenerCaptor.getValue(); + displayListener.onDisplayRemoved(testDisplayId); + + // Then the fallback wallpaper connection for display ID, 2, is detached. + verify(mockIWallpaperService).detach(eq(fallbackWallpaperConnector.mToken)); + } + // Verify a secondary display removed ended + + // Test fallback wallpaper after enabling connected display supports. + @Test + @EnableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WALLPAPER) + public void testFallbackWallpaperForConnectedDisplays() { + final WallpaperData fallbackData = mService.mFallbackWallpaper; + + assertWithMessage( + "The connected wallpaper component should be fallback wallpaper component from " + + "config file.") + .that(fallbackData.connection.mWallpaper.getComponent()) + .isEqualTo(sFallbackWallpaperComponentName); + assertWithMessage("Fallback wallpaper should support both lock & system.") + .that(fallbackData.mWhich) + .isEqualTo(FLAG_LOCK | FLAG_SYSTEM); + } + + // Verify a secondary display removes system decorations started + @Test + @EnableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WALLPAPER) + public void displayRemoveSystemDecorations_sameSystemAndLockWallpaper_shouldDetachWallpaperServiceOnce() + throws Exception { + // GIVEN the same wallpaper used for the lock and system. + final int testUserId = USER_SYSTEM; + mService.switchUser(testUserId, null); + final WallpaperData wallpaper = mService.getCurrentWallpaperData(FLAG_SYSTEM, testUserId); + IWallpaperService mockIWallpaperService = mock(IWallpaperService.class); + wallpaper.connection.mService = mockIWallpaperService; + // GIVEN there are two displays: DEFAULT_DISPLAY, 2 + final int testDisplayId = 2; + setUpDisplays(List.of(DEFAULT_DISPLAY, testDisplayId)); + // GIVEN wallpaper connections have been established for displayID, 2. + WallpaperManagerInternal wallpaperManagerInternal = LocalServices.getService( + WallpaperManagerInternal.class); + wallpaperManagerInternal.onDisplayReady(testDisplayId); + // Save displayConnector for displayId 2 before display removal. + WallpaperManagerService.DisplayConnector displayConnector = + wallpaper.connection.getDisplayConnectorOrCreate(testDisplayId); + + // WHEN displayId, 2, is removed. + wallpaperManagerInternal.onDisplayRemoveSystemDecorations(testDisplayId); + + // Then the wallpaper connection for displayId, 2, is detached. + verify(mockIWallpaperService).detach(eq(displayConnector.mToken)); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WALLPAPER) + public void displayRemoveSystemDecorations_differentSystemAndLockWallpapers_shouldDetachWallpaperServiceTwice() + throws Exception { + // GIVEN different wallpapers used for the lock and system. + final int testUserId = USER_SYSTEM; + mService.switchUser(testUserId, null); + mService.setWallpaperComponent(sImageWallpaperComponentName, sContext.getOpPackageName(), + FLAG_LOCK, testUserId); + final WallpaperData systemWallpaper = mService.getCurrentWallpaperData(FLAG_SYSTEM, + testUserId); + final WallpaperData lockWallpaper = mService.getCurrentWallpaperData(FLAG_LOCK, + testUserId); + IWallpaperService mockIWallpaperService = mock(IWallpaperService.class); + systemWallpaper.connection.mService = mockIWallpaperService; + lockWallpaper.connection.mService = mockIWallpaperService; + // GIVEN there are two displays: DEFAULT_DISPLAY, 2 + final int testDisplayId = 2; + setUpDisplays(List.of(DEFAULT_DISPLAY, testDisplayId)); + // GIVEN wallpaper connections have been established for displayID, 2. + WallpaperManagerInternal wallpaperManagerInternal = LocalServices.getService( + WallpaperManagerInternal.class); + wallpaperManagerInternal.onDisplayReady(testDisplayId); + // Save displayConnectors for displayId 2 before display removal. + WallpaperManagerService.DisplayConnector systemWallpaperDisplayConnector = + systemWallpaper.connection.getDisplayConnectorOrCreate(testDisplayId); + WallpaperManagerService.DisplayConnector lockWallpaperDisplayConnector = + lockWallpaper.connection.getDisplayConnectorOrCreate(testDisplayId); + + // WHEN displayId, 2, is removed. + wallpaperManagerInternal.onDisplayRemoveSystemDecorations(testDisplayId); + + // Then the system wallpaper connection for displayId, 2, is detached. + verify(mockIWallpaperService).detach(eq(systemWallpaperDisplayConnector.mToken)); + // Then the lock wallpaper connection for displayId, 2, is detached. + verify(mockIWallpaperService).detach(eq(lockWallpaperDisplayConnector.mToken)); + } + // Verify a secondary display removes system decorations ended + // Verify that after continue switch user from userId 0 to lastUserId, the wallpaper data for // non-current user must not bind to wallpaper service. private void verifyNoConnectionBeforeLastUser(int lastUserId) { @@ -681,11 +1048,31 @@ public class WallpaperManagerServiceTests { lastData.connection); } + private void verifyLastLockWallpaperData(int lastUserId, ComponentName expectedComponent) { + final WallpaperData lastLockData = mService.mLastLockWallpaper; + assertWithMessage("Last wallpaper must not be null").that(lastLockData).isNotNull(); + assertWithMessage("Last wallpaper component must be equals.") + .that(lastLockData.getComponent()) + .isEqualTo(expectedComponent); + assertWithMessage("The user id in last wallpaper should be the last switched user") + .that(lastLockData.userId) + .isEqualTo(lastUserId); + assertWithMessage("Must exist user data connection on last wallpaper data") + .that(lastLockData.connection) + .isNotNull(); + } + private void verifyCurrentSystemData(int userId) { final WallpaperData lastData = mService.mLastWallpaper; final WallpaperData wallpaper = mService.getCurrentWallpaperData(FLAG_SYSTEM, userId); assertEquals("Last wallpaper should be equals to current system wallpaper", lastData, wallpaper); + + final WallpaperData lastLockData = mService.mLastLockWallpaper; + final WallpaperData lockWallpaper = mService.getCurrentWallpaperData(FLAG_LOCK, userId); + assertWithMessage("Last lock wallpaper should be equals to current lock wallpaper") + .that(lastLockData) + .isEqualTo(lockWallpaper); } private void verifyDisplayData() { @@ -698,6 +1085,33 @@ public class WallpaperManagerServiceTests { } /** + * Sets up mock displays for testing. + * + * <p>This method creates mock {@link Display} objects and configures the {@link DisplayManager} + * to return them. It also sets up the {@link WindowManagerInternal} to indicate that all + * displays support home. + * + * @param displayIds A list of display IDs to create mock displays for. + */ + private void setUpDisplays(List<Integer> displayIds) { + doReturn(true).when(sWindowManagerInternal).isHomeSupportedOnDisplay(anyInt()); + + Display[] mockDisplays = new Display[displayIds.size()]; + for (int i = 0; i < displayIds.size(); i++) { + final int displayId = displayIds.get(i); + final Display mockDisplay = mock(Display.class); + mockDisplays[i] = mockDisplay; + doReturn(DISPLAY_SIZE_DIMENSION).when(mockDisplay).getMaximumSizeDimension(); + doReturn(mockDisplay).when(mDisplayManager).getDisplay(eq(displayId)); + doReturn(displayId).when(mockDisplay).getDisplayId(); + doReturn(true).when(mockDisplay).hasAccess(anyInt()); + mService.addWallpaperCompatibleDisplayForTest(displayId); + } + + doReturn(mockDisplays).when(mDisplayManager).getDisplays(); + } + + /** * Asserts that the contents of the given {@link ParcelFileDescriptor} and {@link File} contain * exactly the same bytes. * diff --git a/services/tests/security/intrusiondetection/AndroidTest.xml b/services/tests/security/intrusiondetection/AndroidTest.xml index 0d211585958a..a0e846b90f03 100644 --- a/services/tests/security/intrusiondetection/AndroidTest.xml +++ b/services/tests/security/intrusiondetection/AndroidTest.xml @@ -25,7 +25,7 @@ </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" /> + <option name="run-command" value="setprop debug.intrusiondetection_package_name com.android.coretests.apps.testapp/.TestLoggingService" /> </target_preparer> <option name="test-tag" value="IntrusionDetectionServiceTests" /> diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java index fb31cfe762f2..f55ca0c0b12b 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java @@ -20,13 +20,13 @@ import static android.view.Display.DEFAULT_DISPLAY; import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS; import static android.view.WindowManagerPolicyConstants.FLAG_PASS_TO_USER; -import static com.android.hardware.input.Flags.FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES; import static com.android.server.accessibility.AccessibilityInputFilter.FLAG_FEATURE_AUTOCLICK; import static com.android.server.accessibility.AccessibilityInputFilter.FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER; import static com.android.server.accessibility.AccessibilityInputFilter.FLAG_FEATURE_FILTER_KEY_EVENTS; import static com.android.server.accessibility.AccessibilityInputFilter.FLAG_FEATURE_INJECT_MOTION_EVENTS; import static com.android.server.accessibility.AccessibilityInputFilter.FLAG_FEATURE_TOUCH_EXPLORATION; import static com.android.server.accessibility.AccessibilityInputFilter.FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER; +import static com.android.server.accessibility.Flags.FLAG_ENABLE_MAGNIFICATION_KEYBOARD_CONTROL; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -57,6 +57,7 @@ import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; import com.android.server.LocalServices; +import com.android.server.accessibility.autoclick.AutoclickController; import com.android.server.accessibility.gestures.TouchExplorer; import com.android.server.accessibility.magnification.FullScreenMagnificationGestureHandler; import com.android.server.accessibility.magnification.MagnificationGestureHandler; @@ -191,7 +192,7 @@ public class AccessibilityInputFilterTest { } @Test - @RequiresFlagsEnabled(FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES) + @RequiresFlagsEnabled(FLAG_ENABLE_MAGNIFICATION_KEYBOARD_CONTROL) public void testEventHandler_shouldIncreaseAndHaveCorrectOrderAfterOnDisplayAdded() { prepareLooper(); @@ -248,7 +249,7 @@ public class AccessibilityInputFilterTest { } @Test - @RequiresFlagsEnabled(FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES) + @RequiresFlagsEnabled(FLAG_ENABLE_MAGNIFICATION_KEYBOARD_CONTROL) public void testEventHandler_shouldHaveCorrectOrderForEventStreamTransformation() { prepareLooper(); @@ -274,7 +275,7 @@ public class AccessibilityInputFilterTest { } @Test - @RequiresFlagsDisabled(FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES) + @RequiresFlagsDisabled(FLAG_ENABLE_MAGNIFICATION_KEYBOARD_CONTROL) public void testEventHandler_shouldHaveCorrectOrderForEventStreamTransformation_noMagKeys() { prepareLooper(); @@ -455,7 +456,7 @@ public class AccessibilityInputFilterTest { } @Test - @RequiresFlagsEnabled(FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES) + @RequiresFlagsEnabled(FLAG_ENABLE_MAGNIFICATION_KEYBOARD_CONTROL) public void testEnabledFeatures_windowMagnificationMode_expectedMagnificationKeyHandler() { prepareLooper(); doReturn(Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW).when( @@ -468,7 +469,7 @@ public class AccessibilityInputFilterTest { } @Test - @RequiresFlagsEnabled(FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES) + @RequiresFlagsEnabled(FLAG_ENABLE_MAGNIFICATION_KEYBOARD_CONTROL) public void testEnabledFeatures_fullscreenMagnificationMode_expectedMagnificationKeyHandler() { prepareLooper(); doReturn(Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN).when( 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 5602fb76e6f5..28e5be505556 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java @@ -68,7 +68,6 @@ import android.annotation.UserIdInt; import android.app.PendingIntent; import android.app.RemoteAction; import android.app.admin.DevicePolicyManager; -import android.app.ecm.EnhancedConfirmationManager; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; @@ -2120,23 +2119,6 @@ public class AccessibilityManagerServiceTest { } @Test - @EnableFlags({android.permission.flags.Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED, - android.security.Flags.FLAG_EXTEND_ECM_TO_ALL_SETTINGS}) - public void isAccessibilityTargetAllowed_nonSystemUserId_useEcmWithNonSystemUserId() { - String fakePackageName = "FAKE_PACKAGE_NAME"; - int uid = 0; // uid is not used in the actual implementation when flags are on - int userId = mTestableContext.getUserId() + 1234; - when(mDevicePolicyManager.getPermittedAccessibilityServices(userId)).thenReturn( - List.of(fakePackageName)); - Context mockUserContext = mock(Context.class); - mTestableContext.addMockUserContext(userId, mockUserContext); - - mA11yms.isAccessibilityTargetAllowed(fakePackageName, uid, userId); - - verify(mockUserContext).getSystemService(EnhancedConfirmationManager.class); - } - - @Test @EnableFlags(com.android.hardware.input.Flags.FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES) public void handleKeyGestureEvent_toggleMagnifier() { mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AutoclickControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java index acce813ff659..f02bdae1d9e6 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AutoclickControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.accessibility; +package com.android.server.accessibility.autoclick; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; @@ -39,6 +39,8 @@ import android.view.MotionEvent; import android.view.WindowManager; import android.view.accessibility.AccessibilityManager; +import com.android.server.accessibility.AccessibilityTraceManager; + import org.junit.After; import org.junit.Before; import org.junit.Rule; diff --git a/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java index e5005d1beed4..1af59daa9c78 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java @@ -33,6 +33,8 @@ import static com.android.server.accessibility.gestures.TouchState.STATE_DRAGGIN import static com.android.server.accessibility.gestures.TouchState.STATE_GESTURE_DETECTING; import static com.android.server.accessibility.gestures.TouchState.STATE_TOUCH_EXPLORING; +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.verify; @@ -132,10 +134,12 @@ public class TouchExplorerTest { */ private class EventCaptor implements EventStreamTransformation { List<MotionEvent> mEvents = new ArrayList<>(); + List<MotionEvent> mRawEvents = new ArrayList<>(); @Override public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { mEvents.add(0, event.copy()); + mRawEvents.add(0, rawEvent.copy()); } @Override @@ -461,6 +465,45 @@ public class TouchExplorerTest { AccessibilityService.GESTURE_3_FINGER_SWIPE_DOWN); } + @Test + public void testSendHoverExitIfNeeded_lastSentHoverExit_noActionNeeded() { + // Prep TouchState so that the last injected hover event was a HOVER_EXIT + mTouchExplorer.getState().onInjectedMotionEvent(hoverExitEvent()); + + mTouchExplorer.sendHoverExitAndTouchExplorationGestureEndIfNeeded(/*policyFlags=*/0); + + assertNoCapturedEvents(); + } + + @Test + @EnableFlags(Flags.FLAG_EVENT_DISPATCHER_RAW_EVENT) + public void testSendHoverExitIfNeeded_lastSentHoverEnter_sendsHoverExit_withCorrectRawEvent() { + final MotionEvent rawEvent = downEvent(); + final MotionEvent modifiedEvent = hoverEnterEvent(); + // Use different display IDs just so that we can differentiate between the raw event and + // the modified event later during test assertions. + final int rawDisplayId = 123; + final int modifiedDisplayId = 456; + rawEvent.setDisplayId(rawDisplayId); + modifiedEvent.setDisplayId(modifiedDisplayId); + // Prep TouchState to track the last received modified and raw events + mTouchExplorer.getState().onReceivedMotionEvent(modifiedEvent, rawEvent, /*policyFlags=*/0); + // Prep TouchState so that the last injected hover event was not a HOVER_EXIT + mTouchExplorer.getState().onInjectedMotionEvent(modifiedEvent); + + mTouchExplorer.sendHoverExitAndTouchExplorationGestureEndIfNeeded(/*policyFlags=*/0); + + assertThat(getCapturedEvents().size()).isEqualTo(1); + assertThat(getCapturedRawEvents().size()).isEqualTo(1); + MotionEvent sentEvent = getCapturedEvents().get(0); + MotionEvent sentRawEvent = getCapturedRawEvents().get(0); + // TouchExplorer should send ACTION_HOVER_EXIT built from the last injected hover event + assertThat(sentEvent.getAction()).isEqualTo(ACTION_HOVER_EXIT); + assertThat(sentEvent.getDisplayId()).isEqualTo(modifiedDisplayId); + // ... while passing along the original raw (unmodified) event + assertThat(sentRawEvent.getDisplayId()).isEqualTo(rawDisplayId); + } + /** * Used to play back event data of a gesture by parsing the log into MotionEvents and sending * them to TouchExplorer. @@ -630,6 +673,10 @@ public class TouchExplorerTest { return ((EventCaptor) mCaptor).mEvents; } + private List<MotionEvent> getCapturedRawEvents() { + return ((EventCaptor) mCaptor).mRawEvents; + } + private MotionEvent cancelEvent() { mLastDownTime = SystemClock.uptimeMillis(); return fromTouchscreen( @@ -688,6 +735,20 @@ public class TouchExplorerTest { return event; } + private MotionEvent hoverEnterEvent() { + mLastDownTime = SystemClock.uptimeMillis(); + return fromTouchscreen( + MotionEvent.obtain( + mLastDownTime, mLastDownTime, ACTION_HOVER_ENTER, DEFAULT_X, DEFAULT_Y, 0)); + } + + private MotionEvent hoverExitEvent() { + mLastDownTime = SystemClock.uptimeMillis(); + return fromTouchscreen( + MotionEvent.obtain( + mLastDownTime, mLastDownTime, ACTION_HOVER_EXIT, DEFAULT_X, DEFAULT_Y, 0)); + } + private void moveEachPointers(MotionEvent event, PointF... points) { final float[] x = new float[points.length]; final float[] y = new float[points.length]; diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationKeyHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationKeyHandlerTest.java index d1ef33d8fb70..1c85086e2f42 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationKeyHandlerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationKeyHandlerTest.java @@ -16,7 +16,7 @@ package com.android.server.accessibility.magnification; -import static com.android.hardware.input.Flags.FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES; +import static com.android.server.accessibility.Flags.FLAG_ENABLE_MAGNIFICATION_KEYBOARD_CONTROL; import static com.android.server.accessibility.magnification.MagnificationController.PAN_DIRECTION_DOWN; import static com.android.server.accessibility.magnification.MagnificationController.PAN_DIRECTION_LEFT; import static com.android.server.accessibility.magnification.MagnificationController.PAN_DIRECTION_RIGHT; @@ -50,7 +50,7 @@ import org.mockito.MockitoAnnotations; * Tests for {@link MagnificationKeyHandler}. */ @RunWith(AndroidJUnit4.class) -@RequiresFlagsEnabled(FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES) +@RequiresFlagsEnabled(FLAG_ENABLE_MAGNIFICATION_KEYBOARD_CONTROL) public class MagnificationKeyHandlerTest { @Rule diff --git a/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java b/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java index ef9580c54de6..cbe2bfb26cd6 100644 --- a/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java +++ b/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java @@ -28,8 +28,10 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.Manifest; import android.app.AppOpsManager; import android.content.Context; +import android.content.pm.PackageManager; import android.content.res.Resources; import android.media.AudioDeviceAttributes; import android.media.AudioDeviceInfo; @@ -38,6 +40,7 @@ import android.media.AudioManager; import android.media.AudioSystem; import android.media.IAudioDeviceVolumeDispatcher; import android.media.VolumeInfo; +import android.os.IpcDataCache; import android.os.PermissionEnforcer; import android.os.RemoteException; import android.os.test.TestLooper; @@ -83,6 +86,7 @@ public class AbsoluteVolumeBehaviorTest { @Before public void setUp() throws Exception { + IpcDataCache.disableForTestMode(); mTestLooper = new TestLooper(); mContext = spy(ApplicationProvider.getApplicationContext()); @@ -93,6 +97,10 @@ public class AbsoluteVolumeBehaviorTest { when(mResources.getBoolean(com.android.internal.R.bool.config_useFixedVolume)) .thenReturn(false); + when(mContext.checkCallingOrSelfPermission( + Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)) + .thenReturn(PackageManager.PERMISSION_GRANTED); + mSpyAudioSystem = spy(new NoOpAudioSystemAdapter()); mSystemServer = new NoOpSystemServerAdapter(); mSettingsAdapter = new NoOpSettingsAdapter(); diff --git a/services/tests/servicestests/src/com/android/server/audio/DeviceVolumeBehaviorTest.java b/services/tests/servicestests/src/com/android/server/audio/DeviceVolumeBehaviorTest.java index 746645a8c585..541dbba67957 100644 --- a/services/tests/servicestests/src/com/android/server/audio/DeviceVolumeBehaviorTest.java +++ b/services/tests/servicestests/src/com/android/server/audio/DeviceVolumeBehaviorTest.java @@ -28,6 +28,7 @@ import android.media.AudioDeviceAttributes; import android.media.AudioDeviceInfo; import android.media.AudioManager; import android.media.IDeviceVolumeBehaviorDispatcher; +import android.os.IpcDataCache; import android.os.PermissionEnforcer; import android.os.test.TestLooper; import android.platform.test.annotations.Presubmit; @@ -69,6 +70,7 @@ public class DeviceVolumeBehaviorTest { @Before public void setUp() throws Exception { + IpcDataCache.disableForTestMode(); mTestLooper = new TestLooper(); mContext = InstrumentationRegistry.getTargetContext(); mAudioSystem = new NoOpAudioSystemAdapter(); diff --git a/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java b/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java index 6b41c434b80f..0bbae247d8bb 100644 --- a/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java +++ b/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java @@ -80,6 +80,7 @@ import android.media.AudioSystem; import android.media.IDeviceVolumeBehaviorDispatcher; import android.media.VolumeInfo; import android.media.audiopolicy.AudioVolumeGroup; +import android.os.IpcDataCache; import android.os.Looper; import android.os.PermissionEnforcer; import android.os.test.TestLooper; @@ -210,6 +211,8 @@ public class VolumeHelperTest { @Before public void setUp() throws Exception { + IpcDataCache.disableForTestMode(); + mContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); mTestLooper = new TestLooper(); diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionStopControllerTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionStopControllerTest.java index affcfc14034e..379079a0018c 100644 --- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionStopControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionStopControllerTest.java @@ -300,6 +300,22 @@ public class MediaProjectionStopControllerTest { } @Test + public void isStopReasonCallEnd_stopReasonCallEnd_returnsTrue() { + boolean result = + mStopController.isStopReasonCallEnd( + MediaProjectionStopController.STOP_REASON_CALL_END); + assertThat(result).isTrue(); + } + + @Test + public void isStopReasonCallEnd_stopReasonKeyguard_returnsFalse() { + boolean result = + mStopController.isStopReasonCallEnd( + MediaProjectionStopController.STOP_REASON_KEYGUARD); + assertThat(result).isFalse(); + } + + @Test @EnableFlags( android.companion.virtualdevice.flags.Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS) public void testKeyguardLockedStateChanged_unlocked() { diff --git a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java index 2f3bca031f46..fbb673a194b4 100644 --- a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java @@ -1762,7 +1762,8 @@ public final class DataManagerTest { /* rankingAdjustment= */ 0, /* isBubble= */ false, /* proposedImportance= */ 0, - /* sensitiveContent= */ false + /* sensitiveContent= */ false, + /* summarization = */ null ); return true; }).when(mRankingMap).getRanking(eq(key), @@ -1806,7 +1807,8 @@ public final class DataManagerTest { /* rankingAdjustment= */ 0, /* isBubble= */ false, /* proposedImportance= */ 0, - /* sensitiveContent= */ false + /* sensitiveContent= */ false, + /* summarization = */ null ); return true; }).when(mRankingMap).getRanking(eq(CUSTOM_KEY), diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest3.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest3.java index 81ebf867fe2d..9ca2282d1d8e 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest3.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest3.java @@ -80,7 +80,7 @@ public class ShortcutManagerTest3 extends BaseShortcutManagerTest { genPackageAddIntent(CALLING_PACKAGE, USER_10)); } - public void testSetDynamicShortcuts_noManifestShortcuts() { + public void disabled_testSetDynamicShortcuts_noManifestShortcuts() { mManager.setDynamicShortcuts(list( shortcut("s1", A1) )); @@ -171,9 +171,9 @@ public class ShortcutManagerTest3 extends BaseShortcutManagerTest { .haveRanksInOrder("ms1"); } - public void disabled_testSetDynamicShortcuts_withManifestShortcuts() { + public void disabled_Shortcuts_withManifestShortcuts() { runTestWithManifestShortcuts(() -> - disabled_testAddDynamicShortcuts_noManifestShortcuts()); + disabled_testSetDynamicShortcuts_noManifestShortcuts()); } public void disabled_testAddDynamicShortcuts_noManifestShortcuts() { @@ -266,10 +266,11 @@ public class ShortcutManagerTest3 extends BaseShortcutManagerTest { } public void disabled_testAddDynamicShortcuts_withManifestShortcuts() { - runTestWithManifestShortcuts(() -> disabled_testAddDynamicShortcuts_noManifestShortcuts()); + runTestWithManifestShortcuts(() -> + disabled_testAddDynamicShortcuts_noManifestShortcuts()); } - public void testUpdateShortcuts_noManifestShortcuts() { + public void disabled_testUpdateShortcuts_noManifestShortcuts() { mManager.addDynamicShortcuts(list( shortcut("s5", A1, 0), shortcut("s4", A1), @@ -375,11 +376,12 @@ public class ShortcutManagerTest3 extends BaseShortcutManagerTest { .haveIds("s3", "s2"); } - public void testUpdateShortcuts_withManifestShortcuts() { - runTestWithManifestShortcuts(() -> testUpdateShortcuts_noManifestShortcuts()); + public void disabled_testUpdateShortcuts_withManifestShortcuts() { + runTestWithManifestShortcuts(() -> + disabled_testUpdateShortcuts_noManifestShortcuts()); } - public void testDeleteDynamicShortcuts_noManifestShortcuts() { + public void disabled_testDeleteDynamicShortcuts_noManifestShortcuts() { mManager.addDynamicShortcuts(list( shortcut("s5", A1, 0), shortcut("s4", A1), @@ -436,11 +438,12 @@ public class ShortcutManagerTest3 extends BaseShortcutManagerTest { .haveIds("s2", "s4"); } - public void testDeleteDynamicShortcuts_withManifestShortcuts() { - runTestWithManifestShortcuts(() -> testDeleteDynamicShortcuts_noManifestShortcuts()); + public void disabled_testDeleteDynamicShortcuts_withManifestShortcuts() { + runTestWithManifestShortcuts(() -> + disabled_testDeleteDynamicShortcuts_noManifestShortcuts()); } - public void testDisableShortcuts_noManifestShortcuts() { + public void disabled_testDisableShortcuts_noManifestShortcuts() { mManager.addDynamicShortcuts(list( shortcut("s5", A1, 0), shortcut("s4", A1), @@ -509,8 +512,9 @@ public class ShortcutManagerTest3 extends BaseShortcutManagerTest { .haveIds("s4", "x2"); } - public void testDisableShortcuts_withManifestShortcuts() { - runTestWithManifestShortcuts(() -> testDisableShortcuts_noManifestShortcuts()); + public void disabled_testDisableShortcuts_withManifestShortcuts() { + runTestWithManifestShortcuts(() -> + disabled_testDisableShortcuts_noManifestShortcuts()); } public void testGetSharingShortcutCount() { diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest5.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest5.java index 400d3a891a8d..5562e3d7c532 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest5.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest5.java @@ -66,7 +66,7 @@ public class ShortcutManagerTest5 extends BaseShortcutManagerTest { mMyUserId = android.os.Process.myUserHandle().getIdentifier(); } - public void testGetPackageUid() { + public void disabled_testGetPackageUid() { assertTrue(mShortcutService.injectGetPackageUid( mMyPackage, mMyUserId) != 0); @@ -74,7 +74,7 @@ public class ShortcutManagerTest5 extends BaseShortcutManagerTest { "no.such.package", mMyUserId)); } - public void testGetPackageInfo() { + public void disabled_testGetPackageInfo() { PackageInfo pi = mShortcutService.getPackageInfo( mMyPackage, mMyUserId, /*signature*/ false); assertEquals(mMyPackage, pi.packageName); @@ -132,7 +132,7 @@ public class ShortcutManagerTest5 extends BaseShortcutManagerTest { meta.close(); } - public void testGetInstalledPackages() { + public void disabled_testGetInstalledPackages() { List<PackageInfo> apks = mShortcutService.getInstalledPackages(mMyUserId); Set<String> expectedPackages = set("com.android.settings", mMyPackage); diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest6.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest6.java index 7f7a4ae13245..306f908258d1 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest6.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest6.java @@ -26,7 +26,7 @@ import androidx.test.filters.SmallTest; @Presubmit @SmallTest public class ShortcutManagerTest6 extends BaseShortcutManagerTest { - public void testHasShortcutHostPermissionInner_with3pLauncher_complicated() { + public void disabled_testHasShortcutHostPermissionInner_with3pLauncher_complicated() { // Set the default launcher. prepareGetRoleHoldersAsUser(CALLING_PACKAGE_2, USER_10); assertFalse(mService.hasShortcutHostPermissionInner(PACKAGE_SYSTEM_LAUNCHER, USER_10)); diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest7.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest7.java index 86bde837bfe1..5a89db143b34 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest7.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest7.java @@ -100,7 +100,7 @@ public class ShortcutManagerTest7 extends BaseShortcutManagerTest { assertEquals(99, mService.mMaxUpdatesPerInterval); } - public void testRoot() throws Exception { + public void disabled_testRoot() throws Exception { mService.mMaxUpdatesPerInterval = 99; mInjectedCallingUid = Process.ROOT_UID; @@ -128,7 +128,7 @@ public class ShortcutManagerTest7 extends BaseShortcutManagerTest { assertEquals(1, mService.mMaxUpdatesPerInterval); } - public void testResetThrottling() throws Exception { + public void disabled_testResetThrottling() throws Exception { prepareCrossProfileDataSet(); runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { @@ -149,7 +149,7 @@ public class ShortcutManagerTest7 extends BaseShortcutManagerTest { }); } - public void testResetThrottling_user_not_running() throws Exception { + public void disabled_testResetThrottling_user_not_running() throws Exception { prepareCrossProfileDataSet(); runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { @@ -177,7 +177,7 @@ public class ShortcutManagerTest7 extends BaseShortcutManagerTest { }); } - public void testResetThrottling_user_running() throws Exception { + public void disabled_testResetThrottling_user_running() throws Exception { prepareCrossProfileDataSet(); runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { @@ -201,7 +201,7 @@ public class ShortcutManagerTest7 extends BaseShortcutManagerTest { }); } - public void testResetAllThrottling() throws Exception { + public void disabled_testResetAllThrottling() throws Exception { prepareCrossProfileDataSet(); runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { @@ -262,7 +262,7 @@ public class ShortcutManagerTest7 extends BaseShortcutManagerTest { "Launcher: ComponentInfo{com.android.test.1/name}"); } - public void testUnloadUser() throws Exception { + public void disabled_testUnloadUser() throws Exception { prepareCrossProfileDataSet(); assertNotNull(mService.getShortcutsForTest().get(USER_11)); @@ -276,7 +276,7 @@ public class ShortcutManagerTest7 extends BaseShortcutManagerTest { assertNull(mService.getShortcutsForTest().get(USER_11)); } - public void testClearShortcuts() throws Exception { + public void disabled_testClearShortcuts() throws Exception { mRunningUsers.put(USER_11, true); diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest8.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest8.java index 9b02a3abf14b..1c0ee09ccc6f 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest8.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest8.java @@ -84,7 +84,7 @@ public class ShortcutManagerTest8 extends BaseShortcutManagerTest { makeCallerForeground(); } - public void testGetParentOrSelfUserId() { + public void disabled_testGetParentOrSelfUserId() { assertEquals(USER_10, mService.getParentOrSelfUserId(USER_10)); assertEquals(USER_11, mService.getParentOrSelfUserId(USER_11)); assertEquals(USER_12, mService.getParentOrSelfUserId(USER_12)); @@ -222,7 +222,7 @@ public class ShortcutManagerTest8 extends BaseShortcutManagerTest { actualIntent.getFlags()); } - public void testNotForeground() { + public void disabled_testNotForeground() { setDefaultLauncher(USER_10, LAUNCHER_1); runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { @@ -336,7 +336,7 @@ public class ShortcutManagerTest8 extends BaseShortcutManagerTest { checkRequestPinShortcut(makeResultIntent()); } - public void testRequestPinShortcut_explicitTargetActivity() { + public void disabled_testRequestPinShortcut_explicitTargetActivity() { setDefaultLauncher(USER_10, LAUNCHER_1); setDefaultLauncher(USER_11, LAUNCHER_2); @@ -475,7 +475,7 @@ public class ShortcutManagerTest8 extends BaseShortcutManagerTest { } - public void testRequestPinShortcut_dynamicExists() { + public void disabled_testRequestPinShortcut_dynamicExists() { setDefaultLauncher(USER_10, LAUNCHER_1); final Icon res32x32 = Icon.createWithResource(getTestContext(), R.drawable.black_32x32); @@ -590,7 +590,7 @@ public class ShortcutManagerTest8 extends BaseShortcutManagerTest { }); } - public void testRequestPinShortcut_dynamicExists_alreadyPinned() { + public void disabled_testRequestPinShortcut_dynamicExists_alreadyPinned() { setDefaultLauncher(USER_10, LAUNCHER_1); final Icon res32x32 = Icon.createWithResource(getTestContext(), R.drawable.black_32x32); @@ -848,7 +848,7 @@ public class ShortcutManagerTest8 extends BaseShortcutManagerTest { }); } - public void testRequestPinShortcut_dynamicExists_alreadyPinnedByAnother() { + public void disabled_testRequestPinShortcut_dynamicExists_alreadyPinnedByAnother() { // Initially all launchers have the shortcut permission, until we call setDefaultLauncher(). runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { @@ -1041,7 +1041,7 @@ public class ShortcutManagerTest8 extends BaseShortcutManagerTest { /** * When trying to pin an existing shortcut, the new fields shouldn't override existing fields. */ - public void testRequestPinShortcut_dynamicExists_titleWontChange() { + public void disabled_testRequestPinShortcut_dynamicExists_titleWontChange() { setDefaultLauncher(USER_10, LAUNCHER_1); final Icon res32x32 = Icon.createWithResource(getTestContext(), R.drawable.black_32x32); @@ -1173,7 +1173,7 @@ public class ShortcutManagerTest8 extends BaseShortcutManagerTest { * The dynamic shortcut existed, but before accepting(), it's removed. Because the request * has a partial shortcut, accept() should fail. */ - public void testRequestPinShortcut_dynamicExists_thenRemoved_error() { + public void disabled_testRequestPinShortcut_dynamicExists_thenRemoved_error() { setDefaultLauncher(USER_10, LAUNCHER_1); runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { @@ -1231,7 +1231,7 @@ public class ShortcutManagerTest8 extends BaseShortcutManagerTest { * The dynamic shortcut existed, but before accepting(), it's removed. Because the request * has all the mandatory fields, we can go ahead and still publish it. */ - public void testRequestPinShortcut_dynamicExists_thenRemoved_okay() { + public void disabled_testRequestPinShortcut_dynamicExists_thenRemoved_okay() { setDefaultLauncher(USER_10, LAUNCHER_1); runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { @@ -1404,7 +1404,7 @@ public class ShortcutManagerTest8 extends BaseShortcutManagerTest { * The dynamic shortcut existed, but before accepting(), it's removed. Because the request * has a partial shortcut, accept() should fail. */ - public void testRequestPinShortcut_dynamicExists_thenDisabled_error() { + public void disabled_testRequestPinShortcut_dynamicExists_thenDisabled_error() { setDefaultLauncher(USER_10, LAUNCHER_1); runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java index 8fad01a7541d..2616ccbe474a 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java @@ -247,7 +247,8 @@ public class NotificationListenerServiceTest extends UiServiceTestCase { getRankingAdjustment(i), isBubble(i), getProposedImportance(i), - hasSensitiveContent(i) + hasSensitiveContent(i), + getSummarization(i) ); rankings[i] = ranking; } @@ -383,6 +384,13 @@ public class NotificationListenerServiceTest extends UiServiceTestCase { return index % 3 == 0; } + public static String getSummarization(int index) { + if ((android.app.Flags.nmSummarizationUi() || android.app.Flags.nmSummarization())) { + return "summary " + index; + } + return null; + } + private boolean isBubble(int index) { return index % 4 == 0; } 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 43302264964e..a8fcadd9dd74 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -51,6 +51,7 @@ 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_EFFECTS_SUPPRESSOR_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; @@ -123,7 +124,9 @@ import static android.service.notification.Flags.FLAG_REDACT_SENSITIVE_NOTIFICAT import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING; import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_CONVERSATIONS; import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ONGOING; +import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS; import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_EFFECTS; +import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_NOTIFICATION_EFFECTS; import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL; import static android.service.notification.NotificationListenerService.REASON_CANCEL; import static android.service.notification.NotificationListenerService.REASON_LOCKDOWN; @@ -163,6 +166,7 @@ import static junit.framework.Assert.fail; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertThrows; +import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyLong; @@ -253,7 +257,6 @@ import android.media.AudioAttributes; import android.media.AudioManager; import android.media.session.MediaSession; import android.net.ConnectivityManager; -import android.net.Network; import android.net.NetworkCapabilities; import android.net.Uri; import android.os.Binder; @@ -361,7 +364,6 @@ import org.junit.rules.TestRule; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.ArgumentMatcher; -import org.mockito.ArgumentMatchers; import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.Mockito; @@ -369,6 +371,9 @@ import org.mockito.MockitoAnnotations; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; +import platform.test.runner.parameterized.ParameterizedAndroidJunit4; +import platform.test.runner.parameterized.Parameters; + import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; @@ -384,9 +389,6 @@ import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.function.Consumer; -import platform.test.runner.parameterized.ParameterizedAndroidJunit4; -import platform.test.runner.parameterized.Parameters; - @SmallTest @RunWith(ParameterizedAndroidJunit4.class) @RunWithLooper @@ -571,6 +573,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Mock NotificationAttentionHelper mAttentionHelper; + @Mock + NetworkCapabilities mWifiNetworkCapabilities; + private NotificationManagerService.WorkerHandler mWorkerHandler; private class TestableToastCallback extends ITransientNotification.Stub { @@ -598,7 +603,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Parameters(name = "{0}") public static List<FlagsParameterization> getParams() { - return FlagsParameterization.allCombinationsOf(); + return FlagsParameterization.allCombinationsOf( + FLAG_NOTIFICATION_CLASSIFICATION); } public NotificationManagerServiceTest(FlagsParameterization flags) { @@ -771,7 +777,14 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mActivityIntentImmutable = spy(PendingIntent.getActivity(mContext, 0, new Intent().setPackage(mPkg), FLAG_IMMUTABLE)); - when(mConnectivityManager.getActiveNetwork()).thenReturn(null); + when(mWifiNetworkCapabilities.hasTransport(eq(NetworkCapabilities.TRANSPORT_WIFI))) + .thenReturn(true); + when(mWifiNetworkCapabilities + .hasCapability(eq(NetworkCapabilities.NET_CAPABILITY_VALIDATED))) + .thenReturn(true); + when(mWifiNetworkCapabilities + .hasCapability(eq(NetworkCapabilities.NET_CAPABILITY_TRUSTED))) + .thenReturn(true); initNMS(); } @@ -10880,6 +10893,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { Bundle signals = new Bundle(); signals.putInt(KEY_IMPORTANCE, IMPORTANCE_LOW); signals.putInt(KEY_USER_SENTIMENT, USER_SENTIMENT_NEGATIVE); + signals.putInt(KEY_TYPE, TYPE_PROMOTION); Adjustment adjustment = new Adjustment(r.getSbn().getPackageName(), r.getKey(), signals, "", r.getUser().getIdentifier()); @@ -11404,8 +11418,14 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { verify(mContext).sendBroadcastAsUser(eqIntent(expected), eq(UserHandle.of(mUserId))); } + private static Intent isIntentWithAction(String wantedAction) { + return argThat( + intent -> intent != null && wantedAction.equals(intent.getAction()) + ); + } + private static Intent eqIntent(Intent wanted) { - return ArgumentMatchers.argThat( + return argThat( new ArgumentMatcher<Intent>() { @Override public boolean matches(Intent argument) { @@ -14391,13 +14411,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void testMakeRankingUpdate_clearsHasSensitiveContentIfConnectedToWifi() { mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS, FLAG_REDACT_SENSITIVE_CONTENT_NOTIFICATIONS_ON_LOCKSCREEN); - when(mConnectivityManager.getActiveNetwork()).thenReturn(mock(Network.class)); - when(mConnectivityManager.getNetworkCapabilities(any())).thenReturn( - new NetworkCapabilities.Builder() - .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) - .build() - ); - mService.updateWifiConnectionState(); + mService.updateWifiConnectionState(mWifiNetworkCapabilities); when(mListeners.hasSensitiveContent(any())).thenReturn(true); NotificationRecord pkgA = new NotificationRecord(mContext, generateSbn("a", 1000, 9, 0), mTestNotificationChannel); @@ -14416,13 +14430,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void testMakeRankingUpdate_doesntClearHasSensitiveContentIfNotConnectedToWifi() { mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS, FLAG_REDACT_SENSITIVE_CONTENT_NOTIFICATIONS_ON_LOCKSCREEN); - when(mConnectivityManager.getActiveNetwork()).thenReturn(mock(Network.class)); - when(mConnectivityManager.getNetworkCapabilities(any())).thenReturn( - new NetworkCapabilities.Builder() - .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) - .build() - ); - mService.updateWifiConnectionState(); + mService.updateWifiConnectionState(mock(NetworkCapabilities.class)); when(mListeners.hasSensitiveContent(any())).thenReturn(true); NotificationRecord record = getSensitiveNotificationRecord(); mService.addNotification(record); @@ -14440,13 +14448,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void testMakeRankingUpdate_doesntClearHasSensitiveContentIfNotSysUi() { mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS); mSetFlagsRule.disableFlags(FLAG_REDACT_SENSITIVE_CONTENT_NOTIFICATIONS_ON_LOCKSCREEN); - when(mConnectivityManager.getActiveNetwork()).thenReturn(mock(Network.class)); - when(mConnectivityManager.getNetworkCapabilities(any())).thenReturn( - new NetworkCapabilities.Builder() - .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) - .build() - ); - mService.updateWifiConnectionState(); + mService.updateWifiConnectionState(mWifiNetworkCapabilities); when(mListeners.hasSensitiveContent(any())).thenReturn(true); NotificationRecord record = getSensitiveNotificationRecord(); mService.addNotification(record); @@ -14463,13 +14465,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void testMakeRankingUpdate_doesntClearHasSensitiveContentIfFlagDisabled() { mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS); mSetFlagsRule.disableFlags(FLAG_REDACT_SENSITIVE_CONTENT_NOTIFICATIONS_ON_LOCKSCREEN); - when(mConnectivityManager.getActiveNetwork()).thenReturn(mock(Network.class)); - when(mConnectivityManager.getNetworkCapabilities(any())).thenReturn( - new NetworkCapabilities.Builder() - .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) - .build() - ); - mService.updateWifiConnectionState(); + mService.updateWifiConnectionState(mWifiNetworkCapabilities); when(mListeners.hasSensitiveContent(any())).thenReturn(true); NotificationRecord record = getSensitiveNotificationRecord(); mService.addNotification(record); @@ -17506,6 +17502,66 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + @EnableFlags({Flags.FLAG_NM_BINDER_PERF_THROTTLE_EFFECTS_SUPPRESSOR_BROADCAST, + Flags.FLAG_NM_BINDER_PERF_REDUCE_ZEN_BROADCASTS}) + public void requestHintsFromListener_changingEffectsButNotSuppressor_noBroadcast() + throws Exception { + // Note that NM_BINDER_PERF_REDUCE_ZEN_BROADCASTS is not strictly necessary; however each + // path will do slightly different calls so we force one of them to simplify the test. + when(mUmInternal.getProfileIds(anyInt(), anyBoolean())).thenReturn(new int[]{mUserId}); + when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener); + INotificationListener token = mock(INotificationListener.class); + mService.isSystemUid = true; + + mBinderService.requestHintsFromListener(token, HINT_HOST_DISABLE_CALL_EFFECTS); + mTestableLooper.moveTimeForward(500); // more than ZEN_BROADCAST_DELAY + waitForIdle(); + + verify(mContext, times(1)).sendBroadcastMultiplePermissions( + isIntentWithAction(ACTION_EFFECTS_SUPPRESSOR_CHANGED), any(), any(), any()); + + // Same suppressor suppresses something else. + mBinderService.requestHintsFromListener(token, HINT_HOST_DISABLE_NOTIFICATION_EFFECTS); + mTestableLooper.moveTimeForward(500); // more than ZEN_BROADCAST_DELAY + waitForIdle(); + + // Still 1 total calls (the previous one). + verify(mContext, times(1)).sendBroadcastMultiplePermissions( + isIntentWithAction(ACTION_EFFECTS_SUPPRESSOR_CHANGED), any(), any(), any()); + } + + @Test + @EnableFlags({Flags.FLAG_NM_BINDER_PERF_THROTTLE_EFFECTS_SUPPRESSOR_BROADCAST, + Flags.FLAG_NM_BINDER_PERF_REDUCE_ZEN_BROADCASTS}) + public void requestHintsFromListener_changingSuppressor_throttlesBroadcast() throws Exception { + // Note that NM_BINDER_PERF_REDUCE_ZEN_BROADCASTS is not strictly necessary; however each + // path will do slightly different calls so we force one of them to simplify the test. + when(mUmInternal.getProfileIds(anyInt(), anyBoolean())).thenReturn(new int[]{mUserId}); + when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener); + INotificationListener token = mock(INotificationListener.class); + mService.isSystemUid = true; + + // Several updates in quick succession. + mBinderService.requestHintsFromListener(token, HINT_HOST_DISABLE_CALL_EFFECTS); + mBinderService.clearRequestedListenerHints(token); + mBinderService.requestHintsFromListener(token, HINT_HOST_DISABLE_NOTIFICATION_EFFECTS); + mBinderService.clearRequestedListenerHints(token); + mBinderService.requestHintsFromListener(token, HINT_HOST_DISABLE_CALL_EFFECTS); + mBinderService.clearRequestedListenerHints(token); + mBinderService.requestHintsFromListener(token, HINT_HOST_DISABLE_NOTIFICATION_EFFECTS); + + // No broadcasts yet! + verify(mContext, never()).sendBroadcastMultiplePermissions(any(), any(), any(), any()); + + mTestableLooper.moveTimeForward(500); // more than ZEN_BROADCAST_DELAY + waitForIdle(); + + // Only one broadcast after idle time. + verify(mContext, times(1)).sendBroadcastMultiplePermissions( + isIntentWithAction(ACTION_EFFECTS_SUPPRESSOR_CHANGED), any(), any(), any()); + } + + @Test @EnableFlags(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION) public void testApplyAdjustment_keyType_validType() throws Exception { final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordExtractorDataTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordExtractorDataTest.java index 9fe0e49c4ab8..09ebc23a1d7b 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordExtractorDataTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordExtractorDataTest.java @@ -29,12 +29,19 @@ import android.os.UserHandle; import android.service.notification.Adjustment; import android.service.notification.StatusBarNotification; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; + import com.android.server.UiServiceTestCase; +import org.junit.Rule; import org.junit.Test; public class NotificationRecordExtractorDataTest extends UiServiceTestCase { + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Test public void testHasDiffs_noDiffs() { NotificationRecord r = generateRecord(); @@ -57,7 +64,8 @@ public class NotificationRecordExtractorDataTest extends UiServiceTestCase { r.getRankingScore(), r.isConversation(), r.getProposedImportance(), - r.hasSensitiveContent()); + r.hasSensitiveContent(), + r.getSummarization()); assertFalse(extractorData.hasDiffForRankingLocked(r, 1)); assertFalse(extractorData.hasDiffForLoggingLocked(r, 1)); @@ -85,7 +93,8 @@ public class NotificationRecordExtractorDataTest extends UiServiceTestCase { r.getRankingScore(), r.isConversation(), r.getProposedImportance(), - r.hasSensitiveContent()); + r.hasSensitiveContent(), + r.getSummarization()); Bundle signals = new Bundle(); signals.putInt(Adjustment.KEY_IMPORTANCE_PROPOSAL, IMPORTANCE_HIGH); @@ -119,7 +128,8 @@ public class NotificationRecordExtractorDataTest extends UiServiceTestCase { r.getRankingScore(), r.isConversation(), r.getProposedImportance(), - r.hasSensitiveContent()); + r.hasSensitiveContent(), + r.getSummarization()); Bundle signals = new Bundle(); signals.putString(Adjustment.KEY_GROUP_KEY, "ranker_group"); @@ -154,7 +164,8 @@ public class NotificationRecordExtractorDataTest extends UiServiceTestCase { r.getRankingScore(), r.isConversation(), r.getProposedImportance(), - r.hasSensitiveContent()); + r.hasSensitiveContent(), + r.getSummarization()); Bundle signals = new Bundle(); signals.putBoolean(Adjustment.KEY_SENSITIVE_CONTENT, true); @@ -166,6 +177,42 @@ public class NotificationRecordExtractorDataTest extends UiServiceTestCase { assertTrue(extractorData.hasDiffForLoggingLocked(r, 1)); } + @Test + @EnableFlags(android.app.Flags.FLAG_NM_SUMMARIZATION) + public void testHasDiffs_summarization() { + NotificationRecord r = generateRecord(); + + NotificationRecordExtractorData extractorData = new NotificationRecordExtractorData( + 1, + r.getPackageVisibilityOverride(), + r.canShowBadge(), + r.canBubble(), + r.getNotification().isBubbleNotification(), + r.getChannel(), + r.getGroupKey(), + r.getPeopleOverride(), + r.getSnoozeCriteria(), + r.getUserSentiment(), + r.getSuppressedVisualEffects(), + r.getSystemGeneratedSmartActions(), + r.getSmartReplies(), + r.getImportance(), + r.getRankingScore(), + r.isConversation(), + r.getProposedImportance(), + r.hasSensitiveContent(), + r.getSummarization()); + + Bundle signals = new Bundle(); + signals.putString(Adjustment.KEY_SUMMARIZATION, "SUMMARIZED!"); + Adjustment adjustment = new Adjustment("pkg", r.getKey(), signals, "", 0); + r.addAdjustment(adjustment); + r.applyAdjustments(); + + assertTrue(extractorData.hasDiffForRankingLocked(r, 1)); + assertTrue(extractorData.hasDiffForLoggingLocked(r, 1)); + } + private NotificationRecord generateRecord() { NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_LOW); final Notification.Builder builder = new Notification.Builder(getContext()) diff --git a/services/tests/wmtests/src/com/android/server/policy/CombinationKeyTests.java b/services/tests/wmtests/src/com/android/server/policy/CombinationKeyTests.java index 038e1357159b..036e03c60091 100644 --- a/services/tests/wmtests/src/com/android/server/policy/CombinationKeyTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/CombinationKeyTests.java @@ -23,6 +23,7 @@ import static com.android.server.policy.PhoneWindowManager.POWER_VOLUME_UP_BEHAV import static com.android.server.policy.PhoneWindowManager.POWER_VOLUME_UP_BEHAVIOR_MUTE; import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.Presubmit; import android.view.ViewConfiguration; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -38,6 +39,7 @@ import org.junit.runner.RunWith; * Build/Install/Run: * atest WmTests:CombinationKeyTests */ +@Presubmit @MediumTest @RunWith(AndroidJUnit4.class) @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER_MULTI_KEY_GESTURES) diff --git a/services/tests/wmtests/src/com/android/server/policy/DeferredKeyActionExecutorTests.java b/services/tests/wmtests/src/com/android/server/policy/DeferredKeyActionExecutorTests.java index ca3787ec4546..bccdd67d33ed 100644 --- a/services/tests/wmtests/src/com/android/server/policy/DeferredKeyActionExecutorTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/DeferredKeyActionExecutorTests.java @@ -19,6 +19,7 @@ package com.android.server.policy; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import android.platform.test.annotations.Presubmit; import android.view.KeyEvent; import org.junit.Before; @@ -29,6 +30,7 @@ import org.junit.Test; * * <p>Build/Install/Run: atest WmTests:DeferredKeyActionExecutorTests */ +@Presubmit public final class DeferredKeyActionExecutorTests { private DeferredKeyActionExecutor mKeyActionExecutor; diff --git a/services/tests/wmtests/src/com/android/server/policy/KeyCombinationManagerTests.java b/services/tests/wmtests/src/com/android/server/policy/KeyCombinationManagerTests.java index 8b5f68a1e974..a912c177c863 100644 --- a/services/tests/wmtests/src/com/android/server/policy/KeyCombinationManagerTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/KeyCombinationManagerTests.java @@ -29,6 +29,7 @@ import static org.testng.Assert.assertTrue; import android.os.Handler; import android.os.Looper; import android.os.SystemClock; +import android.platform.test.annotations.Presubmit; import android.view.KeyEvent; import androidx.test.filters.SmallTest; @@ -45,7 +46,7 @@ import java.util.concurrent.TimeUnit; * Build/Install/Run: * atest KeyCombinationManagerTests */ - +@Presubmit @SmallTest public class KeyCombinationManagerTests { private KeyCombinationManager mKeyCombinationManager; 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 85ef466b2477..16c786b52655 100644 --- a/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java @@ -544,17 +544,6 @@ public class KeyGestureEventTests extends ShortcutKeyTestBase { } @Test - public void testKeyGestureSplitscreenFocus() { - Assert.assertTrue(sendKeyGestureEventComplete( - KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT)); - mPhoneWindowManager.assertSetSplitscreenFocus(true); - - Assert.assertTrue(sendKeyGestureEventComplete( - KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT)); - mPhoneWindowManager.assertSetSplitscreenFocus(false); - } - - @Test public void testKeyGestureShortcutHelper() { Assert.assertTrue(sendKeyGestureEventComplete( KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER)); diff --git a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java index 82a5add407f4..d961a6acc9c1 100644 --- a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java @@ -42,6 +42,7 @@ import android.os.Handler; import android.os.Looper; import android.os.UserHandle; import android.platform.test.annotations.EnableFlags; +import android.platform.test.annotations.Presubmit; import android.platform.test.flag.junit.SetFlagsRule; import android.view.KeyEvent; import android.view.KeyboardShortcutGroup; @@ -64,7 +65,7 @@ import java.util.Collections; * Build/Install/Run: * atest ModifierShortcutManagerTests */ - +@Presubmit @SmallTest @EnableFlags(com.android.hardware.input.Flags.FLAG_MODIFIER_SHORTCUT_MANAGER_REFACTOR) public class ModifierShortcutManagerTests { @@ -127,7 +128,7 @@ public class ModifierShortcutManagerTests { // Total valid shortcuts. KeyboardShortcutGroup group = mModifierShortcutManager.getApplicationLaunchKeyboardShortcuts(-1); - assertEquals(13, group.getItems().size()); + assertEquals(11, group.getItems().size()); // Total valid shift shortcuts. assertEquals(3, group.getItems().stream() diff --git a/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java b/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java index af3dc1da4dcc..c73ce23fe6b5 100644 --- a/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java @@ -48,6 +48,7 @@ import android.app.AppOpsManager; import android.content.Context; import android.hardware.input.InputManager; import android.os.PowerManager; +import android.platform.test.annotations.Presubmit; import android.platform.test.flag.junit.SetFlagsRule; import androidx.test.filters.SmallTest; @@ -72,6 +73,7 @@ import org.junit.Test; * Build/Install/Run: * atest WmTests:PhoneWindowManagerTests */ +@Presubmit @SmallTest public class PhoneWindowManagerTests { diff --git a/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java index 33ccec3f785a..53e82bad818d 100644 --- a/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java @@ -28,6 +28,7 @@ import static com.android.server.policy.PhoneWindowManager.SHORT_PRESS_POWER_GO_ import static org.junit.Assert.assertEquals; import android.platform.test.annotations.EnableFlags; +import android.platform.test.annotations.Presubmit; import android.provider.Settings; import android.view.Display; @@ -40,6 +41,7 @@ import org.junit.Test; * Build/Install/Run: * atest WmTests:PowerKeyGestureTests */ +@Presubmit public class PowerKeyGestureTests extends ShortcutKeyTestBase { @Before public void setUp() { diff --git a/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java index ff8b6d3c1962..6c6594220bad 100644 --- a/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java @@ -23,6 +23,8 @@ import static android.view.KeyEvent.KEYCODE_POWER; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; +import static com.android.hardware.input.Flags.FLAG_ABORT_SLOW_MULTI_PRESS; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -35,9 +37,14 @@ import android.os.HandlerThread; import android.os.Looper; import android.os.Process; import android.os.SystemClock; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.annotations.Presubmit; +import android.platform.test.flag.junit.SetFlagsRule; import android.view.KeyEvent; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import java.util.concurrent.BlockingQueue; @@ -51,7 +58,10 @@ import java.util.concurrent.TimeUnit; * Build/Install/Run: * atest WmTests:SingleKeyGestureTests */ +@Presubmit public class SingleKeyGestureTests { + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + private SingleKeyGestureDetector mDetector; private int mMaxMultiPressCount = 3; @@ -260,6 +270,44 @@ public class SingleKeyGestureTests { } @Test + @EnableFlags(FLAG_ABORT_SLOW_MULTI_PRESS) + public void testMultipress_noLongPressBehavior_longPressCancelsMultiPress() + throws InterruptedException { + mLongPressOnPowerBehavior = false; + + pressKey(KEYCODE_POWER, 0 /* pressTime */); + pressKey(KEYCODE_POWER, mLongPressTime /* pressTime */); + + assertFalse(mMultiPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS)); + } + + @Test + @EnableFlags(FLAG_ABORT_SLOW_MULTI_PRESS) + public void testMultipress_noVeryLongPressBehavior_veryLongPressCancelsMultiPress() + throws InterruptedException { + mLongPressOnPowerBehavior = false; + mVeryLongPressOnPowerBehavior = false; + + pressKey(KEYCODE_POWER, 0 /* pressTime */); + pressKey(KEYCODE_POWER, mVeryLongPressTime /* pressTime */); + + assertFalse(mMultiPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS)); + } + + @Test + @DisableFlags(FLAG_ABORT_SLOW_MULTI_PRESS) + public void testMultipress_flagDisabled_noLongPressBehavior_longPressDoesNotCancelMultiPress() + throws InterruptedException { + mLongPressOnPowerBehavior = false; + mExpectedMultiPressCount = 2; + + pressKey(KEYCODE_POWER, 0 /* pressTime */); + pressKey(KEYCODE_POWER, mLongPressTime /* pressTime */); + + assertTrue(mMultiPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS)); + } + + @Test public void testMultiPress() throws InterruptedException { // Double presses. mExpectedMultiPressCount = 2; diff --git a/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java index 3ea3235df0f4..833dd5d768e4 100644 --- a/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java @@ -35,6 +35,7 @@ import android.app.ActivityTaskManager.RootTaskInfo; import android.content.ComponentName; import android.hardware.input.KeyGestureEvent; import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; import android.provider.Settings; import android.view.Display; @@ -47,6 +48,7 @@ import org.junit.Test; * Build/Install/Run: * atest WmTests:StemKeyGestureTests */ +@Presubmit public class StemKeyGestureTests extends ShortcutKeyTestBase { private static final String TEST_TARGET_ACTIVITY = "com.android.server.policy/.TestActivity"; 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 4ff3d433632a..f88492477487 100644 --- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java +++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java @@ -925,11 +925,6 @@ class TestPhoneWindowManager { verify(mStatusBarManagerInternal).moveFocusedTaskToStageSplit(anyInt(), eq(leftOrTop)); } - void assertSetSplitscreenFocus(boolean leftOrTop) { - mTestLooper.dispatchAll(); - verify(mStatusBarManagerInternal).setSplitscreenFocus(eq(leftOrTop)); - } - void assertStatusBarStartAssist() { mTestLooper.dispatchAll(); verify(mStatusBarManagerInternal).startAssist(any()); diff --git a/services/tests/wmtests/src/com/android/server/policy/WindowWakeUpPolicyTests.java b/services/tests/wmtests/src/com/android/server/policy/WindowWakeUpPolicyTests.java index 3ca352cfa60d..9e59bced01f1 100644 --- a/services/tests/wmtests/src/com/android/server/policy/WindowWakeUpPolicyTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/WindowWakeUpPolicyTests.java @@ -57,6 +57,7 @@ import android.content.res.Resources; import android.os.PowerManager; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; +import android.platform.test.annotations.Presubmit; import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; import android.view.Display; @@ -83,6 +84,7 @@ import java.util.function.BooleanSupplier; * * <p>Build/Install/Run: atest WmTests:WindowWakeUpPolicyTests */ +@Presubmit public final class WindowWakeUpPolicyTests { @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule(); @Rule public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule(); 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 5ac3e483231c..7af4ede05363 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -4461,7 +4461,46 @@ public class SizeCompatTests extends WindowTestsBase { // are aligned to the top of the parentAppBounds assertEquals(new Rect(0, notchHeight, 1000, 1200), appBounds); assertEquals(new Rect(0, 0, 1000, 1200), bounds); + } + + @Test + @DisableCompatChanges({ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED}) + public void testInFreeform_boundsSandboxedToAppBounds() { + final int dw = 2800; + final int dh = 1400; + final int notchHeight = 100; + final DisplayContent display = new TestDisplayContent.Builder(mAtm, dw, dh) + .setNotch(notchHeight) + .build(); + setUpApp(display); + prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT); + mTask.mDisplayContent.getDefaultTaskDisplayArea() + .setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM); + mTask.setWindowingMode(WINDOWING_MODE_FREEFORM); + Rect appBounds = new Rect(0, 0, 1000, 500); + Rect bounds = new Rect(0, 0, 1000, 600); + mTask.getWindowConfiguration().setAppBounds(appBounds); + mTask.getWindowConfiguration().setBounds(bounds); + mActivity.onConfigurationChanged(mTask.getConfiguration()); + + // Bounds are sandboxed to appBounds in freeform. + assertDownScaled(); + assertEquals(mActivity.getWindowConfiguration().getAppBounds(), + mActivity.getWindowConfiguration().getBounds()); + + // Exit freeform. + mTask.mDisplayContent.getDefaultTaskDisplayArea() + .setWindowingMode(WindowConfiguration.WINDOWING_MODE_FULLSCREEN); + mTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + mTask.getWindowConfiguration().setBounds(new Rect(0, 0, dw, dh)); + mActivity.onConfigurationChanged(mTask.getConfiguration()); + assertFitted(); + appBounds = mActivity.getWindowConfiguration().getAppBounds(); + bounds = mActivity.getWindowConfiguration().getBounds(); + // Bounds are not sandboxed to appBounds. + assertNotEquals(appBounds, bounds); + assertEquals(notchHeight, appBounds.top - bounds.top); } @Test diff --git a/services/usb/OWNERS b/services/usb/OWNERS index 2dff392d4e34..259261252032 100644 --- a/services/usb/OWNERS +++ b/services/usb/OWNERS @@ -1,9 +1,9 @@ -anothermark@google.com +vmartensson@google.com +nkapron@google.com febinthattil@google.com -aprasath@google.com +shubhankarm@google.com badhri@google.com elaurent@google.com albertccwang@google.com jameswei@google.com howardyen@google.com -kumarashishg@google.com
\ No newline at end of file diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl index b32379ae4b1e..4d9df4666016 100644 --- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl +++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl @@ -415,4 +415,5 @@ interface ITelecomService { */ boolean hasForegroundServiceDelegation(in PhoneAccountHandle phoneAccountHandle, String callingPackage); + void setMetricsTestMode(boolean enabled); } diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index d3f98d1a1d70..0b3d720bf52a 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -3219,7 +3219,6 @@ public class CarrierConfigManager { * The roaming indicator will be shown if this is {@code true} and will not be shown if this is * {@code false}. */ - @FlaggedApi(Flags.FLAG_HIDE_ROAMING_ICON) public static final String KEY_SHOW_ROAMING_INDICATOR_BOOL = "show_roaming_indicator_bool"; /** diff --git a/telephony/java/android/telephony/PreciseDisconnectCause.java b/telephony/java/android/telephony/PreciseDisconnectCause.java index 2d650ab20802..bb9e7065b154 100644 --- a/telephony/java/android/telephony/PreciseDisconnectCause.java +++ b/telephony/java/android/telephony/PreciseDisconnectCause.java @@ -326,6 +326,13 @@ public final class PreciseDisconnectCause { @Deprecated public static final int CDMA_ACCESS_BLOCKED = 1009; + /** Call was disconnected with cause code retry over volte. */ + @FlaggedApi(Flags.FLAG_ADD_IMS_REDIAL_CODES_FOR_EMERGENCY_CALLS) + public static final int EMERGENCY_REDIAL_ON_IMS = 3001; + /** Call was disconnected with cause code retry over vowifi. */ + @FlaggedApi(Flags.FLAG_ADD_IMS_REDIAL_CODES_FOR_EMERGENCY_CALLS) + public static final int EMERGENCY_REDIAL_ON_VOWIFI = 3002; + /* OEM specific error codes. To be used by OEMs when they don't want to reveal error code which would be replaced by ERROR_UNSPECIFIED */ public static final int OEM_CAUSE_1 = 0xf001; diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index da4165553e57..6d32303fb13b 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -2447,6 +2447,7 @@ public class TelephonyManager { * as follows: * * <ul> + * <li>If the device is running Android 25Q2 or later, then null is returned.</li> * <li>If the calling app's target SDK is API level 28 or lower and the app has the * READ_PHONE_STATE permission then null is returned.</li> * <li>If the calling app's target SDK is API level 28 or lower and the app does not have @@ -2455,7 +2456,8 @@ public class TelephonyManager { * </ul> * * @deprecated Legacy CDMA is unsupported. - * @throws UnsupportedOperationException If the device does not have + * @throws UnsupportedOperationException If the device is running + * Android 25Q1 or earlier and does not have * {@link PackageManager#FEATURE_TELEPHONY_CDMA}. */ @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java index 567314beadd3..5daa29b940bf 100644 --- a/telephony/java/android/telephony/data/ApnSetting.java +++ b/telephony/java/android/telephony/data/ApnSetting.java @@ -130,10 +130,10 @@ public class ApnSetting implements Parcelable { public static final int TYPE_RCS = ApnTypes.RCS; /** APN type for OEM_PAID networks (Automotive PANS) */ @FlaggedApi(Flags.FLAG_OEM_PAID_PRIVATE) - public static final int TYPE_OEM_PAID = 1 << 16; // TODO(b/366194627): ApnTypes.OEM_PAID; + public static final int TYPE_OEM_PAID = ApnTypes.OEM_PAID; /** APN type for OEM_PRIVATE networks (Automotive PANS) */ @FlaggedApi(Flags.FLAG_OEM_PAID_PRIVATE) - public static final int TYPE_OEM_PRIVATE = 1 << 17; // TODO(b/366194627): ApnTypes.OEM_PRIVATE; + public static final int TYPE_OEM_PRIVATE = ApnTypes.OEM_PRIVATE; /** @hide */ @IntDef(flag = true, prefix = {"TYPE_"}, value = { diff --git a/telephony/java/android/telephony/satellite/SatelliteSubscriberInfo.java b/telephony/java/android/telephony/satellite/SatelliteSubscriberInfo.java index 9d9cac9702bb..d62fd63aeda6 100644 --- a/telephony/java/android/telephony/satellite/SatelliteSubscriberInfo.java +++ b/telephony/java/android/telephony/satellite/SatelliteSubscriberInfo.java @@ -22,8 +22,10 @@ import android.annotation.NonNull; import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; +import android.telephony.Rlog; import com.android.internal.telephony.flags.Flags; +import com.android.internal.telephony.util.TelephonyUtils; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -220,7 +222,7 @@ public final class SatelliteSubscriberInfo implements Parcelable { StringBuilder sb = new StringBuilder(); sb.append("SubscriberId:"); - sb.append(mSubscriberId); + sb.append(Rlog.pii(TelephonyUtils.IS_DEBUGGABLE, mSubscriberId)); sb.append(","); sb.append("CarrierId:"); diff --git a/telephony/java/android/telephony/satellite/SatelliteSubscriberProvisionStatus.java b/telephony/java/android/telephony/satellite/SatelliteSubscriberProvisionStatus.java index fb4f89ded547..75d3ec6d3fe6 100644 --- a/telephony/java/android/telephony/satellite/SatelliteSubscriberProvisionStatus.java +++ b/telephony/java/android/telephony/satellite/SatelliteSubscriberProvisionStatus.java @@ -21,8 +21,10 @@ import android.annotation.NonNull; import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; +import android.telephony.Rlog; import com.android.internal.telephony.flags.Flags; +import com.android.internal.telephony.util.TelephonyUtils; import java.util.Objects; @@ -132,7 +134,7 @@ public final class SatelliteSubscriberProvisionStatus implements Parcelable { StringBuilder sb = new StringBuilder(); sb.append("SatelliteSubscriberInfo:"); - sb.append(mSubscriberInfo); + sb.append(Rlog.pii(TelephonyUtils.IS_DEBUGGABLE, mSubscriberInfo)); sb.append(","); sb.append("ProvisionStatus:"); 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 index fe344c9b79f2..7b8b43af18da 100644 --- 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 @@ -25,7 +25,7 @@ import com.android.server.wm.flicker.testapp.ActivityOptions.BottomHalfPip class BottomHalfPipAppHelper( instrumentation: Instrumentation, private val useLaunchingActivity: Boolean = false, - private val fillTaskOnCreate: Boolean = true, + private val fillTaskOnCreate: Boolean = false, ) : PipAppHelper( instrumentation, appName = BottomHalfPip.LABEL, diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/RecentTasksUtils.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/RecentTasksUtils.kt index aa262f9dfd6a..1a5fda7c8324 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/RecentTasksUtils.kt +++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/RecentTasksUtils.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.utils +package com.android.server.wm.flicker.helpers import android.app.Instrumentation @@ -24,4 +24,4 @@ object RecentTasksUtils { "dumpsys activity service SystemUIService WMShell recents clearAll" ) } -}
\ No newline at end of file +} 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 index 3bbb94515f69..1f82d674cfa7 100644 --- 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 @@ -31,7 +31,7 @@ import androidx.annotation.NonNull; public class BottomHalfPipActivity extends PipActivity { - private boolean mUseBottomHalfLayout; + private boolean mUseBottomHalfLayout = true; @Override public void onCreate(Bundle savedInstanceState) { diff --git a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt index d75a8f433af8..40f4f1ab0791 100644 --- a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt +++ b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt @@ -314,7 +314,17 @@ class InputManagerServiceTests { } } - private fun createVirtualDisplays(count: Int): List<VirtualDisplay> { + private class AutoClosingVirtualDisplays(val displays: List<VirtualDisplay>) : AutoCloseable { + operator fun get(i: Int): VirtualDisplay = displays[i] + + override fun close() { + for (display in displays) { + display.release() + } + } + } + + private fun createVirtualDisplays(count: Int): AutoClosingVirtualDisplays { val displayManager: DisplayManager = context.getSystemService( DisplayManager::class.java ) as DisplayManager @@ -329,7 +339,7 @@ class InputManagerServiceTests { /* flags= */ 0 )) } - return virtualDisplays + return AutoClosingVirtualDisplays(virtualDisplays) } // Helper function that creates a KeyEvent with Keycode A with the given action @@ -374,50 +384,51 @@ class InputManagerServiceTests { val mockSurfaceHolder2 = mock(SurfaceHolder::class.java) `when`(mockSurfaceView2.holder).thenReturn(mockSurfaceHolder2) - val virtualDisplays = createVirtualDisplays(2) + createVirtualDisplays(2).use { virtualDisplays -> + // Simulate an InputDevice + val inputDevice = createInputDevice() - // Simulate an InputDevice - val inputDevice = createInputDevice() - - // Associate input device with display - service.addUniqueIdAssociationByDescriptor( - inputDevice.descriptor, - virtualDisplays[0].display.displayId.toString() - ) + // Associate input device with display + service.addUniqueIdAssociationByDescriptor( + inputDevice.descriptor, + virtualDisplays[0].display.displayId.toString() + ) - // Simulate 2 different KeyEvents - val downEvent = createKeycodeAEvent(inputDevice, KeyEvent.ACTION_DOWN) - val upEvent = createKeycodeAEvent(inputDevice, KeyEvent.ACTION_UP) + // Simulate 2 different KeyEvents + val downEvent = createKeycodeAEvent(inputDevice, KeyEvent.ACTION_DOWN) + val upEvent = createKeycodeAEvent(inputDevice, KeyEvent.ACTION_UP) - // Create a mock OnKeyListener object - val mockOnKeyListener = mock(OnKeyListener::class.java) + // Create a mock OnKeyListener object + val mockOnKeyListener = mock(OnKeyListener::class.java) - // Verify that the event went to Display 1 not Display 2 - service.injectInputEvent(downEvent, InputEventInjectionSync.NONE) + // Verify that the event went to Display 1 not Display 2 + service.injectInputEvent(downEvent, InputEventInjectionSync.NONE) - // Call the onKey method on the mock OnKeyListener object - mockOnKeyListener.onKey(mockSurfaceView1, /* keyCode= */ KeyEvent.KEYCODE_A, downEvent) - mockOnKeyListener.onKey(mockSurfaceView2, /* keyCode= */ KeyEvent.KEYCODE_A, upEvent) + // Call the onKey method on the mock OnKeyListener object + mockOnKeyListener.onKey(mockSurfaceView1, /* keyCode= */ KeyEvent.KEYCODE_A, downEvent) + mockOnKeyListener.onKey(mockSurfaceView2, /* keyCode= */ KeyEvent.KEYCODE_A, upEvent) - // Verify that the onKey method was called with the expected arguments - verify(mockOnKeyListener).onKey(mockSurfaceView1, KeyEvent.KEYCODE_A, downEvent) - verify(mockOnKeyListener, never()).onKey(mockSurfaceView2, KeyEvent.KEYCODE_A, downEvent) + // Verify that the onKey method was called with the expected arguments + verify(mockOnKeyListener).onKey(mockSurfaceView1, KeyEvent.KEYCODE_A, downEvent) + verify(mockOnKeyListener, never()) + .onKey(mockSurfaceView2, KeyEvent.KEYCODE_A, downEvent) - // Remove association - service.removeUniqueIdAssociationByDescriptor(inputDevice.descriptor) + // Remove association + service.removeUniqueIdAssociationByDescriptor(inputDevice.descriptor) - // Associate with Display 2 - service.addUniqueIdAssociationByDescriptor( - inputDevice.descriptor, - virtualDisplays[1].display.displayId.toString() - ) + // Associate with Display 2 + service.addUniqueIdAssociationByDescriptor( + inputDevice.descriptor, + virtualDisplays[1].display.displayId.toString() + ) - // Simulate a KeyEvent - service.injectInputEvent(upEvent, InputEventInjectionSync.NONE) + // Simulate a KeyEvent + service.injectInputEvent(upEvent, InputEventInjectionSync.NONE) - // Verify that the event went to Display 2 not Display 1 - verify(mockOnKeyListener).onKey(mockSurfaceView2, KeyEvent.KEYCODE_A, upEvent) - verify(mockOnKeyListener, never()).onKey(mockSurfaceView1, KeyEvent.KEYCODE_A, upEvent) + // Verify that the event went to Display 2 not Display 1 + verify(mockOnKeyListener).onKey(mockSurfaceView2, KeyEvent.KEYCODE_A, upEvent) + verify(mockOnKeyListener, never()).onKey(mockSurfaceView1, KeyEvent.KEYCODE_A, upEvent) + } } @Test @@ -436,50 +447,51 @@ class InputManagerServiceTests { val mockSurfaceHolder2 = mock(SurfaceHolder::class.java) `when`(mockSurfaceView2.holder).thenReturn(mockSurfaceHolder2) - val virtualDisplays = createVirtualDisplays(2) + createVirtualDisplays(2).use { virtualDisplays -> + // Simulate an InputDevice + val inputDevice = createInputDevice() - // Simulate an InputDevice - val inputDevice = createInputDevice() - - // Associate input device with display - service.addUniqueIdAssociationByPort( - inputDevice.name, - virtualDisplays[0].display.displayId.toString() - ) + // Associate input device with display + service.addUniqueIdAssociationByPort( + inputDevice.name, + virtualDisplays[0].display.displayId.toString() + ) - // Simulate 2 different KeyEvents - val downEvent = createKeycodeAEvent(inputDevice, KeyEvent.ACTION_DOWN) - val upEvent = createKeycodeAEvent(inputDevice, KeyEvent.ACTION_UP) + // Simulate 2 different KeyEvents + val downEvent = createKeycodeAEvent(inputDevice, KeyEvent.ACTION_DOWN) + val upEvent = createKeycodeAEvent(inputDevice, KeyEvent.ACTION_UP) - // Create a mock OnKeyListener object - val mockOnKeyListener = mock(OnKeyListener::class.java) + // Create a mock OnKeyListener object + val mockOnKeyListener = mock(OnKeyListener::class.java) - // Verify that the event went to Display 1 not Display 2 - service.injectInputEvent(downEvent, InputEventInjectionSync.NONE) + // Verify that the event went to Display 1 not Display 2 + service.injectInputEvent(downEvent, InputEventInjectionSync.NONE) - // Call the onKey method on the mock OnKeyListener object - mockOnKeyListener.onKey(mockSurfaceView1, /* keyCode= */ KeyEvent.KEYCODE_A, downEvent) - mockOnKeyListener.onKey(mockSurfaceView2, /* keyCode= */ KeyEvent.KEYCODE_A, upEvent) + // Call the onKey method on the mock OnKeyListener object + mockOnKeyListener.onKey(mockSurfaceView1, /* keyCode= */ KeyEvent.KEYCODE_A, downEvent) + mockOnKeyListener.onKey(mockSurfaceView2, /* keyCode= */ KeyEvent.KEYCODE_A, upEvent) - // Verify that the onKey method was called with the expected arguments - verify(mockOnKeyListener).onKey(mockSurfaceView1, KeyEvent.KEYCODE_A, downEvent) - verify(mockOnKeyListener, never()).onKey(mockSurfaceView2, KeyEvent.KEYCODE_A, downEvent) + // Verify that the onKey method was called with the expected arguments + verify(mockOnKeyListener).onKey(mockSurfaceView1, KeyEvent.KEYCODE_A, downEvent) + verify(mockOnKeyListener, never()) + .onKey(mockSurfaceView2, KeyEvent.KEYCODE_A, downEvent) - // Remove association - service.removeUniqueIdAssociationByPort(inputDevice.name) + // Remove association + service.removeUniqueIdAssociationByPort(inputDevice.name) - // Associate with Display 2 - service.addUniqueIdAssociationByPort( - inputDevice.name, - virtualDisplays[1].display.displayId.toString() - ) + // Associate with Display 2 + service.addUniqueIdAssociationByPort( + inputDevice.name, + virtualDisplays[1].display.displayId.toString() + ) - // Simulate a KeyEvent - service.injectInputEvent(upEvent, InputEventInjectionSync.NONE) + // Simulate a KeyEvent + service.injectInputEvent(upEvent, InputEventInjectionSync.NONE) - // Verify that the event went to Display 2 not Display 1 - verify(mockOnKeyListener).onKey(mockSurfaceView2, KeyEvent.KEYCODE_A, upEvent) - verify(mockOnKeyListener, never()).onKey(mockSurfaceView1, KeyEvent.KEYCODE_A, upEvent) + // Verify that the event went to Display 2 not Display 1 + verify(mockOnKeyListener).onKey(mockSurfaceView2, KeyEvent.KEYCODE_A, upEvent) + verify(mockOnKeyListener, never()).onKey(mockSurfaceView1, KeyEvent.KEYCODE_A, upEvent) + } } @Test diff --git a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt index e0532633d40b..eef4e6f58463 100644 --- a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt +++ b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt @@ -467,30 +467,6 @@ class KeyGestureControllerTests { intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) ), TestData( - "CTRL + ALT + DPAD_LEFT -> Change Splitscreen Focus Left", - intArrayOf( - KeyEvent.KEYCODE_CTRL_LEFT, - KeyEvent.KEYCODE_ALT_LEFT, - KeyEvent.KEYCODE_DPAD_LEFT - ), - KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT, - intArrayOf(KeyEvent.KEYCODE_DPAD_LEFT), - KeyEvent.META_CTRL_ON or KeyEvent.META_ALT_ON, - intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) - ), - TestData( - "CTRL + ALT + DPAD_RIGHT -> Change Splitscreen Focus Right", - intArrayOf( - KeyEvent.KEYCODE_CTRL_LEFT, - KeyEvent.KEYCODE_ALT_LEFT, - KeyEvent.KEYCODE_DPAD_RIGHT - ), - KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT, - intArrayOf(KeyEvent.KEYCODE_DPAD_RIGHT), - KeyEvent.META_CTRL_ON or KeyEvent.META_ALT_ON, - intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) - ), - TestData( "META + / -> Open Shortcut Helper", intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_SLASH), KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER, diff --git a/tests/Input/src/com/android/test/input/UinputRecordingIntegrationTests.kt b/tests/Input/src/com/android/test/input/UinputRecordingIntegrationTests.kt index 9e0f7347943d..bc0b7a5c816c 100644 --- a/tests/Input/src/com/android/test/input/UinputRecordingIntegrationTests.kt +++ b/tests/Input/src/com/android/test/input/UinputRecordingIntegrationTests.kt @@ -29,6 +29,7 @@ import android.view.MotionEvent import androidx.test.platform.app.InstrumentationRegistry import com.android.cts.input.BatchedEventSplitter import com.android.cts.input.CaptureEventActivity +import com.android.cts.input.DebugInputRule import com.android.cts.input.InputJsonParser import com.android.cts.input.VirtualDisplayActivityScenario import com.android.cts.input.inputeventmatchers.isResampled @@ -97,6 +98,10 @@ class UinputRecordingIntegrationTests { private lateinit var instrumentation: Instrumentation private lateinit var parser: InputJsonParser + + @get:Rule + val debugInputRule = DebugInputRule() + @get:Rule val testName = TestName() @@ -109,6 +114,7 @@ class UinputRecordingIntegrationTests { parser = InputJsonParser(instrumentation.context) } + @DebugInputRule.DebugInput(bug = 389901828) @Test fun testEvemuRecording() { VirtualDisplayActivityScenario.AutoClose<CaptureEventActivity>( diff --git a/tests/Tracing/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java b/tests/Tracing/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java index 9e029a8d5e57..72b1780ceb06 100644 --- a/tests/Tracing/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java +++ b/tests/Tracing/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java @@ -33,6 +33,7 @@ import org.junit.runners.JUnit4; import perfetto.protos.ProtologCommon; import java.io.File; +import java.io.IOException; @Presubmit @RunWith(JUnit4.class) @@ -159,4 +160,14 @@ public class ProtoLogViewerConfigReaderTest { loadViewerConfig(); unloadViewerConfig(); } + + @Test + public void testMessageHashIsAvailableInFile() throws IOException { + Truth.assertThat(mConfig.messageHashIsAvailableInFile(1)).isTrue(); + Truth.assertThat(mConfig.messageHashIsAvailableInFile(2)).isTrue(); + Truth.assertThat(mConfig.messageHashIsAvailableInFile(3)).isTrue(); + Truth.assertThat(mConfig.messageHashIsAvailableInFile(4)).isTrue(); + Truth.assertThat(mConfig.messageHashIsAvailableInFile(5)).isTrue(); + Truth.assertThat(mConfig.messageHashIsAvailableInFile(6)).isFalse(); + } } diff --git a/tools/aapt/SdkConstants.h b/tools/aapt/SdkConstants.h index ebb82ce1e2b7..af6063c37820 100644 --- a/tools/aapt/SdkConstants.h +++ b/tools/aapt/SdkConstants.h @@ -51,6 +51,7 @@ enum { SDK_TIRAMISU = 33, SDK_UPSIDE_DOWN_CAKE = 34, SDK_VANILLA_ICE_CREAM = 35, + SDK_BAKLAVA = 36, SDK_CUR_DEVELOPMENT = 10000, }; diff --git a/tools/aapt2/SdkConstants.h b/tools/aapt2/SdkConstants.h index f131cc6d7553..2a93c177e2ce 100644 --- a/tools/aapt2/SdkConstants.h +++ b/tools/aapt2/SdkConstants.h @@ -61,6 +61,7 @@ enum : ApiVersion { SDK_TIRAMISU = 33, SDK_UPSIDE_DOWN_CAKE = 34, SDK_VANILLA_ICE_CREAM = 35, + SDK_BAKLAVA = 36, SDK_CUR_DEVELOPMENT = 10000, }; diff --git a/tools/aapt2/tools/finalize_res.py b/tools/aapt2/tools/finalize_res.py index 0e4d865bc890..059f3b2087cf 100755 --- a/tools/aapt2/tools/finalize_res.py +++ b/tools/aapt2/tools/finalize_res.py @@ -38,13 +38,22 @@ Usage: $ANDROID_BUILD_TOP/frameworks/base/tools/aapt2/tools/finalize_res.py \ import re import sys +import subprocess +from collections import defaultdict resTypes = ["attr", "id", "style", "string", "dimen", "color", "array", "drawable", "layout", "anim", "animator", "interpolator", "mipmap", "integer", "transition", "raw", "bool", "fraction"] +NO_FLAG_MAGIC_CONSTANT = "no_flag" + +_aconfig_map = {} +_not_finalized = defaultdict(list) _type_ids = {} _type = "" +_finalized_flags = defaultdict(list) +_non_finalized_flags = defaultdict(list) + _lowest_staging_first_id = 0x01FFFFFF @@ -53,13 +62,53 @@ _lowest_staging_first_id = 0x01FFFFFF prefixed with removed_. The IDs are assigned without holes starting from the last ID for that type currently finalized in public-final.xml. """ -def finalize_item(raw): - name = raw.group(1) - if re.match(r'_*removed.+', name): - return "" +def finalize_item(comment_and_item): + print("Processing:\n" + comment_and_item) + name = re.search('<public name="(.+?)"',comment_and_item, flags=re.DOTALL).group(1) + if re.match('removed_.+', name): + # Remove it from <staging-public-group> in public-staging.xml + # Include it as is in <staging-public-group-final> in public-final.xml + # Don't assign an id in public-final.xml + return ("", comment_and_item, "") + + comment = re.search(' *<!--.+?-->\n', comment_and_item, flags=re.DOTALL).group(0) + + match = re.search('<!-- @FlaggedApi\((.+?)\)', comment, flags=re.DOTALL) + if match: + flag = match.group(1) + else: + flag = NO_FLAG_MAGIC_CONSTANT + + if flag.startswith("\""): + # Flag is a string value, just remove " + flag = flag.replace("\"", "") + else: + # Flag is a java constant, convert to string value + flag = flag.replace(".Flags.FLAG_", ".").lower() + + if flag not in _aconfig_map: + raise Exception("Unknown flag: " + flag) + + # READ_ONLY-ENABLED is a magic string from printflags output below + if _aconfig_map[flag] != "READ_ONLY-ENABLED": + _non_finalized_flags[flag].append(name) + # Keep it as is in <staging-public-group> in public-staging.xml + # Include as magic constant "removed_" in <staging-public-group-final> in public-final.xml + # Don't assign an id in public-final.xml + return (comment_and_item, " <public name=\"removed_\" />\n", "") + + _finalized_flags[flag].append(name) + id = _type_ids[_type] _type_ids[_type] += 1 - return ' <public type="%s" name="%s" id="%s" />\n' % (_type, name, '0x{0:0{1}x}'.format(id, 8)) + + # Removes one indentation step to align the comment with the item outside the + comment = re.sub("^ ", "", comment, flags=re.MULTILINE) + + # Remove from <staging-public-group> in public-staging.xml + # Include as is in <staging-public-group-final> in public-final.xml + # Assign an id in public-final.xml + return ("", comment_and_item, comment + ' <public type="%s" name="%s" id="%s" />\n' % (_type, name, '0x{0:0{1}x}'.format(id, 8))) """ @@ -72,10 +121,26 @@ def finalize_group(raw): _type = raw.group(1) id = int(raw.group(2), 16) _type_ids[_type] = _type_ids.get(_type, id) - (res, count) = re.subn(' {0,4}<public name="(.+?)" */>\n', finalize_item, raw.group(3)) - if count > 0: - res = raw.group(0).replace("staging-public-group", - "staging-public-group-final") + '\n' + res + + + all = re.findall(' *<!--.*?<public name=".+?" */>\n', raw.group(3), flags=re.DOTALL) + res = "" + group_matches = "" + for match in all: + (staging_group, final_group, final_id_assignment) = finalize_item(match) + + if staging_group: + _not_finalized[_type].append(staging_group) + + if final_group: + group_matches += final_group + + if final_id_assignment: + res += final_id_assignment + + # Only add it to final.xml if new ids were actually assigned + if res: + res = '<staging-public-group-final type="%s" first-id="%s">\n%s </staging-public-group-final>\n\n%s' % (_type, raw.group(2), group_matches, res) _lowest_staging_first_id = min(id, _lowest_staging_first_id) return res @@ -88,6 +153,15 @@ def collect_ids(raw): id = int(m.group(2), 16) _type_ids[type] = max(id + 1, _type_ids.get(type, 0)) +# This is a hack and assumes this script is run from the top directory +output=subprocess.run("printflags --format='{fully_qualified_name} {permission}-{state}'", shell=True, capture_output=True, encoding="utf-8", check=True) +for line in output.stdout.splitlines(): + parts = line.split() + key = parts[0] + value = parts[1] + _aconfig_map[key]=value + +_aconfig_map[NO_FLAG_MAGIC_CONSTANT]="READ_ONLY-DISABLED" with open(sys.argv[1], "r+") as stagingFile: with open(sys.argv[2], "r+") as finalFile: @@ -132,10 +206,25 @@ with open(sys.argv[1], "r+") as stagingFile: nextId = _lowest_staging_first_id - 0x00010000 for resType in resTypes: stagingFile.write(' <staging-public-group type="%s" first-id="%s">\n' - ' </staging-public-group>\n\n' % - (resType, '0x{0:0{1}x}'.format(nextId, 8))) + % (resType, '0x{0:0{1}x}'.format(nextId, 8))) + for item in _not_finalized[resType]: + stagingFile.write(item) + stagingFile.write(' </staging-public-group>\n\n') nextId -= 0x00010000 # Close the resources tag and truncate, since the file will be shorter than the previous stagingFile.write("</resources>\n") stagingFile.truncate() + + +print("\nFlags that had resources that were NOT finalized:") +for flag in sorted(_non_finalized_flags.keys()): + print(f" {flag}") + for value in _non_finalized_flags[flag]: + print(f" {value}") + +print("\nFlags that had resources that were finalized:") +for flag in sorted(_finalized_flags.keys()): + print(f" {flag}") + for value in _finalized_flags[flag]: + print(f" {value}") diff --git a/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt index af753e5963a3..b62843ca3ff4 100644 --- a/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt +++ b/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt @@ -19,6 +19,7 @@ package com.google.android.lint import com.android.tools.lint.client.api.IssueRegistry import com.android.tools.lint.client.api.Vendor import com.android.tools.lint.detector.api.CURRENT_API +import com.google.android.lint.multiuser.PendingIntentGetActivityDetector import com.google.android.lint.parcel.SaferParcelChecker import com.google.auto.service.AutoService @@ -40,6 +41,7 @@ class AndroidFrameworkIssueRegistry : IssueRegistry() { PermissionMethodDetector.ISSUE_PERMISSION_METHOD_USAGE, PermissionMethodDetector.ISSUE_CAN_BE_PERMISSION_METHOD, FeatureAutomotiveDetector.ISSUE, + PendingIntentGetActivityDetector.ISSUE_PENDING_INTENT_GET_ACTIVITY, ) override val api: Int diff --git a/tools/lint/framework/checks/src/main/java/com/google/android/lint/multiuser/PendingIntentGetActivityDetector.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/multiuser/PendingIntentGetActivityDetector.kt new file mode 100644 index 000000000000..b9f22ebfa8ec --- /dev/null +++ b/tools/lint/framework/checks/src/main/java/com/google/android/lint/multiuser/PendingIntentGetActivityDetector.kt @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.lint.multiuser + +import com.android.tools.lint.detector.api.Category +import com.android.tools.lint.detector.api.Detector +import com.android.tools.lint.detector.api.Implementation +import com.android.tools.lint.detector.api.Issue +import com.android.tools.lint.detector.api.JavaContext +import com.android.tools.lint.detector.api.Scope +import com.android.tools.lint.detector.api.Severity +import com.android.tools.lint.detector.api.SourceCodeScanner +import com.intellij.psi.PsiMethod +import java.util.EnumSet +import org.jetbrains.uast.UCallExpression + +/** + * Detector for flagging potential multiuser issues in `PendingIntent.getActivity()` calls. + * + * This detector checks for calls to `PendingIntent#getActivity()` and + * reports a warning if such a call is found, suggesting that the + * default user 0 context might not be the right one. + */ +class PendingIntentGetActivityDetector : Detector(), SourceCodeScanner { + + companion object { + + val description = """Flags potential multiuser issue in PendingIntent.getActivity() calls.""" + + val EXPLANATION = + """ + **Problem:** + + Calling `PendingIntent.getActivity()` in the `system_server` often accidentally uses the user 0 context. Moreover, since there's no explicit user parameter in the `getActivity` method, it can be hard to tell which user the `PendingIntent` activity is associated with, making the code error-prone and less readable. + + **Solution:** + + Always use the user aware methods to refer the correct user context. You can achieve this by: + + * **Using `PendingIntent.getActivityAsUser(...)`:** This API allows you to explicitly specify the user for the activity. + + ```java + PendingIntent.getActivityAsUser( + mContext, /*requestCode=*/0, intent, + PendingIntent.FLAG_IMMUTABLE, /*options=*/null, + UserHandle.of(mUserId)); + ``` + + **When to Ignore this Warning:** + + You can safely ignore this warning if you are certain that: + + * You've confirmed that the `PendingIntent` activity you're targeting is the correct one and is **rightly** associated with the context parameter passed into the `PendingIntent.getActivity` method. + + **Note:** If you are unsure about the user context, it's best to err on the side of caution and explicitly specify the user using the method specified above. + + **For any further questions, please reach out to go/multiuser-help.** + """.trimIndent() + + val ISSUE_PENDING_INTENT_GET_ACTIVITY: Issue = + Issue.create( + id = "PendingIntent#getActivity", + briefDescription = description, + explanation = EXPLANATION, + category = Category.SECURITY, + priority = 8, + severity = Severity.WARNING, + implementation = + Implementation( + PendingIntentGetActivityDetector::class.java, + EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES), + ), + ) + } + + override fun getApplicableMethodNames() = listOf("getActivity") + + override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) { + // Check if the method call is PendingIntent.getActivity + if ( + context.evaluator.isMemberInClass(method, "android.app.PendingIntent") && + method.name == "getActivity" + ) { + context.report( + ISSUE_PENDING_INTENT_GET_ACTIVITY, + node, + context.getLocation(node), + "Using `PendingIntent.getActivity(...)` might not be multiuser-aware. " + + "Consider using the user aware method `PendingIntent.getActivityAsUser(...)`.", + ) + } + } +} diff --git a/tools/lint/framework/checks/src/test/java/com/google/android/lint/multiuser/PendingIntentGetActivityDetectorTest.kt b/tools/lint/framework/checks/src/test/java/com/google/android/lint/multiuser/PendingIntentGetActivityDetectorTest.kt new file mode 100644 index 000000000000..401055055232 --- /dev/null +++ b/tools/lint/framework/checks/src/test/java/com/google/android/lint/multiuser/PendingIntentGetActivityDetectorTest.kt @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.lint.multiuser + +import com.android.tools.lint.checks.infrastructure.LintDetectorTest +import com.android.tools.lint.checks.infrastructure.TestFile +import com.android.tools.lint.checks.infrastructure.TestLintTask +import com.android.tools.lint.detector.api.Detector +import com.android.tools.lint.detector.api.Issue + +@Suppress("UnstableApiUsage") +class PendingIntentGetActivityDetectorTest : LintDetectorTest() { + + override fun getDetector(): Detector = PendingIntentGetActivityDetector() + + override fun getIssues(): List<Issue> = + listOf(PendingIntentGetActivityDetector.ISSUE_PENDING_INTENT_GET_ACTIVITY) + + override fun lint(): TestLintTask = super.lint().allowMissingSdk(true) + + fun testPendingIntentGetActivity() { + lint() + .files( + java( + """ + package test.pkg; + + import android.app.PendingIntent; + import android.content.Context; + import android.content.Intent; + + public class TestClass { + private Context mContext; + + public void testMethod(Intent intent) { + PendingIntent.getActivity( + mContext, /*requestCode=*/0, intent, + PendingIntent.FLAG_IMMUTABLE, /*options=*/null + ); + } + } + """ + ) + .indented(), + *stubs, + ) + .issues(PendingIntentGetActivityDetector.ISSUE_PENDING_INTENT_GET_ACTIVITY) + .run() + .expect( + """ + src/test/pkg/TestClass.java:11: Warning: Using PendingIntent.getActivity(...) might not be multiuser-aware. Consider using the user aware method PendingIntent.getActivityAsUser(...). [PendingIntent#getActivity] + PendingIntent.getActivity( + ^ + 0 errors, 1 warnings + """ + ) + } + + fun testPendingIntentGetActivityAsUser() { + lint() + .files( + java( + """ + package test.pkg; + + import android.app.PendingIntent; + import android.content.Context; + import android.content.Intent; + import android.os.UserHandle; + + public class TestClass { + private Context mContext; + + public void testMethod(Intent intent) { + PendingIntent.getActivityAsUser( + mContext, /*requestCode=*/0, intent, + 0, /*options=*/null, + UserHandle.CURRENT + ); + } + } + """ + ) + .indented(), + *stubs, + ) + .issues(PendingIntentGetActivityDetector.ISSUE_PENDING_INTENT_GET_ACTIVITY) + .run() + .expectClean() + } + + private val pendingIntentStub: TestFile = + java( + """ + package android.app; + + import android.content.Context; + import android.content.Intent; + import android.os.UserHandle; + + public class PendingIntent { + public static boolean getActivity(Context context, int requestCode, Intent intent, int flags) { + return true; + } + + public static boolean getActivityAsUser( + Context context, + int requestCode, + Intent intent, + int flags, + UserHandle userHandle + ) { + return true; + } + } + """ + ) + + private val contxtStub: TestFile = + java( + """ + package android.content; + + import android.os.UserHandle; + + public class Context { + + public Context createContextAsUser(UserHandle userHandle, int flags) { + return this; + } + } + + """ + ) + + private val userHandleStub: TestFile = + java( + """ + package android.os; + + public class UserHandle { + + } + + """ + ) + + private val intentStub: TestFile = + java( + """ + package android.content; + + public class Intent { + + } + """ + ) + + private val stubs = arrayOf(pendingIntentStub, contxtStub, userHandleStub, intentStub) +} |