diff options
1112 files changed, 25104 insertions, 10389 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index 4e05cbc23dc3..2dd16de3e188 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -73,6 +73,7 @@ aconfig_declarations_group { "android.service.dreams.flags-aconfig-java", "android.service.notification.flags-aconfig-java", "android.service.quickaccesswallet.flags-aconfig-java", + "android.service.selinux.flags-aconfig-java", "android.service.voice.flags-aconfig-java", "android.speech.flags-aconfig-java", "android.systemserver.flags-aconfig-java", @@ -1943,3 +1944,19 @@ java_aconfig_library { aconfig_declarations: "android.service.quickaccesswallet.flags-aconfig", defaults: ["framework-minus-apex-aconfig-java-defaults"], } + +// SELinux log collector +aconfig_declarations { + name: "android.service.selinux.flags-aconfig", + package: "com.android.server.selinux.flags", + container: "system", + srcs: [ + "services/core/java/com/android/server/selinux/*.aconfig", + ], +} + +java_aconfig_library { + name: "android.service.selinux.flags-aconfig-java", + aconfig_declarations: "android.service.selinux.flags-aconfig", + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} diff --git a/Android.bp b/Android.bp index 444725eb2c79..127556f8e075 100644 --- a/Android.bp +++ b/Android.bp @@ -415,6 +415,7 @@ java_defaults { "mimemap", "av-types-aidl-java", "tv_tuner_resource_manager_aidl_interface-java", + "media_quality_aidl_interface-java", "soundtrigger_middleware-aidl-java", "modules-utils-binary-xml", "modules-utils-build", diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java index cc2d104813e4..d48af2cccd59 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java @@ -810,7 +810,7 @@ public class JobInfo implements Parcelable { /** * <p class="caution"><strong>Note:</strong> Beginning with - * {@link android.os.Build.VERSION_CODES#B}, this flag will be ignored and no longer + * {@link android.os.Build.VERSION_CODES#BAKLAVA}, this flag will be ignored and no longer * function effectively, regardless of the calling app's target SDK version. * Calling this method will always return {@code false}. * @@ -2137,9 +2137,9 @@ public class JobInfo implements Parcelable { * Jobs marked as important-while-foreground are given {@link #PRIORITY_HIGH} by default. * * <p class="caution"><strong>Note:</strong> Beginning with - * {@link android.os.Build.VERSION_CODES#B}, this flag will be ignored and no longer + * {@link android.os.Build.VERSION_CODES#BAKLAVA}, this flag will be ignored and no longer * function effectively, regardless of the calling app's target SDK version. - * {link #isImportantWhileForeground()} will always return {@code false}. + * {@link #isImportantWhileForeground()} will always return {@code false}. * Apps should use {link #setExpedited(boolean)} with {@code true} to indicate * that this job is important and needs to run as soon as possible. * diff --git a/boot/boot-image-profile-extra.txt b/boot/boot-image-profile-extra.txt index ce99bfed1ce3..cc02c8ae36e6 100644 --- a/boot/boot-image-profile-extra.txt +++ b/boot/boot-image-profile-extra.txt @@ -70,3 +70,10 @@ HSPLandroid/os/PerfettoTrackEventExtra$FieldString;->* HSPLandroid/os/PerfettoTrackEventExtra$FieldNested;->* HSPLandroid/os/PerfettoTrackEventExtra$Pool;->* HSPLandroid/os/PerfettoTrackEventExtra$RingBuffer;->* + +# While the SystemFeaturesMetadata static cache isn't heavyweight, ensure it's +# pre-initialized in the boot image to avoid redundant per-process overhead. +# TODO(b/326623529): Consider removing this after the feature has fully ramped +# and is captured with the boot image profiling pipeline. +HSPLcom/android/internal/pm/SystemFeaturesMetadata;->* +Lcom/android/internal/pm/SystemFeaturesMetadata; diff --git a/boot/preloaded-classes b/boot/preloaded-classes index f87828ec8d9a..7f4b3244c164 100644 --- a/boot/preloaded-classes +++ b/boot/preloaded-classes @@ -11885,6 +11885,7 @@ com.android.internal.os.ZygoteServer$UsapPoolRefillAction com.android.internal.os.ZygoteServer com.android.internal.os.logging.MetricsLoggerWrapper com.android.internal.pm.RoSystemFeatures +com.android.internal.pm.SystemFeaturesMetadata com.android.internal.pm.parsing.PackageParser2$Callback com.android.internal.pm.parsing.PackageParserException com.android.internal.pm.pkg.component.flags.FeatureFlags diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java index 696bc82a9ffc..ae150aece432 100644 --- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java +++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java @@ -1309,6 +1309,8 @@ public class Bmgr { return "AGENT_FAILURE_DURING_RESTORE"; case BackupManagerMonitor.LOG_EVENT_ID_FAILED_TO_READ_DATA_FROM_TRANSPORT: return "FAILED_TO_READ_DATA_FROM_TRANSPORT"; + case BackupManagerMonitor.LOG_EVENT_ID_FULL_BACKUP_AGENT_PIPE_BROKEN: + return "LOG_EVENT_ID_FULL_BACKUP_AGENT_PIPE_BROKEN"; default: return "UNKNOWN_ID"; } diff --git a/cmds/svc/src/com/android/commands/svc/UsbCommand.java b/cmds/svc/src/com/android/commands/svc/UsbCommand.java index 26e20f601c7a..6542d08d6384 100644 --- a/cmds/svc/src/com/android/commands/svc/UsbCommand.java +++ b/cmds/svc/src/com/android/commands/svc/UsbCommand.java @@ -89,6 +89,11 @@ public class UsbCommand extends Svc.Command { IUsbManager usbMgr = IUsbManager.Stub.asInterface(ServiceManager.getService( Context.USB_SERVICE)); + if (usbMgr == null) { + System.err.println("Could not obtain USB service. Try again later."); + return; + } + Executor executor = context.getMainExecutor(); Consumer<Integer> consumer = new Consumer<Integer>(){ public void accept(Integer status){ diff --git a/config/preloaded-classes b/config/preloaded-classes index 4147fd7c4ae6..707acb00b102 100644 --- a/config/preloaded-classes +++ b/config/preloaded-classes @@ -11921,6 +11921,7 @@ com.android.internal.os.ZygoteServer$UsapPoolRefillAction com.android.internal.os.ZygoteServer com.android.internal.os.logging.MetricsLoggerWrapper com.android.internal.pm.RoSystemFeatures +com.android.internal.pm.SystemFeaturesMetadata com.android.internal.pm.parsing.PackageParser2$Callback com.android.internal.pm.parsing.PackageParserException com.android.internal.pm.pkg.component.flags.FeatureFlags diff --git a/core/api/current.txt b/core/api/current.txt index 7bc0fb220e1a..1630d80346ce 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -58287,7 +58287,9 @@ package android.view.inspector { } public final class WindowInspector { + method @FlaggedApi("android.view.flags.root_view_changed_listener") public static void addGlobalWindowViewsListener(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.util.List<android.view.View>>); method @NonNull public static java.util.List<android.view.View> getGlobalWindowViews(); + method @FlaggedApi("android.view.flags.root_view_changed_listener") public static void removeGlobalWindowViewsListener(@NonNull java.util.function.Consumer<java.util.List<android.view.View>>); } } diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt index 526a213a6003..132c65cc26ee 100644 --- a/core/api/module-lib-current.txt +++ b/core/api/module-lib-current.txt @@ -410,6 +410,7 @@ package android.os { method public void invalidateCache(); method public static void invalidateCache(@NonNull String, @NonNull String); method @Nullable public Result query(@NonNull Query); + method @FlaggedApi("android.os.ipc_data_cache_test_apis") public static void setTestMode(boolean); field public static final String MODULE_BLUETOOTH = "bluetooth"; } diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 984bc680c685..32b170a6286b 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -11725,6 +11725,7 @@ package android.os { field public static final String USER_TYPE_FULL_GUEST = "android.os.usertype.full.GUEST"; field public static final String USER_TYPE_FULL_SECONDARY = "android.os.usertype.full.SECONDARY"; field public static final String USER_TYPE_FULL_SYSTEM = "android.os.usertype.full.SYSTEM"; + field @FlaggedApi("android.multiuser.allow_supervising_profile") public static final String USER_TYPE_PROFILE_SUPERVISING = "android.os.usertype.profile.SUPERVISING"; field public static final String USER_TYPE_SYSTEM_HEADLESS = "android.os.usertype.system.HEADLESS"; } diff --git a/core/api/test-current.txt b/core/api/test-current.txt index d651010b641a..bb023f25094e 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -1199,6 +1199,7 @@ package android.content.pm { method public boolean isProfile(); method public boolean isQuietModeEnabled(); method public boolean isRestricted(); + method @FlaggedApi("android.multiuser.allow_supervising_profile") public boolean isSupervisingProfile(); method public boolean supportsSwitchTo(); method @Deprecated public boolean supportsSwitchToByUser(); method public void writeToParcel(android.os.Parcel, int); @@ -2459,7 +2460,7 @@ package android.os { method public static void invalidateCache(@NonNull String, @NonNull String); method public final boolean isDisabled(); method @Nullable public Result query(@NonNull Query); - method public static void setTestMode(boolean); + method @FlaggedApi("android.os.ipc_data_cache_test_apis") public static void setTestMode(boolean); field public static final String MODULE_BLUETOOTH = "bluetooth"; field public static final String MODULE_SYSTEM = "system_server"; field public static final String MODULE_TEST = "test"; diff --git a/core/java/android/animation/AnimationHandler.java b/core/java/android/animation/AnimationHandler.java index d84a4c12a2cd..d5b2f980e1a6 100644 --- a/core/java/android/animation/AnimationHandler.java +++ b/core/java/android/animation/AnimationHandler.java @@ -384,6 +384,12 @@ public class AnimationHandler { }); } + void removePendingEndAnimationCallback(Runnable notifyEndAnimation) { + if (mPendingEndAnimationListeners != null) { + mPendingEndAnimationListeners.remove(notifyEndAnimation); + } + } + private void doAnimationFrame(long frameTime) { long currentTime = SystemClock.uptimeMillis(); final int size = mAnimationCallbacks.size(); diff --git a/core/java/android/animation/Animator.java b/core/java/android/animation/Animator.java index 4bf87f91cb2f..e62cd556af9a 100644 --- a/core/java/android/animation/Animator.java +++ b/core/java/android/animation/Animator.java @@ -82,6 +82,12 @@ public abstract class Animator implements Cloneable { static boolean sPostNotifyEndListenerEnabled; /** + * If {@link #sPostNotifyEndListenerEnabled} is enabled, it will be set when the end callback + * is scheduled. It is cleared when it runs or finishes immediately, e.g. cancel. + */ + private Runnable mPendingEndCallback; + + /** * A cache of the values in a list. Used so that when calling the list, we have a copy * of it in case the list is modified while iterating. The array can be reused to avoid * allocation on every notification. @@ -660,10 +666,33 @@ public abstract class Animator implements Cloneable { } } + /** + * This is called when the animator needs to finish immediately. This is usually no-op unless + * {@link #sPostNotifyEndListenerEnabled} is enabled and a finish request calls around the last + * animation frame. + * + * @param notifyListeners Whether to invoke {@link AnimatorListener#onAnimationEnd}. + * @return {@code true} if the pending listeners are removed. + */ + boolean consumePendingEndListeners(boolean notifyListeners) { + if (mPendingEndCallback == null) { + return false; + } + AnimationHandler.getInstance().removePendingEndAnimationCallback(mPendingEndCallback); + mPendingEndCallback = null; + if (notifyListeners) { + notifyEndListeners(false /* isReversing */); + } + return true; + } + void notifyEndListenersFromEndAnimation(boolean isReversing, boolean postNotifyEndListener) { if (postNotifyEndListener) { - AnimationHandler.getInstance().postEndAnimationCallback( - () -> completeEndAnimation(isReversing, "postNotifyAnimEnd")); + mPendingEndCallback = () -> { + completeEndAnimation(isReversing, "postNotifyAnimEnd"); + mPendingEndCallback = null; + }; + AnimationHandler.getInstance().postEndAnimationCallback(mPendingEndCallback); } else { completeEndAnimation(isReversing, "notifyAnimEnd"); } diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java index 78566d2fe98d..4a07de0410ae 100644 --- a/core/java/android/animation/AnimatorSet.java +++ b/core/java/android/animation/AnimatorSet.java @@ -423,6 +423,13 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim notifyListeners(AnimatorCaller.ON_CANCEL, false); callOnPlayingSet(Animator::cancel); mPlayingSet.clear(); + // If the end callback is pending, invoke the end callbacks of the animator nodes before + // ending this set. Pass notifyListeners=false because this endAnimation will do that. + if (consumePendingEndListeners(false /* notifyListeners */)) { + for (int i = mNodeMap.size() - 1; i >= 0; i--) { + mNodeMap.keyAt(i).consumePendingEndListeners(true /* notifyListeners */); + } + } endAnimation(); } } diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java index 492c2ffc561f..fbcc73ea59e7 100644 --- a/core/java/android/animation/ValueAnimator.java +++ b/core/java/android/animation/ValueAnimator.java @@ -1182,6 +1182,7 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio // If end has already been requested, through a previous end() or cancel() call, no-op // until animation starts again. if (mAnimationEndRequested) { + consumePendingEndListeners(true /* notifyListeners */); return; } diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index b38f5da6b638..54ab3b8f185b 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -55,9 +55,7 @@ import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Matrix; import android.graphics.Point; -import android.graphics.Rect; import android.graphics.drawable.Icon; -import android.hardware.HardwareBuffer; import android.os.BatteryStats; import android.os.Binder; import android.os.Build; @@ -86,7 +84,6 @@ import android.util.Log; import android.util.Singleton; import android.util.Size; import android.view.WindowInsetsController.Appearance; -import android.window.TaskSnapshot; import com.android.internal.annotations.GuardedBy; import com.android.internal.app.LocalePicker; @@ -5420,10 +5417,11 @@ public class ActivityManager { * * @hide */ + @Nullable @RequiresPermission(Manifest.permission.MANAGE_USERS) - public @Nullable String getSwitchingFromUserMessage() { + public String getSwitchingFromUserMessage(@UserIdInt int userId) { try { - return getService().getSwitchingFromUserMessage(); + return getService().getSwitchingFromUserMessage(userId); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } @@ -5434,10 +5432,11 @@ public class ActivityManager { * * @hide */ + @Nullable @RequiresPermission(Manifest.permission.MANAGE_USERS) - public @Nullable String getSwitchingToUserMessage() { + public String getSwitchingToUserMessage(@UserIdInt int userId) { try { - return getService().getSwitchingToUserMessage(); + return getService().getSwitchingToUserMessage(userId); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index a12c0674998e..e5f7889859c1 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -292,14 +292,14 @@ public abstract class ActivityManagerInternal { public abstract boolean canStartMoreUsers(); /** - * Sets the user switcher message for switching from {@link android.os.UserHandle#SYSTEM}. + * Sets the user switcher message for switching from a user. */ - public abstract void setSwitchingFromSystemUserMessage(String switchingFromSystemUserMessage); + public abstract void setSwitchingFromUserMessage(@UserIdInt int user, @Nullable String message); /** - * Sets the user switcher message for switching to {@link android.os.UserHandle#SYSTEM}. + * Sets the user switcher message for switching to a user. */ - public abstract void setSwitchingToSystemUserMessage(String switchingToSystemUserMessage); + public abstract void setSwitchingToUserMessage(@UserIdInt int user, @Nullable String message); /** * Returns maximum number of users that can run simultaneously. diff --git a/core/java/android/app/AppCompatTaskInfo.java b/core/java/android/app/AppCompatTaskInfo.java index 599f1a8be233..ea4646aa9eb9 100644 --- a/core/java/android/app/AppCompatTaskInfo.java +++ b/core/java/android/app/AppCompatTaskInfo.java @@ -102,6 +102,8 @@ public class AppCompatTaskInfo implements Parcelable { private static final int FLAG_FULLSCREEN_OVERRIDE_USER = FLAG_BASE << 8; /** Top activity flag for whether min aspect ratio of the activity has been overridden.*/ public static final int FLAG_HAS_MIN_ASPECT_RATIO_OVERRIDE = FLAG_BASE << 9; + /** Top activity flag for whether restart menu is shown due to display move. */ + private static final int FLAG_ENABLE_RESTART_MENU_FOR_DISPLAY_MOVE = FLAG_BASE << 10; @Retention(RetentionPolicy.SOURCE) @IntDef(flag = true, value = { @@ -115,7 +117,8 @@ public class AppCompatTaskInfo implements Parcelable { FLAG_ELIGIBLE_FOR_USER_ASPECT_RATIO_BUTTON, FLAG_FULLSCREEN_OVERRIDE_SYSTEM, FLAG_FULLSCREEN_OVERRIDE_USER, - FLAG_HAS_MIN_ASPECT_RATIO_OVERRIDE + FLAG_HAS_MIN_ASPECT_RATIO_OVERRIDE, + FLAG_ENABLE_RESTART_MENU_FOR_DISPLAY_MOVE }) public @interface TopActivityFlag {} @@ -133,7 +136,8 @@ public class AppCompatTaskInfo implements Parcelable { @TopActivityFlag private static final int FLAGS_COMPAT_UI_INTERESTED = FLAGS_ORGANIZER_INTERESTED - | FLAG_IN_SIZE_COMPAT | FLAG_ELIGIBLE_FOR_LETTERBOX_EDU | FLAG_LETTERBOX_EDU_ENABLED; + | FLAG_IN_SIZE_COMPAT | FLAG_ELIGIBLE_FOR_LETTERBOX_EDU | FLAG_LETTERBOX_EDU_ENABLED + | FLAG_ENABLE_RESTART_MENU_FOR_DISPLAY_MOVE; private AppCompatTaskInfo() { // Do nothing @@ -300,6 +304,21 @@ public class AppCompatTaskInfo implements Parcelable { } /** + * @return {@code true} if the restart menu is enabled for the top activity due to display move. + */ + public boolean isRestartMenuEnabledForDisplayMove() { + return isTopActivityFlagEnabled(FLAG_ENABLE_RESTART_MENU_FOR_DISPLAY_MOVE); + } + + /** + * Sets the top activity flag for whether the restart menu is enabled for the top activity due + * to display move. + */ + public void setRestartMenuEnabledForDisplayMove(boolean enable) { + setTopActivityFlag(FLAG_ENABLE_RESTART_MENU_FOR_DISPLAY_MOVE, enable); + } + + /** * @return {@code true} if the top activity bounds are letterboxed. */ public boolean isTopActivityLetterboxed() { diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 248f191cb8b8..1864d4a55f2e 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -18,7 +18,6 @@ package android.app; import static android.location.flags.Flags.FLAG_LOCATION_BYPASS; -import static android.media.audio.Flags.roForegroundAudioControl; import static android.permission.flags.Flags.FLAG_OP_ENABLE_MOBILE_DATA_BY_USER; import static android.service.notification.Flags.FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS; import static android.view.contentprotection.flags.Flags.FLAG_CREATE_ACCESSIBILITY_OVERLAY_APP_OP_ENABLED; @@ -3481,6 +3480,16 @@ public class AppOpsManager { } /** + * Whether an app op is backed by a runtime permission or not. + * @hide + */ + public static boolean opIsRuntimePermission(int op) { + if (op == OP_NONE) return false; + + return ArrayUtils.contains(RUNTIME_PERMISSION_OPS, op); + } + + /** * Retrieve the user restriction associated with an operation, or null if there is not one. * @hide */ diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index ad01ad57b2d8..6cdfb97520ae 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -403,8 +403,8 @@ interface IActivityManager { void setPackageScreenCompatMode(in String packageName, int mode); @UnsupportedAppUsage boolean switchUser(int userid); - String getSwitchingFromUserMessage(); - String getSwitchingToUserMessage(); + String getSwitchingFromUserMessage(int userId); + String getSwitchingToUserMessage(int userId); @UnsupportedAppUsage void setStopUserOnSwitch(int value); boolean removeTask(int taskId); diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 8d4925d8182d..127a08b04e87 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -6143,6 +6143,20 @@ public class Notification implements Parcelable result.mTitleMarginSet.applyToView(contentView, p.mTextViewId); contentView.setInt(p.mTextViewId, "setNumIndentLines", p.hasTitle() ? 0 : 1); } + // The expand button uses paddings rather than margins, so we'll adjust it + // separately. + adjustExpandButtonPadding(contentView, result.mRightIconVisible); + } + + private void adjustExpandButtonPadding(RemoteViews contentView, boolean rightIconVisible) { + if (notificationsRedesignTemplates()) { + final Resources res = mContext.getResources(); + int normalPadding = res.getDimensionPixelSize(R.dimen.notification_2025_margin); + int iconSpacing = res.getDimensionPixelSize( + R.dimen.notification_2025_expand_button_right_icon_spacing); + contentView.setInt(R.id.expand_button, "setStartPadding", + rightIconVisible ? iconSpacing : normalPadding); + } } // This code is executed on behalf of other apps' notifications, sometimes even by 3p apps, @@ -6154,12 +6168,21 @@ public class Notification implements Parcelable @NonNull TemplateBindResult result) { final Resources resources = mContext.getResources(); final float density = resources.getDisplayMetrics().density; - final float iconMarginDp = resources.getDimension( - R.dimen.notification_right_icon_content_margin) / density; + int iconMarginId = notificationsRedesignTemplates() + ? R.dimen.notification_2025_right_icon_content_margin + : R.dimen.notification_right_icon_content_margin; + final float iconMarginDp = resources.getDimension(iconMarginId) / density; final float contentMarginDp = resources.getDimension( R.dimen.notification_content_margin_end) / density; - final float expanderSizeDp = resources.getDimension( - R.dimen.notification_header_expand_icon_size) / density - contentMarginDp; + float spaceForExpanderDp; + if (notificationsRedesignTemplates()) { + spaceForExpanderDp = resources.getDimension( + R.dimen.notification_2025_right_icon_expanded_margin_end) / density + - contentMarginDp; + } else { + spaceForExpanderDp = resources.getDimension( + R.dimen.notification_header_expand_icon_size) / density - contentMarginDp; + } final float viewHeightDp = resources.getDimension( R.dimen.notification_right_icon_size) / density; float viewWidthDp = viewHeightDp; // icons are 1:1 by default @@ -6176,9 +6199,10 @@ public class Notification implements Parcelable } } } + // Margin needed for the header to accommodate the icon when shown final float extraMarginEndDpIfVisible = viewWidthDp + iconMarginDp; result.setRightIconState(rightIcon != null /* visible */, viewWidthDp, - viewHeightDp, extraMarginEndDpIfVisible, expanderSizeDp); + viewHeightDp, extraMarginEndDpIfVisible, spaceForExpanderDp); } /** @@ -14658,13 +14682,19 @@ public class Notification implements Parcelable public final MarginSet mTitleMarginSet = new MarginSet(); public void setRightIconState(boolean visible, float widthDp, float heightDp, - float marginEndDpIfVisible, float expanderSizeDp) { + float marginEndDpIfVisible, float spaceForExpanderDp) { mRightIconVisible = visible; mRightIconWidthDp = widthDp; mRightIconHeightDp = heightDp; - mHeadingExtraMarginSet.setValues(0, marginEndDpIfVisible); - mHeadingFullMarginSet.setValues(expanderSizeDp, marginEndDpIfVisible + expanderSizeDp); - mTitleMarginSet.setValues(0, marginEndDpIfVisible + expanderSizeDp); + mHeadingExtraMarginSet.setValues( + /* valueIfGone = */ 0, + /* valueIfVisible = */ marginEndDpIfVisible); + mHeadingFullMarginSet.setValues( + /* valueIfGone = */ spaceForExpanderDp, + /* valueIfVisible = */ marginEndDpIfVisible + spaceForExpanderDp); + mTitleMarginSet.setValues( + /* valueIfGone = */ 0, + /* valueIfVisible = */ marginEndDpIfVisible + spaceForExpanderDp); } /** diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java index 38141cf20ce3..6e495768bfd4 100644 --- a/core/java/android/app/PropertyInvalidatedCache.java +++ b/core/java/android/app/PropertyInvalidatedCache.java @@ -1417,7 +1417,36 @@ public class PropertyInvalidatedCache<Query, Result> { } /** - * Enable or disable testing. The protocol requires that the mode toggle: for instance, it is + * Throw if the current process is not allowed to use test APIs. + */ + @android.ravenwood.annotation.RavenwoodReplace + private static void throwIfNotTest() { + final ActivityThread activityThread = ActivityThread.currentActivityThread(); + if (activityThread == null) { + // Only tests can reach here. + return; + } + final Instrumentation instrumentation = activityThread.getInstrumentation(); + if (instrumentation == null) { + // Only tests can reach here. + return; + } + if (instrumentation.isInstrumenting()) { + return; + } + if (Flags.enforcePicTestmodeProtocol()) { + throw new IllegalStateException("Test-only API called not from a test."); + } + } + + /** + * Do not throw if running under ravenwood. + */ + private static void throwIfNotTest$ravenwood() { + } + + /** + * Enable or disable test mode. The protocol requires that the mode toggle: for instance, it is * illegal to clear the test mode if the test mode is already off. Enabling test mode puts * all caches in the process into test mode; all nonces are initialized to UNSET and * subsequent reads and writes are to process memory. This has the effect of disabling all @@ -1425,10 +1454,12 @@ public class PropertyInvalidatedCache<Query, Result> { * operation. * @param mode The desired test mode. * @throws IllegalStateException if the supplied mode is already set. + * @throws IllegalStateException if the process is not running an instrumentation test. * @hide */ @VisibleForTesting public static void setTestMode(boolean mode) { + throwIfNotTest(); synchronized (sGlobalLock) { if (sTestMode == mode) { final String msg = "cannot set test mode redundantly: mode=" + mode; @@ -1464,9 +1495,11 @@ public class PropertyInvalidatedCache<Query, Result> { * for which it would not otherwise have permission. Caches in test mode do NOT write their * values to the system properties. The effect is local to the current process. Test mode * must be true when this method is called. + * @throws IllegalStateException if the process is not running an instrumentation test. * @hide */ public void testPropertyName() { + throwIfNotTest(); synchronized (sGlobalLock) { if (sTestMode == false) { throw new IllegalStateException("cannot test property name with test mode off"); @@ -1777,10 +1810,12 @@ public class PropertyInvalidatedCache<Query, Result> { * When multiple caches share a single property value, using an instance method on one of * the cache objects to invalidate all of the cache objects becomes confusing and you should * just use the static version of this function. + * @throws IllegalStateException if the process is not running an instrumentation test. * @hide */ @VisibleForTesting public void disableSystemWide() { + throwIfNotTest(); disableSystemWide(mPropertyName); } diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java index 6936ddc5eeac..59247934ba46 100644 --- a/core/java/android/app/TaskInfo.java +++ b/core/java/android/app/TaskInfo.java @@ -655,8 +655,10 @@ public class TaskInfo { + " effectiveUid=" + effectiveUid + " displayId=" + displayId + " isRunning=" + isRunning - + " baseIntent=" + baseIntent + " baseActivity=" + baseActivity - + " topActivity=" + topActivity + " origActivity=" + origActivity + + " baseIntent=" + baseIntent + + " baseActivity=" + baseActivity + + " topActivity=" + topActivity + + " origActivity=" + origActivity + " realActivity=" + realActivity + " numActivities=" + numActivities + " lastActiveTime=" + lastActiveTime diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 5359ba44a3d2..73de1b67dc66 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -8269,6 +8269,7 @@ public class DevicePolicyManager { * * @throws SecurityException if the caller is not a device owner, a profile owner or * delegated certificate chooser. + * @throws IllegalArgumentException if {@code alias} does not correspond to an existing key * @see #grantKeyPairToWifiAuth */ public boolean isKeyPairGrantedToWifiAuth(@NonNull String alias) { diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig index 0ecd2754b1f0..572bffe6c6a4 100644 --- a/core/java/android/app/admin/flags/flags.aconfig +++ b/core/java/android/app/admin/flags/flags.aconfig @@ -402,3 +402,13 @@ flag { description: "Add new API for secondary lockscreen" bug: "336297680" } + +flag { + name: "remove_managed_esim_on_work_profile_deletion" + namespace: "enterprise" + description: "Remove managed eSIM when work profile is deleted" + bug: "347925470" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/android/app/backup/BackupManagerMonitor.java b/core/java/android/app/backup/BackupManagerMonitor.java index e741bc2bf608..19c24cd8a14a 100644 --- a/core/java/android/app/backup/BackupManagerMonitor.java +++ b/core/java/android/app/backup/BackupManagerMonitor.java @@ -297,6 +297,9 @@ public class BackupManagerMonitor { @hide */ public static final int LOG_EVENT_ID_FAILED_TO_READ_DATA_FROM_TRANSPORT = 81; + /** The pipe between the BackupAgent and the framework was broken during full backup. @hide */ + public static final int LOG_EVENT_ID_FULL_BACKUP_AGENT_PIPE_BROKEN = 82; + /** * This method will be called each time something important happens on BackupManager. * diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig index 5c267c9f6475..4afe75f7814c 100644 --- a/core/java/android/app/notification.aconfig +++ b/core/java/android/app/notification.aconfig @@ -265,6 +265,16 @@ flag { } flag { + name: "expanding_public_view" + namespace: "systemui" + description: "enables user expanding the public view of a notification" + bug: "398853084" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "api_rich_ongoing" is_exported: true namespace: "systemui" diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 55d78f9b8c48..cc288b1f5601 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -744,15 +744,22 @@ public abstract class Context { */ public static final long BIND_MATCH_QUARANTINED_COMPONENTS = 0x2_0000_0000L; + /** + * Flag for {@link #bindService} that allows the bound app to be frozen if it is eligible. + * + * @hide + */ + public static final long BIND_ALLOW_FREEZE = 0x4_0000_0000L; /** * These bind flags reduce the strength of the binding such that we shouldn't * consider it as pulling the process up to the level of the one that is bound to it. * @hide */ - public static final int BIND_REDUCTION_FLAGS = + public static final long BIND_REDUCTION_FLAGS = Context.BIND_ALLOW_OOM_MANAGEMENT | Context.BIND_WAIVE_PRIORITY - | Context.BIND_NOT_PERCEPTIBLE | Context.BIND_NOT_VISIBLE; + | Context.BIND_NOT_PERCEPTIBLE | Context.BIND_NOT_VISIBLE + | Context.BIND_ALLOW_FREEZE; /** @hide */ @IntDef(flag = true, prefix = { "RECEIVER_VISIBLE" }, value = { diff --git a/core/java/android/content/pm/RegisteredServicesCache.java b/core/java/android/content/pm/RegisteredServicesCache.java index 74da62c85ed2..10d3051cff6f 100644 --- a/core/java/android/content/pm/RegisteredServicesCache.java +++ b/core/java/android/content/pm/RegisteredServicesCache.java @@ -166,7 +166,15 @@ public abstract class RegisteredServicesCache<V> { @UnsupportedAppUsage public RegisteredServicesCache(Context context, String interfaceName, String metaDataName, String attributeName, XmlSerializerAndParser<V> serializerAndParser) { - mContext = context; + this(new Injector<V>(context), interfaceName, metaDataName, attributeName, + serializerAndParser); + } + + /** Provides the basic functionality for unit tests. */ + @VisibleForTesting + public RegisteredServicesCache(Injector<V> injector, String interfaceName, String metaDataName, + String attributeName, XmlSerializerAndParser<V> serializerAndParser) { + mContext = injector.getContext(); mInterfaceName = interfaceName; mMetaDataName = metaDataName; mAttributesName = attributeName; @@ -184,7 +192,7 @@ public abstract class RegisteredServicesCache<V> { if (isCore) { intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); } - mBackgroundHandler = BackgroundThread.getHandler(); + mBackgroundHandler = injector.getBackgroundHandler(); mContext.registerReceiverAsUser( mPackageReceiver, UserHandle.ALL, intentFilter, null, mBackgroundHandler); @@ -918,4 +926,25 @@ public abstract class RegisteredServicesCache<V> { return null; } } + + /** + * Point of injection for test dependencies. + * @param <V> The type of the value. + */ + @VisibleForTesting + public static class Injector<V> { + private final Context mContext; + + public Injector(Context context) { + mContext = context; + } + + public Context getContext() { + return mContext; + } + + public Handler getBackgroundHandler() { + return BackgroundThread.getHandler(); + } + } } diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java index 582a1a9442ce..53203eba4020 100644 --- a/core/java/android/content/pm/UserInfo.java +++ b/core/java/android/content/pm/UserInfo.java @@ -410,6 +410,11 @@ public class UserInfo implements Parcelable { return UserManager.isUserTypePrivateProfile(userType); } + @FlaggedApi(android.multiuser.Flags.FLAG_ALLOW_SUPERVISING_PROFILE) + public boolean isSupervisingProfile() { + return UserManager.isUserTypeSupervisingProfile(userType); + } + /** See {@link #FLAG_DISABLED}*/ @UnsupportedAppUsage public boolean isEnabled() { diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig index 5c904c15e706..7f57f5dbf0ab 100644 --- a/core/java/android/content/pm/multiuser.aconfig +++ b/core/java/android/content/pm/multiuser.aconfig @@ -617,8 +617,8 @@ flag { } flag { - namespace: "multi_user" name: "logout_user_api" + namespace: "multiuser" description: "Add API to logout user" bug: "350045389" } @@ -629,3 +629,10 @@ flag { description: "Enable moving content into the Private Space" bug: "360066001" } + +flag { + name: "allow_supervising_profile" + namespace: "supervision" + description: "Enables support for new supervising user type" + bug: "389712089" +} diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java index e43a5fce6cb7..040dcfdeb998 100644 --- a/core/java/android/database/sqlite/SQLiteConnection.java +++ b/core/java/android/database/sqlite/SQLiteConnection.java @@ -183,11 +183,6 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen private static native long nativeChanges(long connectionPtr); private static native long nativeTotalChanges(long connectionPtr); - // This method is deprecated and should be removed when it is no longer needed by the - // robolectric tests. It should not be called from any frameworks java code. - @Deprecated - private static native void nativeClose(long connectionPtr); - private SQLiteConnection(SQLiteConnectionPool pool, SQLiteDatabaseConfiguration configuration, int connectionId, boolean primaryConnection) { diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java index bcb7ebfb286f..6e9dcf5a83a1 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -1714,7 +1714,7 @@ public final class CameraManager { final TaskInfo taskInfo = appTask.getTaskInfo(); final int freeformCameraCompatMode = taskInfo.appCompatTaskInfo .cameraCompatTaskInfo.freeformCameraCompatMode; - if (freeformCameraCompatMode != 0 + if (isInCameraCompatMode(freeformCameraCompatMode) && taskInfo.topActivity != null && taskInfo.topActivity.getPackageName().equals(packageName)) { // WindowManager has requested rotation override. @@ -1741,6 +1741,12 @@ public final class CameraManager { : ICameraService.ROTATION_OVERRIDE_NONE; } + private static boolean isInCameraCompatMode(@CameraCompatTaskInfo.FreeformCameraCompatMode int + freeformCameraCompatMode) { + return (freeformCameraCompatMode != CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_UNSPECIFIED) + && (freeformCameraCompatMode != CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE); + } + private static int getRotationOverrideForCompatFreeform( @CameraCompatTaskInfo.FreeformCameraCompatMode int freeformCameraCompatMode) { // Only rotate-and-crop if the app and device orientations do not match. diff --git a/core/java/android/hardware/input/IKeyGestureHandler.aidl b/core/java/android/hardware/input/IKeyGestureHandler.aidl index 509b9482154e..4da991ee85b1 100644 --- a/core/java/android/hardware/input/IKeyGestureHandler.aidl +++ b/core/java/android/hardware/input/IKeyGestureHandler.aidl @@ -28,15 +28,4 @@ interface IKeyGestureHandler { * to that gesture. */ boolean handleKeyGesture(in AidlKeyGestureEvent event, in IBinder focusedToken); - - /** - * Called to know if a particular gesture type is supported by the handler. - * - * TODO(b/358569822): Remove this call to reduce the binder calls to single call for - * handleKeyGesture. For this we need to remove dependency of multi-key gestures to identify if - * a key gesture is supported on first relevant key down. - * Also, for now we prioritize handlers in the system server process above external handlers to - * reduce IPC binder calls. - */ - boolean isKeyGestureSupported(int gestureType); } diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java index 49db54d81e65..d6419afb2a5a 100644 --- a/core/java/android/hardware/input/InputManager.java +++ b/core/java/android/hardware/input/InputManager.java @@ -1758,13 +1758,6 @@ public final class InputManager { */ boolean handleKeyGestureEvent(@NonNull KeyGestureEvent event, @Nullable IBinder focusedToken); - - /** - * Called to identify if a particular gesture is of interest to a handler. - * - * NOTE: If no active handler supports certain gestures, the gestures will not be captured. - */ - boolean isKeyGestureSupported(@KeyGestureEvent.KeyGestureType int gestureType); } /** @hide */ diff --git a/core/java/android/hardware/input/InputManagerGlobal.java b/core/java/android/hardware/input/InputManagerGlobal.java index a9a45ae45ec3..c4b4831ba76e 100644 --- a/core/java/android/hardware/input/InputManagerGlobal.java +++ b/core/java/android/hardware/input/InputManagerGlobal.java @@ -1193,23 +1193,6 @@ public final class InputManagerGlobal { } return false; } - - @Override - public boolean isKeyGestureSupported(@KeyGestureEvent.KeyGestureType int gestureType) { - synchronized (mKeyGestureEventHandlerLock) { - if (mKeyGestureEventHandlers == null) { - return false; - } - final int numHandlers = mKeyGestureEventHandlers.size(); - for (int i = 0; i < numHandlers; i++) { - KeyGestureEventHandler handler = mKeyGestureEventHandlers.get(i); - if (handler.isKeyGestureSupported(gestureType)) { - return true; - } - } - } - return false; - } } /** diff --git a/core/java/android/hardware/input/KeyGestureEvent.java b/core/java/android/hardware/input/KeyGestureEvent.java index 1a712d2b3f31..9dd1fed4a85a 100644 --- a/core/java/android/hardware/input/KeyGestureEvent.java +++ b/core/java/android/hardware/input/KeyGestureEvent.java @@ -108,7 +108,8 @@ public final class KeyGestureEvent { public static final int KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD = 55; public static final int KEY_GESTURE_TYPE_RINGER_TOGGLE_CHORD = 56; public static final int KEY_GESTURE_TYPE_GLOBAL_ACTIONS = 57; - public static final int KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD = 58; + @Deprecated + public static final int DEPRECATED_KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD = 58; public static final int KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT = 59; public static final int KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT = 60; public static final int KEY_GESTURE_TYPE_CLOSE_ALL_DIALOGS = 61; @@ -200,7 +201,6 @@ public final class KeyGestureEvent { KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD, KEY_GESTURE_TYPE_RINGER_TOGGLE_CHORD, KEY_GESTURE_TYPE_GLOBAL_ACTIONS, - KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD, KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT, KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT, KEY_GESTURE_TYPE_CLOSE_ALL_DIALOGS, @@ -777,8 +777,6 @@ public final class KeyGestureEvent { return "KEY_GESTURE_TYPE_RINGER_TOGGLE_CHORD"; case KEY_GESTURE_TYPE_GLOBAL_ACTIONS: return "KEY_GESTURE_TYPE_GLOBAL_ACTIONS"; - case KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD: - return "KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD"; case KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT: return "KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT"; case KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT: diff --git a/core/java/android/os/CombinedMessageQueue/MessageQueue.java b/core/java/android/os/CombinedMessageQueue/MessageQueue.java index 0964cde5a1f4..c3ec96d17437 100644 --- a/core/java/android/os/CombinedMessageQueue/MessageQueue.java +++ b/core/java/android/os/CombinedMessageQueue/MessageQueue.java @@ -145,8 +145,17 @@ public final class MessageQueue { } if (Flags.forceConcurrentMessageQueue()) { - sIsProcessAllowedToUseConcurrent = true; - return; + // b/379472827: Robolectric tests use reflection to access MessageQueue.mMessages. + // This is a hack to allow Robolectric tests to use the legacy implementation. + try { + Class.forName("org.robolectric.Robolectric"); + } catch (ClassNotFoundException e) { + // This is not a Robolectric test. + sIsProcessAllowedToUseConcurrent = true; + return; + } + // This is a Robolectric test. + // Continue to the following checks. } final String processName = Process.myProcessName(); diff --git a/core/java/android/os/IpcDataCache.java b/core/java/android/os/IpcDataCache.java index 2e7c3be53d90..e888f520b842 100644 --- a/core/java/android/os/IpcDataCache.java +++ b/core/java/android/os/IpcDataCache.java @@ -718,7 +718,7 @@ public class IpcDataCache<Query, Result> extends PropertyInvalidatedCache<Query, } /** - * Enable or disable testing. The protocol requires that the mode toggle: for instance, it is + * Enable or disable test mode. The protocol requires that the mode toggle: for instance, it is * illegal to clear the test mode if the test mode is already off. Enabling test mode puts * all caches in the process into test mode; all nonces are initialized to UNSET and * subsequent reads and writes are to process memory. This has the effect of disabling all @@ -726,8 +726,11 @@ public class IpcDataCache<Query, Result> extends PropertyInvalidatedCache<Query, * operation. * @param mode The desired test mode. * @throws IllegalStateException if the supplied mode is already set. + * @throws IllegalStateException if the process is not running an instrumentation test. * @hide */ + @FlaggedApi(android.os.Flags.FLAG_IPC_DATA_CACHE_TEST_APIS) + @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES) @TestApi public static void setTestMode(boolean mode) { PropertyInvalidatedCache.setTestMode(mode); diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 767019d97758..c01c3cdc7ace 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -209,6 +209,23 @@ public class UserManager { public static final String USER_TYPE_PROFILE_COMMUNAL = "android.os.usertype.profile.COMMUNAL"; /** + * User type representing a user who manages supervision on the device. + * When any full user on the device is supervised, the credentials for this profile will be + * required in order to perform certain actions for that user (i.e. those controlled by + * {@link android.app.supervision.SupervisionManager} or the + * {@link android.app.role.RoleManager#ROLE_SYSTEM_SUPERVISION supervision role holder}). + * There can only be one supervising profile per device, and the credentials set for that + * profile will be used to authorize actions for any supervised user on the device. This is + * distinct from a managed profile in that it functions only to authorize certain supervised + * actions; it does not represent the user to which restriction or management is applied. + * @hide + */ + @FlaggedApi(android.multiuser.Flags.FLAG_ALLOW_SUPERVISING_PROFILE) + @SystemApi + public static final String USER_TYPE_PROFILE_SUPERVISING = + "android.os.usertype.profile.SUPERVISING"; + + /** * User type representing a {@link UserHandle#USER_SYSTEM system} user that is <b>not</b> a * human user. * This type of user cannot be created; it can only pre-exist on first boot. @@ -3226,6 +3243,18 @@ public class UserManager { } /** + * Returns whether the user type is a + * {@link UserManager#USER_TYPE_PROFILE_SUPERVISING supervising profile}. + * + * @hide + */ + @FlaggedApi(android.multiuser.Flags.FLAG_ALLOW_SUPERVISING_PROFILE) + @android.ravenwood.annotation.RavenwoodKeep + public static boolean isUserTypeSupervisingProfile(@Nullable String userType) { + return USER_TYPE_PROFILE_SUPERVISING.equals(userType); + } + + /** * @hide * @deprecated Use {@link #isRestrictedProfile()} */ diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig index 86acb2b21cfa..5d80119410e1 100644 --- a/core/java/android/os/flags.aconfig +++ b/core/java/android/os/flags.aconfig @@ -227,6 +227,14 @@ flag { } flag { + name: "ipc_data_cache_test_apis" + namespace: "system_performance" + description: "Expose IpcDataCache test apis to mainline modules." + bug: "396173886" + is_exported: true +} + +flag { name: "mainline_vcn_platform_api" namespace: "vcn" description: "Expose platform APIs to mainline VCN" diff --git a/core/java/android/os/health/SystemHealthManager.java b/core/java/android/os/health/SystemHealthManager.java index a8a22f675e08..b82f278ef7d5 100644 --- a/core/java/android/os/health/SystemHealthManager.java +++ b/core/java/android/os/health/SystemHealthManager.java @@ -216,7 +216,7 @@ public class SystemHealthManager { /** * Gets the maximum number of TIDs this device supports for getting CPU headroom. * <p> - * See {@link CpuHeadroomParams#setTids(int...)}. + * See {@link CpuHeadroomParams.Builder#setTids(int...)}. * * @return the maximum size of TIDs supported * @throws UnsupportedOperationException if the CPU headroom API is unsupported. @@ -288,9 +288,7 @@ public class SystemHealthManager { /** * Gets the range of the calculation window size for CPU headroom. * <p> - * In API version 36, the range will be a superset of [50, 10000]. - * <p> - * See {@link CpuHeadroomParams#setCalculationWindowMillis(int)}. + * See {@link CpuHeadroomParams.Builder#setCalculationWindowMillis(int)}. * * @return the range of the calculation window size supported in milliseconds. * @throws UnsupportedOperationException if the CPU headroom API is unsupported. @@ -310,9 +308,7 @@ public class SystemHealthManager { /** * Gets the range of the calculation window size for GPU headroom. * <p> - * In API version 36, the range will be a superset of [50, 10000]. - * <p> - * See {@link GpuHeadroomParams#setCalculationWindowMillis(int)}. + * See {@link GpuHeadroomParams.Builder#setCalculationWindowMillis(int)}. * * @return the range of the calculation window size supported in milliseconds. * @throws UnsupportedOperationException if the GPU headroom API is unsupported. diff --git a/core/java/android/os/vibrator/flags.aconfig b/core/java/android/os/vibrator/flags.aconfig index 414f27498414..176c0c8ab966 100644 --- a/core/java/android/os/vibrator/flags.aconfig +++ b/core/java/android/os/vibrator/flags.aconfig @@ -165,3 +165,14 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + namespace: "haptics" + name: "remove_hidl_support" + description: "Remove framework code to support HIDL vibrator HALs." + bug: "308452413" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_FEATURE + } +} diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig index 0476f62ec263..34272b17cf54 100644 --- a/core/java/android/permission/flags.aconfig +++ b/core/java/android/permission/flags.aconfig @@ -71,6 +71,19 @@ flag { } flag { + name: "unknown_call_setting_blocked_logging_enabled" + is_exported: true + is_fixed_read_only: true + namespace: "permissions" + description: "enable the metrics when blocking certain app installs during an unknown call" + bug: "364535720" + + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "op_enable_mobile_data_by_user" is_exported: true namespace: "permissions" @@ -372,6 +385,15 @@ flag { } flag { + name: "record_all_runtime_appops_sqlite" + is_fixed_read_only: true + is_exported: true + namespace: "permissions" + description: "Enables recording of all runtime app ops into SQlite" + bug: "377584611" +} + +flag { name: "ranging_permission_enabled" is_fixed_read_only: true is_exported: true diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index b97c9b5e83f2..da4709b4b8b1 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -8651,6 +8651,34 @@ public final class Settings { public static final String DOCKED_CLOCK_FACE = "docked_clock_face"; /** + * Setting to indicate that content filters should be enabled on web browsers. + * + * <ul> + * <li>0 = Allow all sites + * <li>1 = Try to block explicit sites + * </ul> + * + * @hide + */ + @Readable + public static final String BROWSER_CONTENT_FILTERS_ENABLED = + "browser_content_filters_enabled"; + + /** + * Setting to indicate that content filters should be enabled in web search engines. + * + * <ul> + * <li>0 = Off + * <li>1 = Filter + * </ul> + * + * @hide + */ + @Readable + public static final String SEARCH_CONTENT_FILTERS_ENABLED = + "search_content_filters_enabled"; + + /** * Set by the system to track if the user needs to see the call to action for * the lockscreen notification policy. * @hide @@ -10589,6 +10617,57 @@ public final class Settings { public static final String GLANCEABLE_HUB_ENABLED = "glanceable_hub_enabled"; /** + * Indicates that glanceable hub should never be started automatically. + * + * @hide + */ + public static final int GLANCEABLE_HUB_START_NEVER = 0; + + /** + * Indicates that glanceable hub should be started when charging. + * + * @hide + */ + public static final int GLANCEABLE_HUB_START_CHARGING = 1; + + /** + * Indicates that glanceable hub should be started when charging and upright. + * + * @hide + */ + public static final int GLANCEABLE_HUB_START_CHARGING_UPRIGHT = 2; + + /** + * Indicates that glanceable hub should be started when docked. + * + * @hide + */ + public static final int GLANCEABLE_HUB_START_DOCKED = 3; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + GLANCEABLE_HUB_START_NEVER, + GLANCEABLE_HUB_START_CHARGING, + GLANCEABLE_HUB_START_CHARGING_UPRIGHT, + GLANCEABLE_HUB_START_DOCKED, + }) + public @interface WhenToStartGlanceableHub { + } + + /** + * Indicates when to start glanceable hub. Possible values are: + * 0: Never + * 1: While charging always + * 2: While upright and charging + * 3: While docked + * + * @hide + */ + public static final String WHEN_TO_START_GLANCEABLE_HUB = + "when_to_start_glanceable_hub"; + + /** * Whether home controls are enabled to be shown over the screensaver by the user. * * @hide @@ -11132,6 +11211,12 @@ public final class Settings { public static final String DOUBLE_TAP_TO_WAKE = "double_tap_to_wake"; /** + * Controls whether double tap to sleep is enabled. + * @hide + */ + public static final String DOUBLE_TAP_TO_SLEEP = "double_tap_to_sleep"; + + /** * The current assistant component. It could be a voice interaction service, * or an activity that handles ACTION_ASSIST, or empty which means using the default * handling. @@ -12499,6 +12584,48 @@ public final class Settings { "accessibility_magnification_always_on_enabled"; /** + * Controls how the magnification follows the cursor. + * + * @hide + */ + public static final String ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE = + "accessibility_magnification_cursor_following_mode"; + + /** + * Magnification cursor following mode value for the continuous mode. + * + * @hide + */ + public static final int ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_CONTINUOUS = 0; + + /** + * Magnification cursor following mode value for the center mode. + * + * @hide + */ + public static final int ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_CENTER = 1; + + /** + * Magnification cursor following mode value for the edge mode. + * + * @hide + */ + public static final int ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_EDGE = 2; + + /** + * Different cursor following settings that can be used as values with + * {@link #ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE}. + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_" }, + value = { + ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_CONTINUOUS, + ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_CENTER, + ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_EDGE}) + public @interface AccessibilityMagnificationCursorFollowingMode {} + + /** * Whether the following typing focus feature for magnification is enabled. * @hide */ diff --git a/core/java/android/security/advancedprotection/AdvancedProtectionManager.java b/core/java/android/security/advancedprotection/AdvancedProtectionManager.java index 770e234381c4..0b2239aa42b2 100644 --- a/core/java/android/security/advancedprotection/AdvancedProtectionManager.java +++ b/core/java/android/security/advancedprotection/AdvancedProtectionManager.java @@ -57,6 +57,7 @@ import java.util.concurrent.Executor; @SystemService(Context.ADVANCED_PROTECTION_SERVICE) public final class AdvancedProtectionManager { private static final String TAG = "AdvancedProtectionMgr"; + private static final String PKG_SETTINGS = "com.android.settings"; //TODO(b/378931989): Switch to android.app.admin.DevicePolicyIdentifiers.MEMORY_TAGGING_POLICY //when the appropriate flag is launched. @@ -343,6 +344,7 @@ public final class AdvancedProtectionManager { } Intent intent = new Intent(ACTION_SHOW_ADVANCED_PROTECTION_SUPPORT_DIALOG); + intent.setPackage(PKG_SETTINGS); intent.setFlags(FLAG_ACTIVITY_NEW_TASK); intent.putExtra(EXTRA_SUPPORT_DIALOG_FEATURE, featureId); intent.putExtra(EXTRA_SUPPORT_DIALOG_TYPE, type); diff --git a/core/java/android/service/chooser/flags.aconfig b/core/java/android/service/chooser/flags.aconfig index ae0b56e6f009..45a21beabd89 100644 --- a/core/java/android/service/chooser/flags.aconfig +++ b/core/java/android/service/chooser/flags.aconfig @@ -44,6 +44,16 @@ flag { } flag { + name: "notify_single_item_change_on_icon_load" + namespace: "intentresolver" + description: "ChooserGridAdapter to notify specific items change when the target icon is loaded (instead of all-item change)." + bug: "298193161" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "fix_resolver_memory_leak" is_exported: true namespace: "intentresolver" diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java index e1dc6f6d642b..e9ddfc3559bf 100644 --- a/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java +++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java @@ -96,6 +96,10 @@ class QuickAccessWalletServiceInfo { defaultAppPackageName = defaultPaymentApp.getPackageName(); } + if (defaultAppPackageName == null || defaultAppUser < 0) { + return null; + } + ServiceInfo serviceInfo = getWalletServiceInfo(context, defaultAppPackageName, defaultAppUser); if (serviceInfo == null) { diff --git a/core/java/android/service/voice/HotwordDetectionService.java b/core/java/android/service/voice/HotwordDetectionService.java index 937aecc8d718..0ace80875325 100644 --- a/core/java/android/service/voice/HotwordDetectionService.java +++ b/core/java/android/service/voice/HotwordDetectionService.java @@ -230,8 +230,8 @@ public abstract class HotwordDetectionService extends Service } @Override - public void ping(IRemoteCallback callback) throws RemoteException { - callback.sendResult(null); + public void ping(IPingMe callback) throws RemoteException { + callback.onPing(); } @Override diff --git a/core/java/android/service/voice/ISandboxedDetectionService.aidl b/core/java/android/service/voice/ISandboxedDetectionService.aidl index c76ac28eb36c..5c4503cb359c 100644 --- a/core/java/android/service/voice/ISandboxedDetectionService.aidl +++ b/core/java/android/service/voice/ISandboxedDetectionService.aidl @@ -65,11 +65,15 @@ oneway interface ISandboxedDetectionService { void updateRecognitionServiceManager( in IRecognitionServiceManager recognitionServiceManager); + interface IPingMe { + void onPing(); + } + /** * Simply requests the service to trigger the callback, so that the system can check its * identity. */ - void ping(in IRemoteCallback callback); + void ping(in IPingMe callback); void stopDetection(); diff --git a/core/java/android/service/voice/VisualQueryDetectionService.java b/core/java/android/service/voice/VisualQueryDetectionService.java index 8c9731d12df3..5dcaa3e976eb 100644 --- a/core/java/android/service/voice/VisualQueryDetectionService.java +++ b/core/java/android/service/voice/VisualQueryDetectionService.java @@ -122,8 +122,8 @@ public abstract class VisualQueryDetectionService extends Service } @Override - public void ping(IRemoteCallback callback) throws RemoteException { - callback.sendResult(null); + public void ping(IPingMe callback) throws RemoteException { + callback.onPing(); } @Override diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java index 44c3f9a8244e..0152c52a6753 100644 --- a/core/java/android/text/Layout.java +++ b/core/java/android/text/Layout.java @@ -75,9 +75,12 @@ public abstract class Layout { // These should match the constants in framework/base/libs/hwui/hwui/DrawTextFunctor.h private static final float HIGH_CONTRAST_TEXT_BORDER_WIDTH_MIN_PX = 0f; private static final float HIGH_CONTRAST_TEXT_BORDER_WIDTH_FACTOR = 0f; - private static final float HIGH_CONTRAST_TEXT_BACKGROUND_CORNER_RADIUS_DP = 5f; // since we're not using soft light yet, this needs to be much lower than the spec'd 0.8 private static final float HIGH_CONTRAST_TEXT_BACKGROUND_ALPHA_PERCENTAGE = 0.7f; + @VisibleForTesting + static final float HIGH_CONTRAST_TEXT_BACKGROUND_CORNER_RADIUS_MIN_DP = 5f; + @VisibleForTesting + static final float HIGH_CONTRAST_TEXT_BACKGROUND_CORNER_RADIUS_FACTOR = 0.5f; /** @hide */ @IntDef(prefix = { "BREAK_STRATEGY_" }, value = { @@ -1030,7 +1033,9 @@ public abstract class Layout { var padding = Math.max(HIGH_CONTRAST_TEXT_BORDER_WIDTH_MIN_PX, mPaint.getTextSize() * HIGH_CONTRAST_TEXT_BORDER_WIDTH_FACTOR); - var cornerRadius = mPaint.density * HIGH_CONTRAST_TEXT_BACKGROUND_CORNER_RADIUS_DP; + var cornerRadius = Math.max( + mPaint.density * HIGH_CONTRAST_TEXT_BACKGROUND_CORNER_RADIUS_MIN_DP, + mPaint.getTextSize() * HIGH_CONTRAST_TEXT_BACKGROUND_CORNER_RADIUS_FACTOR); // We set the alpha on the color itself instead of Paint.setAlpha(), because that function // actually mutates the color in... *ehem* very strange ways. Also the color might get reset diff --git a/core/java/android/util/MapCollections.java b/core/java/android/util/MapCollections.java index cce3a0e3eaa9..e7ceaae964ef 100644 --- a/core/java/android/util/MapCollections.java +++ b/core/java/android/util/MapCollections.java @@ -355,7 +355,12 @@ abstract class MapCollections<K, V> { } return result; } - }; + + @Override + public String toString() { + return toStringHelper(0, this, "KeySet"); + } + } final class ValuesCollection implements Collection<V> { @@ -456,7 +461,12 @@ abstract class MapCollections<K, V> { public <T> T[] toArray(T[] array) { return toArrayHelper(array, 1); } - }; + + @Override + public String toString() { + return toStringHelper(1, this, "ValuesCollection"); + } + } public static <K, V> boolean containsAllHelper(Map<K, V> map, Collection<?> collection) { Iterator<?> it = collection.iterator(); @@ -513,6 +523,29 @@ abstract class MapCollections<K, V> { return array; } + private String toStringHelper(int offset, Object thing, String thingName) { + int size = colGetSize(); + if (size == 0) { + return "[]"; + } + + StringBuilder buffer = new StringBuilder(size * 14); + buffer.append('['); + for (int i = 0; i < size; i++) { + if (i > 0) { + buffer.append(", "); + } + Object entry = colGetEntry(i, offset); + if (entry != thing) { + buffer.append(entry); + } else { + buffer.append("(this ").append(thingName).append(")"); + } + } + buffer.append(']'); + return buffer.toString(); + } + public static <T> boolean equalsSetHelper(Set<T> set, Object object) { if (set == object) { return true; diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java index 7c1e4976b9d3..3a2ec91b3b20 100644 --- a/core/java/android/view/Choreographer.java +++ b/core/java/android/view/Choreographer.java @@ -967,8 +967,11 @@ public final class Choreographer { DisplayEventReceiver.VsyncEventData vsyncEventData) { final long startNanos; final long frameIntervalNanos = vsyncEventData.frameInterval; - boolean resynced = false; + // Original intended vsync time that is not adjusted by jitter + // or buffer stuffing recovery. Reported for jank tracking. + final long intendedFrameTimeNanos = frameTimeNanos; long offsetFrameTimeNanos = frameTimeNanos; + boolean resynced = false; // Evaluate if buffer stuffing recovery needs to start or end, and // what actions need to be taken for recovery. @@ -1012,7 +1015,6 @@ public final class Choreographer { + ((offsetFrameTimeNanos - mLastFrameTimeNanos) * 0.000001f) + " ms"); } - long intendedFrameTimeNanos = offsetFrameTimeNanos; startNanos = System.nanoTime(); // Calculating jitter involves using the original frame time without // adjustments from buffer stuffing diff --git a/core/java/android/view/LayoutInflater.java b/core/java/android/view/LayoutInflater.java index aad3bf2679d9..901fc02f0040 100644 --- a/core/java/android/view/LayoutInflater.java +++ b/core/java/android/view/LayoutInflater.java @@ -516,8 +516,9 @@ public abstract class LayoutInflater { mConstructorArgs[0] = inflaterContext; View result = root; - if (root != null && root.getViewRootImpl() != null) { - root.getViewRootImpl().notifyRendererOfExpensiveFrame(); + ViewRootImpl viewRootImpl = root != null ? root.getViewRootImpl() : null; + if (viewRootImpl != null) { + viewRootImpl.notifyRendererOfExpensiveFrame(); } try { diff --git a/core/java/android/view/ListenerWrapper.java b/core/java/android/view/ListenerWrapper.java new file mode 100644 index 000000000000..fcf3fdb68112 --- /dev/null +++ b/core/java/android/view/ListenerWrapper.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +import android.annotation.NonNull; + +import java.util.Objects; +import java.util.concurrent.Executor; +import java.util.function.Consumer; + +/** + * A utilty class to bundle a {@link Consumer} and an {@link Executor} + * @param <T> the type of value to be reported. + * @hide + */ +public class ListenerWrapper<T> { + + @NonNull + private final Consumer<T> mConsumer; + @NonNull + private final Executor mExecutor; + + public ListenerWrapper(@NonNull Executor executor, @NonNull Consumer<T> consumer) { + mExecutor = Objects.requireNonNull(executor); + mConsumer = Objects.requireNonNull(consumer); + } + + /** + * Relays the new value to the {@link Consumer} using the {@link Executor} + */ + public void accept(@NonNull T value) { + mExecutor.execute(() -> mConsumer.accept(value)); + } + + /** + * Returns {@code true} if the consumer matches the one provided in the constructor, + * {@code false} otherwise. + */ + public boolean isConsumerSame(@NonNull Consumer<T> consumer) { + return mConsumer.equals(consumer); + } +} diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java index a5da0c3ce5b1..624216776f42 100644 --- a/core/java/android/view/WindowManagerGlobal.java +++ b/core/java/android/view/WindowManagerGlobal.java @@ -44,6 +44,7 @@ import android.util.Log; import android.util.Pair; import android.util.SparseArray; import android.view.inputmethod.InputMethodManager; +import android.view.translation.ListenerGroup; import android.window.ITrustedPresentationListener; import android.window.InputTransferToken; import android.window.TrustedPresentationThresholds; @@ -58,6 +59,7 @@ import java.io.FileOutputStream; import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.List; import java.util.WeakHashMap; import java.util.concurrent.Executor; import java.util.function.Consumer; @@ -147,6 +149,12 @@ public final class WindowManagerGlobal { @UnsupportedAppUsage private final ArrayList<View> mViews = new ArrayList<View>(); + /** + * The {@link ListenerGroup} that is associated to {@link #mViews}. + * @hide + */ + @GuardedBy("mLock") + private final ListenerGroup<List<View>> mWindowViewsListenerGroup = new ListenerGroup<>(); @UnsupportedAppUsage private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>(); @UnsupportedAppUsage @@ -319,6 +327,29 @@ public final class WindowManagerGlobal { } } + /** + * Adds a listener that will be notified whenever {@link #getWindowViews()} changes. The + * current value is provided immediately. If it was registered previously then this is ano op. + */ + public void addWindowViewsListener(@NonNull Executor executor, + @NonNull Consumer<List<View>> consumer) { + synchronized (mLock) { + mWindowViewsListenerGroup.addListener(executor, consumer); + mWindowViewsListenerGroup.accept(getWindowViews()); + } + } + + /** + * Removes a listener that was registered in + * {@link #addWindowViewsListener(Executor, Consumer)}. If it was not registered previously, + * then this is a no op. + */ + public void removeWindowViewsListener(@NonNull Consumer<List<View>> consumer) { + synchronized (mLock) { + mWindowViewsListenerGroup.removeListener(consumer); + } + } + public View getWindowView(IBinder windowToken) { synchronized (mLock) { final int numViews = mViews.size(); @@ -454,6 +485,7 @@ public final class WindowManagerGlobal { // do this last because it fires off messages to start doing things try { root.setView(view, wparams, panelParentView, userId); + mWindowViewsListenerGroup.accept(getWindowViews()); } catch (RuntimeException e) { Log.e(TAG, "Couldn't add view: " + view, e); final int viewIndex = (index >= 0) ? index : (mViews.size() - 1); @@ -575,6 +607,7 @@ public final class WindowManagerGlobal { mDyingViews.remove(view); } allViewsRemoved = mRoots.isEmpty(); + mWindowViewsListenerGroup.accept(getWindowViews()); } // If we don't have any views anymore in our process, we no longer need the diff --git a/core/java/android/view/flags/view_flags.aconfig b/core/java/android/view/flags/view_flags.aconfig index d06f885638b6..d97310494d34 100644 --- a/core/java/android/view/flags/view_flags.aconfig +++ b/core/java/android/view/flags/view_flags.aconfig @@ -159,3 +159,11 @@ flag { bug: "364653005" is_fixed_read_only: true } + +flag { + name: "root_view_changed_listener" + namespace: "windowing_sdk" + description: "Implement listener pattern for WindowInspector#getGlobalWindowViews." + bug: "394397033" + is_fixed_read_only: false +} diff --git a/core/java/android/view/inspector/WindowInspector.java b/core/java/android/view/inspector/WindowInspector.java index 69d004e860fd..3ebca3c9d9b6 100644 --- a/core/java/android/view/inspector/WindowInspector.java +++ b/core/java/android/view/inspector/WindowInspector.java @@ -16,11 +16,14 @@ package android.view.inspector; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.view.View; import android.view.WindowManagerGlobal; import java.util.List; +import java.util.concurrent.Executor; +import java.util.function.Consumer; /** * Provides access to window inspection information. @@ -37,4 +40,25 @@ public final class WindowInspector { public static List<View> getGlobalWindowViews() { return WindowManagerGlobal.getInstance().getWindowViews(); } + + /** + * Adds a listener that is notified whenever the list of global window views changes. If a + * {@link Consumer} is already registered this method is a no op. + * @see #getGlobalWindowViews() + */ + @FlaggedApi(android.view.flags.Flags.FLAG_ROOT_VIEW_CHANGED_LISTENER) + public static void addGlobalWindowViewsListener(@NonNull Executor executor, + @NonNull Consumer<List<View>> consumer) { + WindowManagerGlobal.getInstance().addWindowViewsListener(executor, consumer); + } + + /** + * Removes a listener from getting notifications of global window views changes. If the + * {@link Consumer} is not registered this method is a no op. + * @see #addGlobalWindowViewsListener(Executor, Consumer) + */ + @FlaggedApi(android.view.flags.Flags.FLAG_ROOT_VIEW_CHANGED_LISTENER) + public static void removeGlobalWindowViewsListener(@NonNull Consumer<List<View>> consumer) { + WindowManagerGlobal.getInstance().removeWindowViewsListener(consumer); + } } diff --git a/core/java/android/view/translation/ListenerGroup.java b/core/java/android/view/translation/ListenerGroup.java new file mode 100644 index 000000000000..bf506815f841 --- /dev/null +++ b/core/java/android/view/translation/ListenerGroup.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.translation; + +import android.annotation.NonNull; +import android.view.ListenerWrapper; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.Executor; +import java.util.function.Consumer; + +/** + * A utility class to manage a list of {@link ListenerWrapper}. This class is not thread safe. + * @param <T> the type of the value to be reported. + * @hide + */ +public class ListenerGroup<T> { + private final List<ListenerWrapper<T>> mListeners = new ArrayList<>(); + + /** + * Relays the value to all the registered {@link java.util.function.Consumer} + */ + public void accept(@NonNull T value) { + Objects.requireNonNull(value); + for (int i = 0; i < mListeners.size(); i++) { + mListeners.get(i).accept(value); + } + } + + /** + * Adds a {@link Consumer} to the group. If the {@link Consumer} is already present then this + * is a no op. + */ + public void addListener(@NonNull Executor executor, @NonNull Consumer<T> consumer) { + if (isContained(consumer)) { + return; + } + mListeners.add(new ListenerWrapper<>(executor, consumer)); + } + + /** + * Removes a {@link Consumer} from the group. If the {@link Consumer} was not present then this + * is a no op. + */ + public void removeListener(@NonNull Consumer<T> consumer) { + final int index = computeIndex(consumer); + if (index > -1) { + mListeners.remove(index); + } + } + + /** + * Returns {@code true} if the {@link Consumer} is present in the list, {@code false} + * otherwise. + */ + private boolean isContained(Consumer<T> consumer) { + return computeIndex(consumer) > -1; + } + + /** + * Returns the index of the matching {@link ListenerWrapper} if present, {@code -1} otherwise. + */ + private int computeIndex(Consumer<T> consumer) { + for (int i = 0; i < mListeners.size(); i++) { + if (mListeners.get(i).isConsumerSame(consumer)) { + return i; + } + } + return -1; + } +} diff --git a/core/java/android/window/DesktopExperienceFlags.java b/core/java/android/window/DesktopExperienceFlags.java index cf582176a9f7..866c16cb566d 100644 --- a/core/java/android/window/DesktopExperienceFlags.java +++ b/core/java/android/window/DesktopExperienceFlags.java @@ -47,17 +47,19 @@ public enum DesktopExperienceFlags { com.android.server.display.feature.flags.Flags::baseDensityForExternalDisplays, true), CONNECTED_DISPLAYS_CURSOR(com.android.input.flags.Flags::connectedDisplaysCursor, true), DISPLAY_TOPOLOGY(com.android.server.display.feature.flags.Flags::displayTopology, true), - ENABLE_BUG_FIXES_FOR_SECONDARY_DISPLAY(Flags::enableBugFixesForSecondaryDisplay, false), + ENABLE_BUG_FIXES_FOR_SECONDARY_DISPLAY(Flags::enableBugFixesForSecondaryDisplay, true), ENABLE_CONNECTED_DISPLAYS_DND(Flags::enableConnectedDisplaysDnd, false), ENABLE_CONNECTED_DISPLAYS_PIP(Flags::enableConnectedDisplaysPip, false), - ENABLE_CONNECTED_DISPLAYS_WINDOW_DRAG(Flags::enableConnectedDisplaysWindowDrag, false), + ENABLE_CONNECTED_DISPLAYS_WALLPAPER( + android.app.Flags::enableConnectedDisplaysWallpaper, false), + ENABLE_CONNECTED_DISPLAYS_WINDOW_DRAG(Flags::enableConnectedDisplaysWindowDrag, true), ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT( com.android.server.display.feature.flags.Flags::enableDisplayContentModeManagement, true), - ENABLE_DISPLAY_FOCUS_IN_SHELL_TRANSITIONS(Flags::enableDisplayFocusInShellTransitions, false), - ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING(Flags::enableDisplayWindowingModeSwitching, false), + ENABLE_DISPLAY_FOCUS_IN_SHELL_TRANSITIONS(Flags::enableDisplayFocusInShellTransitions, true), + ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING(Flags::enableDisplayWindowingModeSwitching, true), ENABLE_DRAG_TO_MAXIMIZE(Flags::enableDragToMaximize, true), - ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT(Flags::enableMoveToNextDisplayShortcut, false), + ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT(Flags::enableMoveToNextDisplayShortcut, true), ENABLE_MULTIPLE_DESKTOPS_BACKEND(Flags::enableMultipleDesktopsBackend, false), ENABLE_MULTIPLE_DESKTOPS_FRONTEND(Flags::enableMultipleDesktopsFrontend, false), ENABLE_PERSISTING_DISPLAY_SIZE_FOR_CONNECTED_DISPLAYS( @@ -66,9 +68,11 @@ public enum DesktopExperienceFlags { false), ENABLE_PER_DISPLAY_PACKAGE_CONTEXT_CACHE_IN_STATUSBAR_NOTIF( Flags::enablePerDisplayPackageContextCacheInStatusbarNotif, false), - ENABLE_TASKBAR_CONNECTED_DISPLAYS(Flags::enableTaskbarConnectedDisplays, false), + ENABLE_PROJECTED_DISPLAY_DESKTOP_MODE(Flags::enableProjectedDisplayDesktopMode, false), + ENABLE_TASKBAR_CONNECTED_DISPLAYS(Flags::enableTaskbarConnectedDisplays, true), ENTER_DESKTOP_BY_DEFAULT_ON_FREEFORM_DISPLAYS(Flags::enterDesktopByDefaultOnFreeformDisplays, - false), + true), + FORM_FACTOR_BASED_DESKTOP_FIRST_SWITCH(Flags::formFactorBasedDesktopFirstSwitch, false), REPARENT_WINDOW_TOKEN_API(Flags::reparentWindowTokenApi, true) // go/keep-sorted end ; diff --git a/core/java/android/window/DesktopModeFlags.java b/core/java/android/window/DesktopModeFlags.java index 78769248c013..17165cdcf7b1 100644 --- a/core/java/android/window/DesktopModeFlags.java +++ b/core/java/android/window/DesktopModeFlags.java @@ -138,10 +138,14 @@ public enum DesktopModeFlags { ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS( Flags::enableWindowingTransitionHandlersObservers, false), EXCLUDE_CAPTION_FROM_APP_BOUNDS(Flags::excludeCaptionFromAppBounds, false), + FORCE_CLOSE_TOP_TRANSPARENT_FULLSCREEN_TASK( + Flags::forceCloseTopTransparentFullscreenTask, false), IGNORE_ASPECT_RATIO_RESTRICTIONS_FOR_RESIZEABLE_FREEFORM_ACTIVITIES( Flags::ignoreAspectRatioRestrictionsForResizeableFreeformActivities, true), INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC( - Flags::includeTopTransparentFullscreenTaskInDesktopHeuristic, true) + Flags::includeTopTransparentFullscreenTaskInDesktopHeuristic, true), + INHERIT_TASK_BOUNDS_FOR_TRAMPOLINE_TASK_LAUNCHES( + Flags::inheritTaskBoundsForTrampolineTaskLaunches, false), // go/keep-sorted end ; diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java index 1156503cf8e8..ea345a5ef46a 100644 --- a/core/java/android/window/WindowContainerTransaction.java +++ b/core/java/android/window/WindowContainerTransaction.java @@ -688,12 +688,6 @@ public final class WindowContainerTransaction implements Parcelable { @NonNull public WindowContainerTransaction setAdjacentRoots( @NonNull WindowContainerToken root1, @NonNull WindowContainerToken root2) { - if (!Flags.allowMultipleAdjacentTaskFragments()) { - mHierarchyOps.add(HierarchyOp.createForAdjacentRoots( - root1.asBinder(), - root2.asBinder())); - return this; - } return setAdjacentRootSet(root1, root2); } @@ -714,10 +708,6 @@ public final class WindowContainerTransaction implements Parcelable { */ @NonNull public WindowContainerTransaction setAdjacentRootSet(@NonNull WindowContainerToken... roots) { - if (!Flags.allowMultipleAdjacentTaskFragments()) { - throw new IllegalArgumentException("allowMultipleAdjacentTaskFragments is not enabled." - + " Use #setAdjacentRoots instead."); - } if (roots.length < 2) { throw new IllegalArgumentException("setAdjacentRootSet must have size >= 2"); } @@ -1973,13 +1963,6 @@ public final class WindowContainerTransaction implements Parcelable { return mContainers; } - /** @deprecated b/373709676 replace with {@link #getContainers()}. */ - @Deprecated - @NonNull - public IBinder getAdjacentRoot() { - return mReparent; - } - public boolean getToTop() { return mToTop; } @@ -2127,17 +2110,12 @@ public final class WindowContainerTransaction implements Parcelable { sb.append(mContainer).append(" to ").append(mToTop ? "top" : "bottom"); break; case HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS: - if (Flags.allowMultipleAdjacentTaskFragments()) { - for (IBinder container : mContainers) { - if (container == mContainers[0]) { - sb.append("adjacentRoots=").append(container); - } else { - sb.append(", ").append(container); - } + for (IBinder container : mContainers) { + if (container == mContainers[0]) { + sb.append("adjacentRoots=").append(container); + } else { + sb.append(", ").append(container); } - } else { - sb.append("container=").append(mContainer) - .append(" adjacentRoot=").append(mReparent); } break; case HIERARCHY_OP_TYPE_LAUNCH_TASK: diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig index 1f710c1cc8c0..e706af999117 100644 --- a/core/java/android/window/flags/lse_desktop_experience.aconfig +++ b/core/java/android/window/flags/lse_desktop_experience.aconfig @@ -27,6 +27,17 @@ flag { } flag { + name: "inherit_task_bounds_for_trampoline_task_launches" + namespace: "lse_desktop_experience" + description: "Forces trampoline task launches to inherit the bounds of the previous instance /n" + "before is closes to prevent each task from cascading." + bug: "392815318" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "include_top_transparent_fullscreen_task_in_desktop_heuristic" namespace: "lse_desktop_experience" description: "Whether to include any top transparent fullscreen task launched in desktop /n" @@ -50,6 +61,17 @@ flag { } flag { + name: "force_close_top_transparent_fullscreen_task" + namespace: "lse_desktop_experience" + description: "If a top transparent fullscreen task is on top of desktop mode, force it to /n" + "close if another task is opened or brought to front." + bug: "395041610" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "enable_windowing_dynamic_initial_bounds" namespace: "lse_desktop_experience" description: "Enables new initial bounds for desktop windowing which adjust depending on app constraints" @@ -916,3 +938,10 @@ flag { description: "Enables the desktop-first mode switching logic based on its form factor." bug: "394736817" } + +flag { + name: "enable_restart_menu_for_connected_displays" + namespace: "lse_desktop_experience" + description: "Enable restart menu UI, which is shown when an app moves between displays." + bug: "397804287" +} diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig index b4fec416bd5f..3927c713e500 100644 --- a/core/java/android/window/flags/windowing_frontend.aconfig +++ b/core/java/android/window/flags/windowing_frontend.aconfig @@ -86,6 +86,14 @@ flag { } flag { + name: "action_mode_edge_to_edge" + namespace: "windowing_frontend" + description: "Make contextual action bar edge-to-edge" + bug: "379783298" + is_fixed_read_only: true +} + +flag { name: "keyguard_going_away_timeout" namespace: "windowing_frontend" description: "Allow a maximum of 10 seconds with keyguardGoingAway=true before force-resetting" @@ -501,6 +509,17 @@ flag { description: "Sets Launch powermode for activity launches earlier" bug: "399380676" is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + name: "scramble_snapshot_file_name" + namespace: "windowing_frontend" + description: "Scramble the file name of task snapshot." + bug: "293139053" + is_fixed_read_only: true metadata { purpose: PURPOSE_BUGFIX } diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index c009fc3b7e63..9bc6671bbc31 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -23,6 +23,7 @@ import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CROSS_PROFILE_BLOCKED_TITLE; import static android.content.ContentProvider.getUriWithoutUserId; import static android.content.ContentProvider.getUserIdFromUri; +import static android.service.chooser.Flags.notifySingleItemChangeOnIconLoad; import static android.stats.devicepolicy.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_PERSONAL; import static android.stats.devicepolicy.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK; @@ -163,9 +164,11 @@ import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -3212,6 +3215,8 @@ public class ChooserActivity extends ResolverActivity implements private static final int NUM_EXPANSIONS_TO_HIDE_AZ_LABEL = 20; + private final Set<ViewHolderBase> mBoundViewHolders = new HashSet<>(); + ChooserGridAdapter(ChooserListAdapter wrappedAdapter) { super(); mChooserListAdapter = wrappedAdapter; @@ -3232,6 +3237,31 @@ public class ChooserActivity extends ResolverActivity implements notifyDataSetChanged(); } }); + if (notifySingleItemChangeOnIconLoad()) { + wrappedAdapter.setOnIconLoadedListener(this::onTargetIconLoaded); + } + } + + private void onTargetIconLoaded(DisplayResolveInfo info) { + for (ViewHolderBase holder : mBoundViewHolders) { + switch (holder.getViewType()) { + case VIEW_TYPE_NORMAL: + TargetInfo itemInfo = + mChooserListAdapter.getItem( + ((ItemViewHolder) holder).mListPosition); + if (info == itemInfo) { + notifyItemChanged(holder.getAdapterPosition()); + } + break; + case VIEW_TYPE_CALLER_AND_RANK: + ItemGroupViewHolder groupHolder = (ItemGroupViewHolder) holder; + if (suggestedAppsGroupContainsTarget(groupHolder, info)) { + notifyItemChanged(holder.getAdapterPosition()); + } + break; + } + + } } public void setFooterHeight(int height) { @@ -3382,6 +3412,9 @@ public class ChooserActivity extends ResolverActivity implements @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + if (notifySingleItemChangeOnIconLoad()) { + mBoundViewHolders.add((ViewHolderBase) holder); + } int viewType = ((ViewHolderBase) holder).getViewType(); switch (viewType) { case VIEW_TYPE_DIRECT_SHARE: @@ -3396,6 +3429,22 @@ public class ChooserActivity extends ResolverActivity implements } @Override + public void onViewRecycled(RecyclerView.ViewHolder holder) { + if (notifySingleItemChangeOnIconLoad()) { + mBoundViewHolders.remove((ViewHolderBase) holder); + } + super.onViewRecycled(holder); + } + + @Override + public boolean onFailedToRecycleView(RecyclerView.ViewHolder holder) { + if (notifySingleItemChangeOnIconLoad()) { + mBoundViewHolders.remove((ViewHolderBase) holder); + } + return super.onFailedToRecycleView(holder); + } + + @Override public int getItemViewType(int position) { int count; @@ -3604,6 +3653,33 @@ public class ChooserActivity extends ResolverActivity implements } } + /** + * Checks whether the suggested apps group, {@code holder}, contains the target, + * {@code info}. + */ + private boolean suggestedAppsGroupContainsTarget( + ItemGroupViewHolder holder, DisplayResolveInfo info) { + + int position = holder.getAdapterPosition(); + int start = getListPosition(position); + int startType = getRowType(start); + + int columnCount = holder.getColumnCount(); + int end = start + columnCount - 1; + while (getRowType(end) != startType && end >= start) { + end--; + } + + for (int i = 0; i < columnCount; i++) { + if (start + i <= end) { + if (mChooserListAdapter.getItem(holder.getItemIndex(i)) == info) { + return true; + } + } + } + return false; + } + int getListPosition(int position) { position -= getSystemRowCount() + getProfileRowCount(); diff --git a/core/java/com/android/internal/app/ChooserListAdapter.java b/core/java/com/android/internal/app/ChooserListAdapter.java index d38689c7505b..1b8c36db3908 100644 --- a/core/java/com/android/internal/app/ChooserListAdapter.java +++ b/core/java/com/android/internal/app/ChooserListAdapter.java @@ -16,9 +16,12 @@ package com.android.internal.app; +import static android.service.chooser.Flags.notifySingleItemChangeOnIconLoad; + import static com.android.internal.app.ChooserActivity.TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE; import static com.android.internal.app.ChooserActivity.TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER; +import android.annotation.Nullable; import android.app.prediction.AppPredictor; import android.content.ComponentName; import android.content.Context; @@ -56,6 +59,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Consumer; public class ChooserListAdapter extends ResolverListAdapter { private static final String TAG = "ChooserListAdapter"; @@ -108,6 +112,9 @@ public class ChooserListAdapter extends ResolverListAdapter { // Represents the UserSpace in which the Initial Intents should be resolved. private final UserHandle mInitialIntentsUserSpace; + @Nullable + private Consumer<DisplayResolveInfo> mOnIconLoadedListener; + // For pinned direct share labels, if the text spans multiple lines, the TextView will consume // the full width, even if the characters actually take up less than that. Measure the actual // line widths and constrain the View's width based upon that so that the pin doesn't end up @@ -218,6 +225,10 @@ public class ChooserListAdapter extends ResolverListAdapter { true); } + public void setOnIconLoadedListener(Consumer<DisplayResolveInfo> onIconLoadedListener) { + mOnIconLoadedListener = onIconLoadedListener; + } + AppPredictor getAppPredictor() { return mAppPredictor; } @@ -329,6 +340,15 @@ public class ChooserListAdapter extends ResolverListAdapter { } } + @Override + protected void onIconLoaded(DisplayResolveInfo info) { + if (notifySingleItemChangeOnIconLoad() && mOnIconLoadedListener != null) { + mOnIconLoadedListener.accept(info); + } else { + notifyDataSetChanged(); + } + } + private void loadDirectShareIcon(SelectableTargetInfo info) { LoadDirectShareIconTask task = (LoadDirectShareIconTask) mIconLoaders.get(info); if (task == null) { diff --git a/core/java/com/android/internal/app/ResolverListAdapter.java b/core/java/com/android/internal/app/ResolverListAdapter.java index 54c0e61fd5cd..4d9ce86096c7 100644 --- a/core/java/com/android/internal/app/ResolverListAdapter.java +++ b/core/java/com/android/internal/app/ResolverListAdapter.java @@ -680,6 +680,10 @@ public class ResolverListAdapter extends BaseAdapter { } } + protected void onIconLoaded(DisplayResolveInfo info) { + notifyDataSetChanged(); + } + private void loadLabel(DisplayResolveInfo info) { LoadLabelTask task = mLabelLoaders.get(info); if (task == null) { @@ -1004,7 +1008,7 @@ public class ResolverListAdapter extends BaseAdapter { mResolverListCommunicator.updateProfileViewButton(); } else if (!mDisplayResolveInfo.hasDisplayIcon()) { mDisplayResolveInfo.setDisplayIcon(d); - notifyDataSetChanged(); + onIconLoaded(mDisplayResolveInfo); } } } diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java index 81ca23173457..c6207f9451c2 100644 --- a/core/java/com/android/internal/os/BatteryStatsHistory.java +++ b/core/java/com/android/internal/os/BatteryStatsHistory.java @@ -45,11 +45,13 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import java.io.PrintWriter; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.ConcurrentModificationException; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Queue; import java.util.concurrent.locks.ReentrantLock; /** @@ -203,15 +205,6 @@ public class BatteryStatsHistory { BatteryHistoryFragment getLatestFragment(); /** - * Given a fragment, returns the earliest fragment that follows it whose monotonic - * start time falls within the specified range. `startTimeMs` is inclusive, `endTimeMs` - * is exclusive. - */ - @Nullable - BatteryHistoryFragment getNextFragment(BatteryHistoryFragment current, long startTimeMs, - long endTimeMs); - - /** * Acquires a lock on the entire store. */ void lock(); @@ -268,6 +261,60 @@ public class BatteryStatsHistory { void reset(); } + class BatteryHistoryParcelContainer { + private boolean mParcelReadyForReading; + private Parcel mParcel; + private BatteryStatsHistory.BatteryHistoryFragment mFragment; + private long mMonotonicStartTime; + + BatteryHistoryParcelContainer(@NonNull Parcel parcel, long monotonicStartTime) { + mParcel = parcel; + mMonotonicStartTime = monotonicStartTime; + mParcelReadyForReading = true; + } + + BatteryHistoryParcelContainer(@NonNull BatteryHistoryFragment fragment) { + mFragment = fragment; + mMonotonicStartTime = fragment.monotonicTimeMs; + mParcelReadyForReading = false; + } + + @Nullable + Parcel getParcel() { + if (mParcelReadyForReading) { + return mParcel; + } + + Parcel parcel = Parcel.obtain(); + if (readFragmentToParcel(parcel, mFragment)) { + parcel.readInt(); // skip buffer size + mParcel = parcel; + } else { + parcel.recycle(); + } + mParcelReadyForReading = true; + return mParcel; + } + + long getMonotonicStartTime() { + return mMonotonicStartTime; + } + + /** + * Recycles the parcel (if appropriate). Should be called after the parcel has been + * processed by the iterator. + */ + void close() { + if (mParcel != null && mFragment != null) { + mParcel.recycle(); + } + // ParcelContainers are not meant to be reusable. To prevent any unintentional + // access to the parcel after it has been recycled, clear the references to it. + mParcel = null; + mFragment = null; + } + } + private final Parcel mHistoryBuffer; private final HistoryStepDetailsCalculator mStepDetailsCalculator; private final Clock mClock; @@ -447,6 +494,7 @@ public class BatteryStatsHistory { mWritableHistory = writableHistory; if (mWritableHistory != null) { mMutable = false; + mHistoryBufferStartTime = mWritableHistory.mHistoryBufferStartTime; mHistoryMonotonicEndTime = mWritableHistory.mHistoryMonotonicEndTime; } @@ -689,95 +737,66 @@ public class BatteryStatsHistory { } /** - * When iterating history files and history buffer, always start from the lowest numbered - * history file, when reached the mActiveFile (highest numbered history file), do not read from - * mActiveFile, read from history buffer instead because the buffer has more updated data. - * - * @return The parcel that has next record. null if finished all history files and history - * buffer + * Returns all chunks of accumulated history that contain items within the range between + * `startTimeMs` (inclusive) and `endTimeMs` (exclusive) */ - @Nullable - public Parcel getNextParcel(long startTimeMs, long endTimeMs) { - checkImmutable(); + Queue<BatteryHistoryParcelContainer> getParcelContainers(long startTimeMs, long endTimeMs) { + if (mMutable) { + throw new IllegalStateException("Iterating over a mutable battery history"); + } - // First iterate through all records in current parcel. - if (mCurrentParcel != null) { - if (mCurrentParcel.dataPosition() < mCurrentParcelEnd) { - // There are more records in current parcel. - return mCurrentParcel; - } else if (mHistoryBuffer == mCurrentParcel) { - // finished iterate through all history files and history buffer. - return null; - } else if (mHistoryParcels == null - || !mHistoryParcels.contains(mCurrentParcel)) { - // current parcel is from history file. - mCurrentParcel.recycle(); - } + if (endTimeMs == MonotonicClock.UNDEFINED || endTimeMs == 0) { + endTimeMs = Long.MAX_VALUE; } + Queue<BatteryHistoryParcelContainer> containers = new ArrayDeque<>(); + if (mStore != null) { - BatteryHistoryFragment next = mStore.getNextFragment(mCurrentFragment, startTimeMs, - endTimeMs); - while (next != null) { - mCurrentParcel = null; - mCurrentParcelEnd = 0; - final Parcel p = Parcel.obtain(); - if (readFragmentToParcel(p, next)) { - int bufSize = p.readInt(); - int curPos = p.dataPosition(); - mCurrentParcelEnd = curPos + bufSize; - mCurrentParcel = p; - if (curPos < mCurrentParcelEnd) { - mCurrentFragment = next; - return mCurrentParcel; - } - } else { - p.recycle(); + List<BatteryHistoryFragment> fragments = mStore.getFragments(); + for (int i = 0; i < fragments.size(); i++) { + BatteryHistoryFragment fragment = fragments.get(i); + if (fragment.monotonicTimeMs >= endTimeMs) { + break; + } + + if (fragment.monotonicTimeMs >= startTimeMs && fragment != mActiveFragment) { + containers.add(new BatteryHistoryParcelContainer(fragment)); } - next = mStore.getNextFragment(next, startTimeMs, endTimeMs); } } - // mHistoryParcels is created when BatteryStatsImpl object is created from deserialization - // of a parcel, such as Settings app or checkin file. if (mHistoryParcels != null) { - while (mParcelIndex < mHistoryParcels.size()) { - final Parcel p = mHistoryParcels.get(mParcelIndex++); + for (int i = 0; i < mHistoryParcels.size(); i++) { + final Parcel p = mHistoryParcels.get(i); if (!verifyVersion(p)) { continue; } - // skip monotonic time field. - p.readLong(); - // skip monotonic end time field - p.readLong(); + + long monotonicStartTime = p.readLong(); + if (monotonicStartTime >= endTimeMs) { + continue; + } + + long monotonicEndTime = p.readLong(); + if (monotonicEndTime < startTimeMs) { + continue; + } + // skip monotonic size field p.readLong(); + // skip buffer size field + p.readInt(); - final int bufSize = p.readInt(); - final int curPos = p.dataPosition(); - mCurrentParcelEnd = curPos + bufSize; - mCurrentParcel = p; - if (curPos < mCurrentParcelEnd) { - return mCurrentParcel; - } + containers.add(new BatteryHistoryParcelContainer(p, monotonicStartTime)); } } - // finished iterator through history files (except the last one), now history buffer. - if (mHistoryBuffer.dataSize() <= 0) { - // buffer is empty. - return null; - } - mHistoryBuffer.setDataPosition(0); - mCurrentParcel = mHistoryBuffer; - mCurrentParcelEnd = mCurrentParcel.dataSize(); - return mCurrentParcel; - } - - private void checkImmutable() { - if (mMutable) { - throw new IllegalStateException("Iterating over a mutable battery history"); + if (mHistoryBufferStartTime < endTimeMs) { + mHistoryBuffer.setDataPosition(0); + containers.add( + new BatteryHistoryParcelContainer(mHistoryBuffer, mHistoryBufferStartTime)); } + return containers; } /** @@ -818,21 +837,6 @@ public class BatteryStatsHistory { } /** - * Extracts the monotonic time, as per {@link MonotonicClock}, from the supplied battery history - * buffer. - */ - public long getHistoryBufferStartTime(Parcel p) { - int pos = p.dataPosition(); - p.setDataPosition(0); - p.readInt(); // Skip the version field - long monotonicTime = p.readLong(); - p.readLong(); // Skip monotonic end time field - p.readLong(); // Skip monotonic size field - p.setDataPosition(pos); - return monotonicTime; - } - - /** * Writes the battery history contents for persistence. */ public void writeSummaryToParcel(Parcel out, boolean inclHistory) { diff --git a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java index 38398b4bf6f0..09f9b0bf8c7e 100644 --- a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java +++ b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java @@ -24,6 +24,7 @@ import android.util.Slog; import android.util.SparseArray; import java.util.Iterator; +import java.util.Queue; /** * An iterator for {@link BatteryStats.HistoryItem}'s. @@ -48,7 +49,10 @@ public class BatteryStatsHistoryIterator implements Iterator<BatteryStats.Histor private long mBaseMonotonicTime; private long mBaseTimeUtc; private int mItemIndex = 0; - private int mMaxHistoryItems; + private final int mMaxHistoryItems; + private int mParcelDataPosition; + + private Queue<BatteryStatsHistory.BatteryHistoryParcelContainer> mParcelContainers; public BatteryStatsHistoryIterator(@NonNull BatteryStatsHistory history, long startTimeMs, long endTimeMs) { @@ -62,7 +66,11 @@ public class BatteryStatsHistoryIterator implements Iterator<BatteryStats.Histor @Override public boolean hasNext() { if (!mNextItemReady) { - advance(); + if (!advance()) { + mHistoryItem = null; + close(); + } + mNextItemReady = true; } return mHistoryItem != null; @@ -75,35 +83,48 @@ public class BatteryStatsHistoryIterator implements Iterator<BatteryStats.Histor @Override public BatteryStats.HistoryItem next() { if (!mNextItemReady) { - advance(); + if (!advance()) { + mHistoryItem = null; + close(); + } } mNextItemReady = false; return mHistoryItem; } - private void advance() { - while (true) { - if (mItemIndex > mMaxHistoryItems) { - Slog.wtfStack(TAG, "Number of battery history items is too large: " + mItemIndex); - break; - } + private boolean advance() { + if (mParcelContainers == null) { + mParcelContainers = mBatteryStatsHistory.getParcelContainers(mStartTimeMs, mEndTimeMs); + } - Parcel p = mBatteryStatsHistory.getNextParcel(mStartTimeMs, mEndTimeMs); - if (p == null) { - break; + BatteryStatsHistory.BatteryHistoryParcelContainer container; + while ((container = mParcelContainers.peek()) != null) { + Parcel p = container.getParcel(); + if (p == null || p.dataPosition() >= p.dataSize()) { + container.close(); + mParcelContainers.remove(); + mParcelDataPosition = 0; + continue; } if (!mTimeInitialized) { - mBaseMonotonicTime = mBatteryStatsHistory.getHistoryBufferStartTime(p); + mBaseMonotonicTime = container.getMonotonicStartTime(); mHistoryItem.time = mBaseMonotonicTime; mTimeInitialized = true; } try { readHistoryDelta(p, mHistoryItem); + int dataPosition = p.dataPosition(); + if (dataPosition <= mParcelDataPosition) { + Slog.wtf(TAG, "Corrupted battery history, parcel is not progressing: " + + dataPosition + " of " + p.dataSize()); + return false; + } + mParcelDataPosition = dataPosition; } catch (Throwable t) { Slog.wtf(TAG, "Corrupted battery history", t); - break; + return false; } if (mHistoryItem.cmd == BatteryStats.HistoryItem.CMD_CURRENT_TIME @@ -111,21 +132,24 @@ public class BatteryStatsHistoryIterator implements Iterator<BatteryStats.Histor mBaseTimeUtc = mHistoryItem.currentTime - (mHistoryItem.time - mBaseMonotonicTime); } - mHistoryItem.currentTime = mBaseTimeUtc + (mHistoryItem.time - mBaseMonotonicTime); + if (mHistoryItem.time < mStartTimeMs) { + continue; + } - if (mEndTimeMs != 0 && mHistoryItem.time >= mEndTimeMs) { - break; + if (mEndTimeMs != 0 && mEndTimeMs != MonotonicClock.UNDEFINED + && mHistoryItem.time >= mEndTimeMs) { + return false; } - if (mHistoryItem.time >= mStartTimeMs) { - mItemIndex++; - mNextItemReady = true; - return; + + if (mItemIndex++ > mMaxHistoryItems) { + Slog.wtfStack(TAG, "Number of battery history items is too large: " + mItemIndex); + return false; } - } - mHistoryItem = null; - mNextItemReady = true; - close(); + mHistoryItem.currentTime = mBaseTimeUtc + (mHistoryItem.time - mBaseMonotonicTime); + return true; + } + return false; } private void readHistoryDelta(Parcel src, BatteryStats.HistoryItem cur) { diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java index e20a52b24485..3d81e4fc7acd 100644 --- a/core/java/com/android/internal/policy/DecorView.java +++ b/core/java/com/android/internal/policy/DecorView.java @@ -120,6 +120,7 @@ import com.android.internal.view.menu.MenuHelper; import com.android.internal.widget.ActionBarContextView; import com.android.internal.widget.BackgroundFallback; import com.android.internal.widget.floatingtoolbar.FloatingToolbar; +import com.android.window.flags.Flags; import java.util.List; import java.util.concurrent.Executor; @@ -1003,7 +1004,8 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind public void onWindowSystemUiVisibilityChanged(int visible) { updateColorViews(null /* insets */, true /* animate */); - if (mStatusGuard != null && mStatusGuard.getVisibility() == VISIBLE) { + if (!Flags.actionModeEdgeToEdge() + && mStatusGuard != null && mStatusGuard.getVisibility() == VISIBLE) { updateStatusGuardColor(); } } @@ -1040,7 +1042,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind } mFrameOffsets.set(insets.getSystemWindowInsetsAsRect()); insets = updateColorViews(insets, true /* animate */); - insets = updateStatusGuard(insets); + insets = updateActionModeInsets(insets); if (getForeground() != null) { drawableChanged(); } @@ -1592,7 +1594,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind } } - private WindowInsets updateStatusGuard(WindowInsets insets) { + private WindowInsets updateActionModeInsets(WindowInsets insets) { boolean showStatusGuard = false; // Show the status guard when the non-overlay contextual action bar is showing if (mPrimaryActionModeView != null) { @@ -1608,54 +1610,78 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind final Rect rect = mTempRect; // Apply the insets that have not been applied by the contentParent yet. - WindowInsets innerInsets = + final WindowInsets innerInsets = mWindow.mContentParent.computeSystemWindowInsets(insets, rect); - int newTopMargin = innerInsets.getSystemWindowInsetTop(); - int newLeftMargin = innerInsets.getSystemWindowInsetLeft(); - int newRightMargin = innerInsets.getSystemWindowInsetRight(); - - // Must use root window insets for the guard, because the color views consume - // the navigation bar inset if the window does not request LAYOUT_HIDE_NAV - but - // the status guard is attached at the root. - WindowInsets rootInsets = getRootWindowInsets(); - int newGuardLeftMargin = rootInsets.getSystemWindowInsetLeft(); - int newGuardRightMargin = rootInsets.getSystemWindowInsetRight(); - - if (mlp.topMargin != newTopMargin || mlp.leftMargin != newLeftMargin - || mlp.rightMargin != newRightMargin) { - mlpChanged = true; - mlp.topMargin = newTopMargin; - mlp.leftMargin = newLeftMargin; - mlp.rightMargin = newRightMargin; - } + final boolean consumesSystemWindowInsetsTop; + if (Flags.actionModeEdgeToEdge()) { + final Insets newPadding = innerInsets.getSystemWindowInsets(); + final Insets newMargin = innerInsets.getInsets( + WindowInsets.Type.navigationBars()); + + // Don't extend into navigation bar area so the width can align with status + // bar color view. + if (mlp.leftMargin != newMargin.left + || mlp.rightMargin != newMargin.right) { + mlpChanged = true; + mlp.leftMargin = newMargin.left; + mlp.rightMargin = newMargin.right; + } + + mPrimaryActionModeView.setPadding( + newPadding.left - newMargin.left, + newPadding.top, + newPadding.right - newMargin.right, + 0); + consumesSystemWindowInsetsTop = newPadding.top > 0; + } else { + int newTopMargin = innerInsets.getSystemWindowInsetTop(); + int newLeftMargin = innerInsets.getSystemWindowInsetLeft(); + int newRightMargin = innerInsets.getSystemWindowInsetRight(); + + // Must use root window insets for the guard, because the color views + // consume the navigation bar inset if the window does not request + // LAYOUT_HIDE_NAV - but the status guard is attached at the root. + WindowInsets rootInsets = getRootWindowInsets(); + int newGuardLeftMargin = rootInsets.getSystemWindowInsetLeft(); + int newGuardRightMargin = rootInsets.getSystemWindowInsetRight(); + + if (mlp.topMargin != newTopMargin || mlp.leftMargin != newLeftMargin + || mlp.rightMargin != newRightMargin) { + mlpChanged = true; + mlp.topMargin = newTopMargin; + mlp.leftMargin = newLeftMargin; + mlp.rightMargin = newRightMargin; + } - if (newTopMargin > 0 && mStatusGuard == null) { - mStatusGuard = new View(mContext); - mStatusGuard.setVisibility(GONE); - final LayoutParams lp = new LayoutParams(MATCH_PARENT, - mlp.topMargin, Gravity.LEFT | Gravity.TOP); - lp.leftMargin = newGuardLeftMargin; - lp.rightMargin = newGuardRightMargin; - addView(mStatusGuard, indexOfChild(mStatusColorViewState.view), lp); - } else if (mStatusGuard != null) { - final LayoutParams lp = (LayoutParams) - mStatusGuard.getLayoutParams(); - if (lp.height != mlp.topMargin || lp.leftMargin != newGuardLeftMargin - || lp.rightMargin != newGuardRightMargin) { - lp.height = mlp.topMargin; + if (newTopMargin > 0 && mStatusGuard == null) { + mStatusGuard = new View(mContext); + mStatusGuard.setVisibility(GONE); + final LayoutParams lp = new LayoutParams(MATCH_PARENT, + mlp.topMargin, Gravity.LEFT | Gravity.TOP); lp.leftMargin = newGuardLeftMargin; lp.rightMargin = newGuardRightMargin; - mStatusGuard.setLayoutParams(lp); + addView(mStatusGuard, indexOfChild(mStatusColorViewState.view), lp); + } else if (mStatusGuard != null) { + final LayoutParams lp = (LayoutParams) + mStatusGuard.getLayoutParams(); + if (lp.height != mlp.topMargin || lp.leftMargin != newGuardLeftMargin + || lp.rightMargin != newGuardRightMargin) { + lp.height = mlp.topMargin; + lp.leftMargin = newGuardLeftMargin; + lp.rightMargin = newGuardRightMargin; + mStatusGuard.setLayoutParams(lp); + } } - } - // The action mode's theme may differ from the app, so - // always show the status guard above it if we have one. - showStatusGuard = mStatusGuard != null; + // The action mode's theme may differ from the app, so + // always show the status guard above it if we have one. + showStatusGuard = mStatusGuard != null; - if (showStatusGuard && mStatusGuard.getVisibility() != VISIBLE) { - // If it wasn't previously shown, the color may be stale - updateStatusGuardColor(); + if (showStatusGuard && mStatusGuard.getVisibility() != VISIBLE) { + // If it wasn't previously shown, the color may be stale + updateStatusGuardColor(); + } + consumesSystemWindowInsetsTop = showStatusGuard; } // We only need to consume the insets if the action @@ -1664,14 +1690,16 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind // screen_simple_overlay_action_mode.xml). final boolean nonOverlay = (mWindow.getLocalFeaturesPrivate() & (1 << Window.FEATURE_ACTION_MODE_OVERLAY)) == 0; - if (nonOverlay && showStatusGuard) { + if (nonOverlay && consumesSystemWindowInsetsTop) { insets = insets.inset(0, insets.getSystemWindowInsetTop(), 0, 0); } } else { - // reset top margin - if (mlp.topMargin != 0 || mlp.leftMargin != 0 || mlp.rightMargin != 0) { - mlpChanged = true; - mlp.topMargin = 0; + if (!Flags.actionModeEdgeToEdge()) { + // reset top margin + if (mlp.topMargin != 0 || mlp.leftMargin != 0 || mlp.rightMargin != 0) { + mlpChanged = true; + mlp.topMargin = 0; + } } } if (mlpChanged) { @@ -1679,7 +1707,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind } } } - if (mStatusGuard != null) { + if (!Flags.actionModeEdgeToEdge() && mStatusGuard != null) { mStatusGuard.setVisibility(showStatusGuard ? VISIBLE : GONE); } return insets; @@ -2183,7 +2211,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind for (int i = getChildCount() - 1; i >= 0; i--) { View v = getChildAt(i); if (v != mStatusColorViewState.view && v != mNavigationColorViewState.view - && v != mStatusGuard) { + && (Flags.actionModeEdgeToEdge() || v != mStatusGuard)) { removeViewAt(i); } } diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java index 2d989943800e..6f7e5ad51b89 100644 --- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java +++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java @@ -79,6 +79,7 @@ import java.util.Objects; import java.util.Set; import java.util.TreeMap; import java.util.UUID; +import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; @@ -864,5 +865,24 @@ public abstract class PerfettoProtoLogImpl extends IProtoLogClient.Stub implemen throw new RuntimeException("Both mMessageString and mMessageHash should never be null"); } } + + /** + * This is only used by unit tests to wait until {@link #connectToConfigurationService} is + * done. Because unit tests are sensitive to concurrent accesses. + */ + @VisibleForTesting + public static void waitForInitialization() { + final IProtoLog currentInstance = ProtoLog.getSingleInstance(); + if (!(currentInstance instanceof PerfettoProtoLogImpl protoLog)) { + return; + } + try { + protoLog.mBackgroundLoggingService.submit(() -> { + Log.i(LOG_TAG, "Complete initialization"); + }).get(); + } catch (InterruptedException | ExecutionException e) { + Log.e(LOG_TAG, "Failed to wait for tracing service", e); + } + } } diff --git a/core/java/com/android/internal/widget/ActionBarContextView.java b/core/java/com/android/internal/widget/ActionBarContextView.java index 80fc218839d5..d5bb51187ba4 100644 --- a/core/java/com/android/internal/widget/ActionBarContextView.java +++ b/core/java/com/android/internal/widget/ActionBarContextView.java @@ -34,6 +34,7 @@ import android.widget.TextView; import com.android.internal.R; import com.android.internal.view.menu.MenuBuilder; +import com.android.window.flags.Flags; /** * @hide @@ -315,12 +316,14 @@ public class ActionBarContextView extends AbsActionBarView { final int contentWidth = MeasureSpec.getSize(widthMeasureSpec); - int maxHeight = mContentHeight > 0 ? - mContentHeight : MeasureSpec.getSize(heightMeasureSpec); + final int maxHeight = !Flags.actionModeEdgeToEdge() && mContentHeight > 0 + ? mContentHeight : MeasureSpec.getSize(heightMeasureSpec); final int verticalPadding = getPaddingTop() + getPaddingBottom(); int availableWidth = contentWidth - getPaddingLeft() - getPaddingRight(); - final int height = maxHeight - verticalPadding; + final int height = Flags.actionModeEdgeToEdge() + ? mContentHeight > 0 ? mContentHeight : maxHeight + : maxHeight - verticalPadding; final int childSpecHeight = MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST); if (mClose != null) { @@ -376,7 +379,8 @@ public class ActionBarContextView extends AbsActionBarView { } setMeasuredDimension(contentWidth, measuredHeight); } else { - setMeasuredDimension(contentWidth, maxHeight); + setMeasuredDimension(contentWidth, Flags.actionModeEdgeToEdge() + ? mContentHeight + verticalPadding : maxHeight); } } diff --git a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java index ff57fd4fe2ce..362b79db4003 100644 --- a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java +++ b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java @@ -294,54 +294,24 @@ public class ActionBarOverlayLayout extends ViewGroup implements DecorContentPar } } - private boolean applyInsets(View view, Rect insets, boolean toPadding, - boolean left, boolean top, boolean right, boolean bottom) { - boolean changed; - if (toPadding) { - changed = setMargin(view, EMPTY_RECT, left, top, right, bottom); - changed |= setPadding(view, insets, left, top, right, bottom); - } else { - changed = setPadding(view, EMPTY_RECT, left, top, right, bottom); - changed |= setMargin(view, insets, left, top, right, bottom); - } - return changed; - } - - private boolean setPadding(View view, Rect insets, - boolean left, boolean top, boolean right, boolean bottom) { - if ((left && view.getPaddingLeft() != insets.left) - || (top && view.getPaddingTop() != insets.top) - || (right && view.getPaddingRight() != insets.right) - || (bottom && view.getPaddingBottom() != insets.bottom)) { - view.setPadding( - left ? insets.left : view.getPaddingLeft(), - top ? insets.top : view.getPaddingTop(), - right ? insets.right : view.getPaddingRight(), - bottom ? insets.bottom : view.getPaddingBottom()); - return true; - } - return false; - } - - private boolean setMargin(View view, Rect insets, - boolean left, boolean top, boolean right, boolean bottom) { + private boolean setMargin(View view, int left, int top, int right, int bottom) { final LayoutParams lp = (LayoutParams) view.getLayoutParams(); boolean changed = false; - if (left && lp.leftMargin != insets.left) { + if (lp.leftMargin != left) { changed = true; - lp.leftMargin = insets.left; + lp.leftMargin = left; } - if (top && lp.topMargin != insets.top) { + if (lp.topMargin != top) { changed = true; - lp.topMargin = insets.top; + lp.topMargin = top; } - if (right && lp.rightMargin != insets.right) { + if (lp.rightMargin != right) { changed = true; - lp.rightMargin = insets.right; + lp.rightMargin = right; } - if (bottom && lp.bottomMargin != insets.bottom) { + if (lp.bottomMargin != bottom) { changed = true; - lp.bottomMargin = insets.bottom; + lp.bottomMargin = bottom; } return changed; } @@ -367,12 +337,30 @@ public class ActionBarOverlayLayout extends ViewGroup implements DecorContentPar final Insets sysInsets = insets.getSystemWindowInsets(); mSystemInsets.set(sysInsets.left, sysInsets.top, sysInsets.right, sysInsets.bottom); - // The top and bottom action bars are always within the content area. - boolean changed = applyInsets(mActionBarTop, mSystemInsets, - mActionBarExtendsIntoSystemInsets, true, true, true, false); - if (mActionBarBottom != null) { - changed |= applyInsets(mActionBarBottom, mSystemInsets, - mActionBarExtendsIntoSystemInsets, true, false, true, true); + boolean changed = false; + if (mActionBarExtendsIntoSystemInsets) { + // Don't extend into navigation bar area so the width can align with status bar + // color view. + final Insets navBarInsets = insets.getInsets(WindowInsets.Type.navigationBars()); + final int paddingLeft = sysInsets.left - navBarInsets.left; + final int paddingRight = sysInsets.right - navBarInsets.right; + mActionBarTop.setPadding(paddingLeft, sysInsets.top, paddingRight, 0); + changed |= setMargin( + mActionBarTop, navBarInsets.left, 0, navBarInsets.right, 0); + if (mActionBarBottom != null) { + mActionBarBottom.setPadding(paddingLeft, 0, paddingRight, sysInsets.bottom); + changed |= setMargin( + mActionBarBottom, navBarInsets.left, 0, navBarInsets.right, 0); + } + } else { + mActionBarTop.setPadding(0, 0, 0, 0); + changed |= setMargin( + mActionBarTop, sysInsets.left, sysInsets.top, sysInsets.right, 0); + if (mActionBarBottom != null) { + mActionBarBottom.setPadding(0, 0, 0, 0); + changed |= setMargin( + mActionBarTop, sysInsets.left, 0, sysInsets.right, sysInsets.bottom); + } } // Cannot use the result of computeSystemWindowInsets, because that consumes the @@ -521,7 +509,12 @@ public class ActionBarOverlayLayout extends ViewGroup implements DecorContentPar ); } } - setMargin(mContent, mContentInsets, true, true, true, true); + setMargin( + mContent, + mContentInsets.left, + mContentInsets.top, + mContentInsets.right, + mContentInsets.bottom); if (!mLastInnerInsets.equals(mInnerInsets)) { // If the inner insets have changed, we need to dispatch this down to diff --git a/core/java/com/android/internal/widget/NotificationExpandButton.java b/core/java/com/android/internal/widget/NotificationExpandButton.java index dd12f69a56fb..42b1bdbc51b2 100644 --- a/core/java/com/android/internal/widget/NotificationExpandButton.java +++ b/core/java/com/android/internal/widget/NotificationExpandButton.java @@ -129,6 +129,16 @@ public class NotificationExpandButton extends FrameLayout { updateExpandedState(); } + /** + * Adjust the padding at the start of the view based on the layout direction (RTL/LTR). + * This is needed because RemoteViews don't have an equivalent for + * {@link this#setPaddingRelative}. + */ + @RemotableViewMethod + public void setStartPadding(int startPadding) { + setPaddingRelative(startPadding, getPaddingTop(), getPaddingEnd(), getPaddingBottom()); + } + private void updateExpandedState() { int drawableId; int contentDescriptionId; diff --git a/core/jni/android_database_SQLiteConnection.cpp b/core/jni/android_database_SQLiteConnection.cpp index 36c08a51c66a..95bab542f9d6 100644 --- a/core/jni/android_database_SQLiteConnection.cpp +++ b/core/jni/android_database_SQLiteConnection.cpp @@ -231,12 +231,6 @@ static void nativeClose(JNIEnv* env, jclass clazz, jlong connectionPtr, jboolean } } -// This method is deprecated and should be removed when it is no longer needed by the -// robolectric tests. -static void nativeClose(JNIEnv* env, jclass clazz, jlong connectionPtr) { - nativeClose(env, clazz, connectionPtr, false); -} - static void sqliteCustomScalarFunctionCallback(sqlite3_context *context, int argc, sqlite3_value **argv) { JNIEnv* env = AndroidRuntime::getJNIEnv(); @@ -974,8 +968,6 @@ static const JNINativeMethod sMethods[] = (void*)nativeOpen }, { "nativeClose", "(JZ)V", (void*) static_cast<void(*)(JNIEnv*,jclass,jlong,jboolean)>(nativeClose) }, - { "nativeClose", "(J)V", - (void*) static_cast<void(*)(JNIEnv*,jclass,jlong)>(nativeClose) }, { "nativeRegisterCustomScalarFunction", "(JLjava/lang/String;Ljava/util/function/UnaryOperator;)V", (void*)nativeRegisterCustomScalarFunction }, { "nativeRegisterCustomAggregateFunction", "(JLjava/lang/String;Ljava/util/function/BinaryOperator;)V", diff --git a/core/jni/com_android_internal_content_FileSystemUtils.cpp b/core/jni/com_android_internal_content_FileSystemUtils.cpp index 48c92c87f54e..886b9f1ae658 100644 --- a/core/jni/com_android_internal_content_FileSystemUtils.cpp +++ b/core/jni/com_android_internal_content_FileSystemUtils.cpp @@ -201,8 +201,8 @@ bool punchHoles(const char *filePath, const uint64_t offset, return true; } -bool getLoadSegmentPhdrs(const char *filePath, const uint64_t offset, - std::vector<Elf64_Phdr> &programHeaders) { +read_elf_status_t getLoadSegmentPhdrs(const char *filePath, const uint64_t offset, + std::vector<Elf64_Phdr> &programHeaders) { // Open Elf file Elf64_Ehdr ehdr; std::ifstream inputStream(filePath, std::ifstream::in); @@ -212,13 +212,13 @@ bool getLoadSegmentPhdrs(const char *filePath, const uint64_t offset, // read executable headers inputStream.read((char *)&ehdr, sizeof(ehdr)); if (!inputStream.good()) { - return false; + return ELF_READ_ERROR; } - // only consider elf64 for punching holes + // only consider ELF64 files if (ehdr.e_ident[EI_CLASS] != ELFCLASS64) { ALOGW("Provided file is not ELF64"); - return false; + return ELF_IS_NOT_64_BIT; } // read the program headers from elf file @@ -229,7 +229,7 @@ bool getLoadSegmentPhdrs(const char *filePath, const uint64_t offset, uint64_t phOffset; if (__builtin_add_overflow(offset, programHeaderOffset, &phOffset)) { ALOGE("Overflow occurred when calculating phOffset"); - return false; + return ELF_READ_ERROR; } inputStream.seekg(phOffset); @@ -237,7 +237,7 @@ bool getLoadSegmentPhdrs(const char *filePath, const uint64_t offset, Elf64_Phdr header; inputStream.read((char *)&header, sizeof(header)); if (!inputStream.good()) { - return false; + return ELF_READ_ERROR; } if (header.p_type != PT_LOAD) { @@ -246,13 +246,14 @@ bool getLoadSegmentPhdrs(const char *filePath, const uint64_t offset, programHeaders.push_back(header); } - return true; + return ELF_READ_OK; } bool punchHolesInElf64(const char *filePath, const uint64_t offset) { std::vector<Elf64_Phdr> programHeaders; - if (!getLoadSegmentPhdrs(filePath, offset, programHeaders)) { - ALOGE("Failed to read program headers from ELF file."); + read_elf_status_t status = getLoadSegmentPhdrs(filePath, offset, programHeaders); + if (status != ELF_READ_OK) { + ALOGE("Failed to read program headers from 64 bit ELF file."); return false; } return punchHoles(filePath, offset, programHeaders); diff --git a/core/jni/com_android_internal_content_FileSystemUtils.h b/core/jni/com_android_internal_content_FileSystemUtils.h index 4a95686c5a0c..c4dc1115e6c4 100644 --- a/core/jni/com_android_internal_content_FileSystemUtils.h +++ b/core/jni/com_android_internal_content_FileSystemUtils.h @@ -22,6 +22,12 @@ namespace android { +enum read_elf_status_t { + ELF_IS_NOT_64_BIT = -2, + ELF_READ_ERROR = -1, + ELF_READ_OK = 0, +}; + /* * This function deallocates space used by zero padding at the end of LOAD segments in given * uncompressed ELF file. Read ELF headers and find out the offset and sizes of LOAD segments. @@ -39,10 +45,10 @@ bool punchHolesInElf64(const char* filePath, uint64_t offset); bool punchHolesInZip(const char* filePath, uint64_t offset, uint16_t extraFieldLen); /* - * This function reads program headers from ELF file. ELF can be specified with file path directly - * or it should be at offset inside Apk. Program headers passed to function is populated. + * This function reads program headers from 64 bit ELF file. ELF can be specified with file path + * directly or it should be at offset inside Apk. Program headers passed to function is populated. */ -bool getLoadSegmentPhdrs(const char* filePath, const uint64_t offset, - std::vector<Elf64_Phdr>& programHeaders); +read_elf_status_t getLoadSegmentPhdrs(const char* filePath, const uint64_t offset, + std::vector<Elf64_Phdr>& programHeaders); } // namespace android
\ No newline at end of file diff --git a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp index 14132e61ff0e..7cf523f18a90 100644 --- a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp +++ b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp @@ -640,7 +640,17 @@ com_android_internal_content_NativeLibraryHelper_openApkFd(JNIEnv *env, jclass, static jint checkLoadSegmentAlignment(const char* fileName, off64_t offset) { std::vector<Elf64_Phdr> programHeaders; - if (!getLoadSegmentPhdrs(fileName, offset, programHeaders)) { + read_elf_status_t status = getLoadSegmentPhdrs(fileName, offset, programHeaders); + // Ignore the ELFs which are not 64 bit. + if (status == ELF_IS_NOT_64_BIT) { + ALOGW("ELF file is not 64 Bit"); + // PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED is equivalent of skipping the current file. + // on return, flag is OR'ed with flags from other ELF files. If some app has 32 bit ELF in + // 64 bit directory, alignment of that ELF will be ignored. + return PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED; + } + + if (status == ELF_READ_ERROR) { ALOGE("Failed to read program headers from ELF file."); return PAGE_SIZE_APP_COMPAT_FLAG_ERROR; } diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto index ac4bac6d206e..1caa9e7af348 100644 --- a/core/proto/android/providers/settings/secure.proto +++ b/core/proto/android/providers/settings/secure.proto @@ -112,7 +112,8 @@ message SecureSettingsProto { optional SettingProto autoclick_ignore_minor_cursor_movement = 63 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto autoclick_panel_position = 64 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto autoclick_revert_to_left_click = 65 [ (android.privacy).dest = DEST_AUTOMATIC ]; - + // Setting for accessibility magnification for cursor following mode. + optional SettingProto accessibility_magnification_cursor_following_mode = 66 [ (android.privacy).dest = DEST_AUTOMATIC ]; } optional Accessibility accessibility = 2; diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 36b65ba43162..e16ce9849ff2 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -9292,9 +9292,6 @@ <action android:name="android.intent.action.UPDATE_PINS" /> <data android:scheme="content" android:host="*" android:mimeType="*/*" /> </intent-filter> - <intent-filter> - <action android:name="android.intent.action.BOOT_COMPLETED" /> - </intent-filter> </receiver> <receiver android:name="com.android.server.updates.IntentFirewallInstallReceiver" diff --git a/core/res/res/drawable/accessibility_autoclick_scroll_down.xml b/core/res/res/drawable/accessibility_autoclick_scroll_down.xml new file mode 100644 index 000000000000..13f1ba0dafe8 --- /dev/null +++ b/core/res/res/drawable/accessibility_autoclick_scroll_down.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright 2025 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:fillColor="@color/materialColorPrimary" + android:pathData="M12,20l8,-8h-5V4h-6v8H4z"/> +</vector> diff --git a/core/res/res/drawable/accessibility_autoclick_scroll_exit.xml b/core/res/res/drawable/accessibility_autoclick_scroll_exit.xml new file mode 100644 index 000000000000..e53301f25d65 --- /dev/null +++ b/core/res/res/drawable/accessibility_autoclick_scroll_exit.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright 2025 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:fillColor="@color/materialColorPrimary" + android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/> +</vector> diff --git a/core/res/res/drawable/accessibility_autoclick_scroll_left.xml b/core/res/res/drawable/accessibility_autoclick_scroll_left.xml new file mode 100644 index 000000000000..39475bc689d2 --- /dev/null +++ b/core/res/res/drawable/accessibility_autoclick_scroll_left.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright 2025 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:fillColor="@color/materialColorPrimary" + android:pathData="M4,12l8,8v-5h8v-6h-8V4z"/> +</vector> diff --git a/core/res/res/drawable/accessibility_autoclick_scroll_right.xml b/core/res/res/drawable/accessibility_autoclick_scroll_right.xml new file mode 100644 index 000000000000..bbd7b2a79459 --- /dev/null +++ b/core/res/res/drawable/accessibility_autoclick_scroll_right.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright 2025 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:fillColor="@color/materialColorPrimary" + android:pathData="M20,12l-8,-8v5H4v6h8v5z"/> +</vector> diff --git a/core/res/res/drawable/accessibility_autoclick_scroll_up.xml b/core/res/res/drawable/accessibility_autoclick_scroll_up.xml new file mode 100644 index 000000000000..2e2c245effa1 --- /dev/null +++ b/core/res/res/drawable/accessibility_autoclick_scroll_up.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright 2025 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:fillColor="@color/materialColorPrimary" + android:pathData="M12,4L4,12h5v8h6v-8h5z"/> +</vector> diff --git a/core/res/res/layout/accessibility_autoclick_scroll_panel.xml b/core/res/res/layout/accessibility_autoclick_scroll_panel.xml new file mode 100644 index 000000000000..1e093bbc7c35 --- /dev/null +++ b/core/res/res/layout/accessibility_autoclick_scroll_panel.xml @@ -0,0 +1,92 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright 2025 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/accessibility_autoclick_scroll_panel" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal" + android:background="@drawable/accessibility_autoclick_type_panel_rounded_background" + android:orientation="vertical" + android:padding="16dp"> + + <!-- Up arrow --> + <LinearLayout + android:id="@+id/scroll_up_layout" + style="@style/AccessibilityAutoclickScrollPanelButtonLayoutStyle" + android:layout_gravity="center_horizontal" + android:layout_marginBottom="@dimen/accessibility_autoclick_type_panel_button_spacing"> + <ImageButton + android:id="@+id/scroll_up" + style="@style/AccessibilityAutoclickScrollPanelImageButtonStyle" + android:contentDescription="@string/accessibility_autoclick_scroll_up" + android:src="@drawable/accessibility_autoclick_scroll_up" /> + </LinearLayout> + + <!-- Middle row: Left, Exit, Right --> + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="center" + android:orientation="horizontal"> + + <LinearLayout + android:id="@+id/scroll_left_layout" + style="@style/AccessibilityAutoclickScrollPanelButtonLayoutStyle" + android:layout_marginEnd="@dimen/accessibility_autoclick_type_panel_button_spacing"> + <ImageButton + android:id="@+id/scroll_left" + style="@style/AccessibilityAutoclickScrollPanelImageButtonStyle" + android:contentDescription="@string/accessibility_autoclick_scroll_left" + android:src="@drawable/accessibility_autoclick_scroll_left" /> + </LinearLayout> + + <LinearLayout + android:id="@+id/scroll_exit_layout" + style="@style/AccessibilityAutoclickScrollPanelButtonLayoutStyle" + android:layout_marginEnd="@dimen/accessibility_autoclick_type_panel_button_spacing"> + <ImageButton + android:id="@+id/scroll_exit" + style="@style/AccessibilityAutoclickScrollPanelImageButtonStyle" + android:contentDescription="@string/accessibility_autoclick_scroll_exit" + android:src="@drawable/ic_close" /> + </LinearLayout> + + <LinearLayout + android:id="@+id/scroll_right_layout" + style="@style/AccessibilityAutoclickScrollPanelButtonLayoutStyle"> + <ImageButton + android:id="@+id/scroll_right" + style="@style/AccessibilityAutoclickScrollPanelImageButtonStyle" + android:contentDescription="@string/accessibility_autoclick_scroll_right" + android:src="@drawable/accessibility_autoclick_scroll_right" /> + </LinearLayout> + </LinearLayout> + + <!-- Down arrow --> + <LinearLayout + android:id="@+id/scroll_down_layout" + style="@style/AccessibilityAutoclickScrollPanelButtonLayoutStyle" + android:layout_gravity="center_horizontal" + android:layout_marginTop="@dimen/accessibility_autoclick_type_panel_button_spacing"> + <ImageButton + android:id="@+id/scroll_down" + style="@style/AccessibilityAutoclickScrollPanelImageButtonStyle" + android:contentDescription="@string/accessibility_autoclick_scroll_down" + android:src="@drawable/accessibility_autoclick_scroll_down" /> + </LinearLayout> + +</LinearLayout> diff --git a/core/res/res/layout/notification_2025_expand_button.xml b/core/res/res/layout/notification_2025_expand_button.xml index 1c367544c90a..8ba844a4868b 100644 --- a/core/res/res/layout/notification_2025_expand_button.xml +++ b/core/res/res/layout/notification_2025_expand_button.xml @@ -15,6 +15,7 @@ --> <!-- extends FrameLayout --> +<!-- Note: The button's padding may be dynamically adjusted in code --> <com.android.internal.widget.NotificationExpandButton xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/expand_button" diff --git a/core/res/res/layout/notification_2025_right_icon.xml b/core/res/res/layout/notification_2025_right_icon.xml new file mode 100644 index 000000000000..24d381d10501 --- /dev/null +++ b/core/res/res/layout/notification_2025_right_icon.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<!-- Large icon to be used in expanded notification layouts. --> +<com.android.internal.widget.CachingIconView + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/right_icon" + android:layout_width="@dimen/notification_right_icon_size" + android:layout_height="@dimen/notification_right_icon_size" + android:layout_gravity="top|end" + android:layout_marginEnd="@dimen/notification_2025_right_icon_expanded_margin_end" + android:layout_marginVertical="@dimen/notification_2025_right_icon_vertical_margin" + android:background="@drawable/notification_large_icon_outline" + android:clipToOutline="true" + android:importantForAccessibility="no" + android:scaleType="centerCrop" + android:maxDrawableWidth="@dimen/notification_right_icon_size" + android:maxDrawableHeight="@dimen/notification_right_icon_size" + /> diff --git a/core/res/res/layout/notification_2025_template_collapsed_base.xml b/core/res/res/layout/notification_2025_template_collapsed_base.xml index d29b7af9e24e..63f32e3b3cd2 100644 --- a/core/res/res/layout/notification_2025_template_collapsed_base.xml +++ b/core/res/res/layout/notification_2025_template_collapsed_base.xml @@ -66,14 +66,17 @@ android:orientation="horizontal" > + <!-- + We use a smaller vertical margin than usual, to allow the content of custom views to + grow up to 48dp height when needed in collapsed notifications. + --> <LinearLayout android:id="@+id/notification_headerless_view_column" android:layout_width="0px" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_weight="1" - android:layout_marginBottom="@dimen/notification_2025_margin" - android:layout_marginTop="@dimen/notification_2025_margin" + android:layout_marginVertical="@dimen/notification_2025_reduced_margin" android:orientation="vertical" > @@ -81,6 +84,7 @@ android:id="@+id/notification_top_line" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_marginTop="@dimen/notification_2025_additional_margin" android:minHeight="@dimen/notification_2025_content_min_height" android:clipChildren="false" android:theme="@style/Theme.DeviceDefault.Notification" @@ -118,17 +122,10 @@ <com.android.internal.widget.NotificationVanishingFrameLayout android:layout_width="match_parent" android:layout_height="wrap_content" + android:layout_marginBottom="@dimen/notification_2025_additional_margin" android:minHeight="@dimen/notification_headerless_line_height" > - <!-- This is the simplest way to keep this text vertically centered without - gravity="center_vertical" which causes jumpiness in expansion animations. --> - <include - layout="@layout/notification_2025_text" - android:layout_width="match_parent" - android:layout_height="@dimen/notification_text_height" - android:layout_gravity="center_vertical" - android:layout_marginTop="0dp" - /> + <include layout="@layout/notification_2025_text" /> </com.android.internal.widget.NotificationVanishingFrameLayout> <include @@ -146,9 +143,8 @@ android:layout_width="@dimen/notification_right_icon_size" android:layout_height="@dimen/notification_right_icon_size" android:layout_gravity="center_vertical|end" - android:layout_marginTop="@dimen/notification_right_icon_headerless_margin" - android:layout_marginBottom="@dimen/notification_right_icon_headerless_margin" - android:layout_marginStart="@dimen/notification_right_icon_content_margin" + android:layout_marginVertical="@dimen/notification_2025_right_icon_vertical_margin" + android:layout_marginStart="@dimen/notification_2025_right_icon_content_margin" android:background="@drawable/notification_large_icon_outline" android:clipToOutline="true" android:importantForAccessibility="no" diff --git a/core/res/res/layout/notification_2025_template_collapsed_call.xml b/core/res/res/layout/notification_2025_template_collapsed_call.xml index ee691e4d6894..fbea10d42b2b 100644 --- a/core/res/res/layout/notification_2025_template_collapsed_call.xml +++ b/core/res/res/layout/notification_2025_template_collapsed_call.xml @@ -128,15 +128,7 @@ android:layout_height="wrap_content" android:minHeight="@dimen/notification_headerless_line_height" > - <!-- This is the simplest way to keep this text vertically centered without - gravity="center_vertical" which causes jumpiness in expansion animations. --> - <include - layout="@layout/notification_2025_text" - android:layout_width="match_parent" - android:layout_height="@dimen/notification_text_height" - android:layout_gravity="center_vertical" - android:layout_marginTop="0dp" - /> + <include layout="@layout/notification_2025_text" /> </com.android.internal.widget.NotificationVanishingFrameLayout> </LinearLayout> @@ -147,9 +139,8 @@ android:layout_width="@dimen/notification_right_icon_size" android:layout_height="@dimen/notification_right_icon_size" android:layout_gravity="center_vertical|end" - android:layout_marginTop="@dimen/notification_right_icon_headerless_margin" - android:layout_marginBottom="@dimen/notification_right_icon_headerless_margin" - android:layout_marginStart="@dimen/notification_right_icon_content_margin" + android:layout_marginVertical="@dimen/notification_2025_right_icon_vertical_margin" + android:layout_marginStart="@dimen/notification_2025_right_icon_content_margin" android:background="@drawable/notification_large_icon_outline" android:clipToOutline="true" android:importantForAccessibility="no" diff --git a/core/res/res/layout/notification_2025_template_collapsed_conversation.xml b/core/res/res/layout/notification_2025_template_collapsed_conversation.xml index f80411103501..a6fdcd95399e 100644 --- a/core/res/res/layout/notification_2025_template_collapsed_conversation.xml +++ b/core/res/res/layout/notification_2025_template_collapsed_conversation.xml @@ -149,9 +149,9 @@ android:layout_width="@dimen/notification_right_icon_size" android:layout_height="@dimen/notification_right_icon_size" android:layout_gravity="center_vertical|end" - android:layout_marginTop="@dimen/notification_right_icon_headerless_margin" - android:layout_marginBottom="@dimen/notification_right_icon_headerless_margin" - android:layout_marginStart="@dimen/notification_right_icon_content_margin" + android:layout_marginTop="@dimen/notification_2025_margin" + android:layout_marginBottom="@dimen/notification_2025_margin" + android:layout_marginStart="@dimen/notification_2025_right_icon_content_margin" android:forceHasOverlappingRendering="false" android:spacing="0dp" android:clipChildren="false" @@ -163,9 +163,8 @@ android:layout_width="@dimen/notification_right_icon_size" android:layout_height="@dimen/notification_right_icon_size" android:layout_gravity="center_vertical|end" - android:layout_marginTop="@dimen/notification_right_icon_headerless_margin" - android:layout_marginBottom="@dimen/notification_right_icon_headerless_margin" - android:layout_marginStart="@dimen/notification_right_icon_content_margin" + android:layout_marginVertical="@dimen/notification_2025_right_icon_vertical_margin" + android:layout_marginStart="@dimen/notification_2025_right_icon_content_margin" android:background="@drawable/notification_large_icon_outline" android:clipToOutline="true" android:importantForAccessibility="no" diff --git a/core/res/res/layout/notification_2025_template_collapsed_media.xml b/core/res/res/layout/notification_2025_template_collapsed_media.xml index 5beab508aecf..629af77b3dda 100644 --- a/core/res/res/layout/notification_2025_template_collapsed_media.xml +++ b/core/res/res/layout/notification_2025_template_collapsed_media.xml @@ -68,14 +68,17 @@ android:orientation="horizontal" > + <!-- + We use a smaller vertical margin than usual, to allow the content of custom views to + grow up to 48dp height when needed in collapsed notifications. + --> <LinearLayout android:id="@+id/notification_headerless_view_column" android:layout_width="0px" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_weight="1" - android:layout_marginBottom="@dimen/notification_2025_margin" - android:layout_marginTop="@dimen/notification_2025_margin" + android:layout_marginVertical="@dimen/notification_2025_reduced_margin" android:orientation="vertical" > @@ -83,6 +86,7 @@ android:id="@+id/notification_top_line" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_marginTop="@dimen/notification_2025_additional_margin" android:minHeight="@dimen/notification_headerless_line_height" android:clipChildren="false" android:theme="@style/Theme.DeviceDefault.Notification" @@ -119,17 +123,10 @@ <com.android.internal.widget.NotificationVanishingFrameLayout android:layout_width="match_parent" + android:layout_marginBottom="@dimen/notification_2025_additional_margin" android:layout_height="@dimen/notification_headerless_line_height" > - <!-- This is the simplest way to keep this text vertically centered without - gravity="center_vertical" which causes jumpiness in expansion animations. --> - <include - layout="@layout/notification_template_text" - android:layout_width="match_parent" - android:layout_height="@dimen/notification_text_height" - android:layout_gravity="center_vertical" - android:layout_marginTop="0dp" - /> + <include layout="@layout/notification_2025_text" /> </com.android.internal.widget.NotificationVanishingFrameLayout> <include @@ -147,9 +144,8 @@ android:layout_width="@dimen/notification_right_icon_size" android:layout_height="@dimen/notification_right_icon_size" android:layout_gravity="center_vertical|end" - android:layout_marginTop="@dimen/notification_right_icon_headerless_margin" - android:layout_marginBottom="@dimen/notification_right_icon_headerless_margin" - android:layout_marginStart="@dimen/notification_right_icon_content_margin" + android:layout_marginVertical="@dimen/notification_2025_right_icon_vertical_margin" + android:layout_marginStart="@dimen/notification_2025_right_icon_content_margin" android:background="@drawable/notification_large_icon_outline" android:clipToOutline="true" android:importantForAccessibility="no" diff --git a/core/res/res/layout/notification_2025_template_collapsed_messaging.xml b/core/res/res/layout/notification_2025_template_collapsed_messaging.xml index d7c3263904d4..3716fa6825b3 100644 --- a/core/res/res/layout/notification_2025_template_collapsed_messaging.xml +++ b/core/res/res/layout/notification_2025_template_collapsed_messaging.xml @@ -159,9 +159,9 @@ android:layout_width="@dimen/notification_right_icon_size" android:layout_height="@dimen/notification_right_icon_size" android:layout_gravity="center_vertical|end" - android:layout_marginTop="@dimen/notification_right_icon_headerless_margin" - android:layout_marginBottom="@dimen/notification_right_icon_headerless_margin" - android:layout_marginStart="@dimen/notification_right_icon_content_margin" + android:layout_marginTop="@dimen/notification_2025_margin" + android:layout_marginBottom="@dimen/notification_2025_margin" + android:layout_marginStart="@dimen/notification_2025_right_icon_content_margin" android:forceHasOverlappingRendering="false" android:spacing="0dp" android:clipChildren="false" @@ -173,9 +173,8 @@ android:layout_width="@dimen/notification_right_icon_size" android:layout_height="@dimen/notification_right_icon_size" android:layout_gravity="center_vertical|end" - android:layout_marginTop="@dimen/notification_right_icon_headerless_margin" - android:layout_marginBottom="@dimen/notification_right_icon_headerless_margin" - android:layout_marginStart="@dimen/notification_right_icon_content_margin" + android:layout_marginVertical="@dimen/notification_2025_right_icon_vertical_margin" + android:layout_marginStart="@dimen/notification_2025_right_icon_content_margin" android:background="@drawable/notification_large_icon_outline" android:clipToOutline="true" android:importantForAccessibility="no" diff --git a/core/res/res/layout/notification_2025_template_expanded_base.xml b/core/res/res/layout/notification_2025_template_expanded_base.xml index e12db2783191..8d99e47c5386 100644 --- a/core/res/res/layout/notification_2025_template_expanded_base.xml +++ b/core/res/res/layout/notification_2025_template_expanded_base.xml @@ -63,7 +63,7 @@ /> </LinearLayout> - <include layout="@layout/notification_template_right_icon" /> + <include layout="@layout/notification_2025_right_icon" /> </FrameLayout> <ViewStub diff --git a/core/res/res/layout/notification_2025_template_expanded_big_picture.xml b/core/res/res/layout/notification_2025_template_expanded_big_picture.xml index fac9d1c47f41..e8e460d1b4ae 100644 --- a/core/res/res/layout/notification_2025_template_expanded_big_picture.xml +++ b/core/res/res/layout/notification_2025_template_expanded_big_picture.xml @@ -25,7 +25,7 @@ <include layout="@layout/notification_2025_template_header" /> - <include layout="@layout/notification_template_right_icon" /> + <include layout="@layout/notification_2025_right_icon" /> <LinearLayout android:id="@+id/notification_action_list_margin_target" diff --git a/core/res/res/layout/notification_2025_template_expanded_big_text.xml b/core/res/res/layout/notification_2025_template_expanded_big_text.xml index 4a807cb674c6..b68db7f0a638 100644 --- a/core/res/res/layout/notification_2025_template_expanded_big_text.xml +++ b/core/res/res/layout/notification_2025_template_expanded_big_text.xml @@ -91,5 +91,5 @@ <include layout="@layout/notification_material_action_list" /> </com.android.internal.widget.RemeasuringLinearLayout> - <include layout="@layout/notification_template_right_icon" /> + <include layout="@layout/notification_2025_right_icon" /> </FrameLayout> diff --git a/core/res/res/layout/notification_2025_template_expanded_call.xml b/core/res/res/layout/notification_2025_template_expanded_call.xml index bbc29664d594..7b45b55ba15b 100644 --- a/core/res/res/layout/notification_2025_template_expanded_call.xml +++ b/core/res/res/layout/notification_2025_template_expanded_call.xml @@ -68,6 +68,6 @@ </com.android.internal.widget.RemeasuringLinearLayout> - <include layout="@layout/notification_template_right_icon" /> + <include layout="@layout/notification_2025_right_icon" /> </com.android.internal.widget.CallLayout> diff --git a/core/res/res/layout/notification_2025_template_expanded_conversation.xml b/core/res/res/layout/notification_2025_template_expanded_conversation.xml index d7e8bb3b6da2..592785d53018 100644 --- a/core/res/res/layout/notification_2025_template_expanded_conversation.xml +++ b/core/res/res/layout/notification_2025_template_expanded_conversation.xml @@ -70,6 +70,6 @@ </com.android.internal.widget.RemeasuringLinearLayout> - <include layout="@layout/notification_template_right_icon" /> + <include layout="@layout/notification_2025_right_icon" /> </com.android.internal.widget.ConversationLayout> diff --git a/core/res/res/layout/notification_2025_template_expanded_inbox.xml b/core/res/res/layout/notification_2025_template_expanded_inbox.xml index ccab02e312cc..6459e1eab862 100644 --- a/core/res/res/layout/notification_2025_template_expanded_inbox.xml +++ b/core/res/res/layout/notification_2025_template_expanded_inbox.xml @@ -130,5 +130,5 @@ android:layout_marginTop="@dimen/notification_content_margin" /> <include layout="@layout/notification_material_action_list" /> </LinearLayout> - <include layout="@layout/notification_template_right_icon" /> + <include layout="@layout/notification_2025_right_icon" /> </FrameLayout> diff --git a/core/res/res/layout/notification_2025_template_expanded_media.xml b/core/res/res/layout/notification_2025_template_expanded_media.xml index e90ab792581f..801e339b3a92 100644 --- a/core/res/res/layout/notification_2025_template_expanded_media.xml +++ b/core/res/res/layout/notification_2025_template_expanded_media.xml @@ -99,6 +99,6 @@ </LinearLayout> - <include layout="@layout/notification_template_right_icon" /> + <include layout="@layout/notification_2025_right_icon" /> </com.android.internal.widget.MediaNotificationView> diff --git a/core/res/res/layout/notification_2025_template_expanded_messaging.xml b/core/res/res/layout/notification_2025_template_expanded_messaging.xml index 20abfee6a4b6..82c71527a291 100644 --- a/core/res/res/layout/notification_2025_template_expanded_messaging.xml +++ b/core/res/res/layout/notification_2025_template_expanded_messaging.xml @@ -70,6 +70,6 @@ </com.android.internal.widget.RemeasuringLinearLayout> - <include layout="@layout/notification_template_right_icon" /> + <include layout="@layout/notification_2025_right_icon" /> </com.android.internal.widget.MessagingLayout> diff --git a/core/res/res/layout/notification_2025_template_expanded_progress.xml b/core/res/res/layout/notification_2025_template_expanded_progress.xml index 87ded8975cb0..2ff252747fd2 100644 --- a/core/res/res/layout/notification_2025_template_expanded_progress.xml +++ b/core/res/res/layout/notification_2025_template_expanded_progress.xml @@ -99,7 +99,7 @@ </LinearLayout> </LinearLayout> - <include layout="@layout/notification_template_right_icon" /> + <include layout="@layout/notification_2025_right_icon" /> </FrameLayout> <ViewStub diff --git a/core/res/res/layout/notification_2025_text.xml b/core/res/res/layout/notification_2025_text.xml index 474f6d2099c6..a725de44b0bf 100644 --- a/core/res/res/layout/notification_2025_text.xml +++ b/core/res/res/layout/notification_2025_text.xml @@ -13,14 +13,16 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License --> +<!-- Note that we prefer layout_gravity="center_vertical" over gravity="center_vertical", since the + latter can cause jumpiness in expansion animations. --> <com.android.internal.widget.ImageFloatingTextView xmlns:android="http://schemas.android.com/apk/res/android" style="@style/Widget.DeviceDefault.Notification.Text" android:id="@+id/text" android:layout_width="match_parent" - android:layout_height="@dimen/notification_text_height" - android:layout_gravity="top" - android:layout_marginTop="@dimen/notification_text_margin_top" + android:layout_height="wrap_content" + android:minHeight="@dimen/notification_text_height" + android:layout_gravity="center_vertical" android:ellipsize="end" android:fadingEdge="horizontal" android:gravity="top" diff --git a/core/res/res/layout/notification_template_notification_progress_bar.xml b/core/res/res/layout/notification_template_notification_progress_bar.xml index 35748962cfb2..8511d38718d1 100644 --- a/core/res/res/layout/notification_template_notification_progress_bar.xml +++ b/core/res/res/layout/notification_template_notification_progress_bar.xml @@ -20,4 +20,5 @@ android:layout_width="match_parent" android:layout_height="@dimen/notification_progress_tracker_height" style="@style/Widget.Material.Notification.NotificationProgressBar" + android:accessibilityLiveRegion="polite" /> diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml index ae05666bc2a2..9967209c3f49 100644 --- a/core/res/res/values-af/strings.xml +++ b/core/res/res/values-af/strings.xml @@ -1803,8 +1803,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Eenhandmodus"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Ekstra donker"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"Gehoortoestelle"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"Outoklik"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"Ontkoppel"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"Gekoppel"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"Aktief"</string> @@ -1966,7 +1965,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Vra ontsluitpatroon voordat jy ontspeld"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Vra wagwoord voordat jy ontspeld"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"Deur jou admin geïnstalleer.\nGaan na instellings om toegestaande toestemmings te sien"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"Opgedateer deur jou administrateur"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"Uitgevee deur jou administrateur"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Batterybespaarder skakel Donkertema aan en beperk of skakel agtergrondaktiwiteit, sommige visuele effekte, sekere kenmerke en sommige netwerkverbindings af"</string> diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml index 19be04eabfbf..bdaf5a18a5ba 100644 --- a/core/res/res/values-am/strings.xml +++ b/core/res/res/values-am/strings.xml @@ -1803,8 +1803,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"የአንድ እጅ ሁነታ"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"ተጨማሪ ደብዛዛ"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"የመስሚያ መሣሪያዎች"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"ራስ-ሰር ጠቅ ማድረግ"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"ግንኙነት ተቋርጧል"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"ተገናኝቷል"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"ገቢር"</string> @@ -1966,7 +1965,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"ከመንቀል በፊት የማስከፈቻ ሥርዓተ-ጥለት ጠይቅ"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"ከመንቀል በፊት የይለፍ ቃል ጠይቅ"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"በአስተዳዳሪዎ ተጭኗል።\nየተፈቀዱ ፍቃዶችን ለማየት ወደ ቅንብሮች ይሂዱ"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"በእርስዎ አስተዳዳሪ ተዘምኗል"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"በእርስዎ አስተዳዳሪ ተሰርዟል"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"እሺ"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"ባትሪ ቆጣቢ ጠቆር ያለ ገጽታን ያበራል እና የጀርባ እንቅስቃሴን፣ አንዳንድ ዕይታዊ ውጤቶችን፣ አንዳንድ ባህሪዎችን፣ እና አንዳንድ የአውታረ መረብ ግንኙነቶችን ይገድባል ወይም ያጠፋል።"</string> diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml index 66192cb01ebf..3b0e78dbada1 100644 --- a/core/res/res/values-ar/strings.xml +++ b/core/res/res/values-ar/strings.xml @@ -1807,8 +1807,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"وضع \"التصفح بيد واحدة\""</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"زيادة تعتيم الشاشة"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"سماعات الأذن الطبية"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"النقر التلقائي"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"غير متّصل"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"متّصل"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"متّصل حاليًا"</string> @@ -1970,7 +1969,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"طلب إدخال نقش فتح القفل قبل إزالة التثبيت"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"طلب إدخال كلمة المرور قبل إزالة التثبيت"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"تم التثبيت من قِبل المشرف.\nانتقِل إلى الإعدادات للاطّلاع على الأذونات الممنوحة"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"تم التحديث بواسطة المشرف"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"تم الحذف بواسطة المشرف"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"حسنًا"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"يؤدي استخدام ميزة \"توفير شحن البطارية\" إلى تفعيل وضع \"المظهر الداكن\" وتقييد أو إيقاف الأنشطة في الخلفية وبعض التأثيرات المرئية وميزات معيّنة وبعض اتصالات الشبكات."</string> diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml index b85be47b4a38..5d93b655d741 100644 --- a/core/res/res/values-as/strings.xml +++ b/core/res/res/values-as/strings.xml @@ -1803,8 +1803,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"এখন হাতেৰে ব্যৱহাৰ কৰাৰ ম’ড"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"অতিৰিক্তভাৱে পোহৰ কমোৱাৰ সুবিধা"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"শুনাৰ ডিভাইচ"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"স্বয়ংক্ৰিয়ভাৱে ক্লিক কৰাৰ সুবিধা"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"সংযোগ বিচ্ছিন্ন হ’ল"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"সংযোগ কৰা হ’ল"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"সক্ৰিয় হৈ আছে"</string> @@ -1966,7 +1965,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"আনপিন কৰাৰ পূৰ্বে আনলক আৰ্হি দিবলৈ কওক"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"আনপিন কৰাৰ পূৰ্বে পাছৱৰ্ড দিবলৈ কওক"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"আপোনাৰ প্ৰশাসকে ইনষ্টল কৰিছে।\nপ্ৰদান কৰা অনুমতিসমূহ চাবলৈ ছেটিঙলৈ যাওক"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"আপোনাৰ প্ৰশাসকে আপেডট কৰিছে"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"আপোনাৰ প্ৰশাসকে মচিছে"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"ঠিক আছে"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"বেটাৰী সঞ্চয়কাৰীয়ে গাঢ় ৰঙৰ থীম অন কৰে আৰু নেপথ্যৰ কাৰ্যকলাপ, কিছুমান ভিজুৱেল ইফেক্ট, নিৰ্দিষ্ট কিছুমান সুবিধা আৰু নেটৱৰ্কৰ সংযোগ সীমিত অথবা অফ কৰে।"</string> diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml index b9bd5437c7b0..c6a0adea6d6f 100644 --- a/core/res/res/values-az/strings.xml +++ b/core/res/res/values-az/strings.xml @@ -1803,8 +1803,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Birəlli rejim"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Əlavə tündləşmə"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"Eşitmə cihazları"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"Avtomatik klikləmə"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"Bağlantı kəsildi"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"Qoşuldu"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"Aktivdir"</string> @@ -1966,7 +1965,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Qrafik açar istənilsin"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Ayırmadan öncə parol istənilsin"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"Admin quraşdırıb.\nVerilən icazələrə baxmaq üçün ayarlara keçin"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"Admin tərəfindən yeniləndi"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"Admin tərəfindən silindi"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Enerjiyə Qənaət rejimi Tünd temanı aktivləşdirir, habelə arxa fon fəaliyyətini, bəzi vizual effektləri, müəyyən xüsusiyyətləri və bəzi şəbəkə bağlantılarını məhdudlaşdırır, yaxud söndürür."</string> diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml index a558de3d755a..2b4dc578e610 100644 --- a/core/res/res/values-b+sr+Latn/strings.xml +++ b/core/res/res/values-b+sr+Latn/strings.xml @@ -1966,7 +1966,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Traži šablon za otključavanje pre otkačinjanja"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Traži lozinku pre otkačinjanja"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"Instalirao je administrator.\nIdite u podešavanja da biste videli odobrene dozvole"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"Ažurirao je administrator"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"Izbrisao je administrator"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"Potvrdi"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Ušteda baterije uključuje tamnu temu i ograničava ili isključuje aktivnosti u pozadini, neke vizuelne efekte, određene funkcije i neke mrežne veze."</string> diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml index 113be9de3185..8230b1c45757 100644 --- a/core/res/res/values-be/strings.xml +++ b/core/res/res/values-be/strings.xml @@ -1805,8 +1805,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Рэжым кіравання адной рукой"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Дадатковае памяншэнне яркасці"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"Слыхавыя апараты"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"Аўтанацісканне"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"Адключана"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"Падключана"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"Актыўная"</string> @@ -1968,7 +1967,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Запытваць узор разблакіроўкі перад адмацаваннем"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Запытваць пароль перад адмацаваннем"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"Усталявана адміністратарам.\nКаб паглядзець дадзеныя дазволы, перайдзіце ў налады"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"Абноўлены вашым адміністратарам"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"Выдалены вашым адміністратарам"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"ОК"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"У рэжыме энергазберажэння ўключаецца цёмная тэма і выключаюцца ці абмяжоўваюцца дзеянні ў фонавым рэжыме, некаторыя візуальныя эфекты, пэўныя функцыі і падключэнні да сетак."</string> diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml index c36940f47371..1b0477f09538 100644 --- a/core/res/res/values-bg/strings.xml +++ b/core/res/res/values-bg/strings.xml @@ -1803,8 +1803,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Работа с една ръка"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Допълнително затъмняване"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"Слухови апарати"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"Автоматично кликване"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"Няма връзка"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"Свързано"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"Активно"</string> @@ -1966,7 +1965,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Запитване за фигура за отключване преди освобождаване"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Запитване за парола преди освобождаване"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"Инсталирано от администратора ви.\nОтворете настройките, за да прегледате предоставените разрешения"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"Актуализирано от администратора ви"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"Изтрито от администратора ви"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"ОК"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Режимът за запазване на батерията включва тъмната тема и ограничава или изключва активността на заден план, някои визуални ефекти, определени функции и някои връзки с мрежата."</string> diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml index b79c1d703eb9..2461a70e1ab8 100644 --- a/core/res/res/values-bn/strings.xml +++ b/core/res/res/values-bn/strings.xml @@ -1803,8 +1803,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"এক হাতে ব্যবহার করার মোড"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"অতিরিক্ত কম উজ্জ্বলতা"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"হিয়ারিং ডিভাইস"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"অটোক্লিক"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"ডিসকানেক্ট হয়ে গেছে"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"কানেক্ট করা হয়েছে"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"অ্যাক্টিভ"</string> @@ -1966,7 +1965,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"আনপিন করার আগে আনলক প্যাটার্ন চান"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"আনপিন করার আগে পাসওয়ার্ড চান"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"আপনার অ্যাডমিন ইনস্টল করেছেন।\nঅনুমোদন করা অনুমতি দেখতে সেটিংসে যান"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"আপনার প্রশাসক আপডেট করেছেন"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"আপনার প্রশাসক মুছে দিয়েছেন"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"ঠিক আছে"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"ব্যাটারি সেভার ডার্ক থিম চালু করে এবং ব্যাকগ্রাউন্ড অ্যাক্টিভিটি, কিছু ভিজ্যুয়াল এফেক্ট, নির্দিষ্ট ফিচার ও কয়েকটি নেটওয়ার্ক কানেকশনের ব্যবহার সীমিত করে বা বন্ধ করে দেয়।"</string> diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml index a995746a28e3..b8d3283491c3 100644 --- a/core/res/res/values-bs/strings.xml +++ b/core/res/res/values-bs/strings.xml @@ -1804,8 +1804,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Način rada jednom rukom"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Dodatno zatamnjenje"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"Slušni aparati"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"Automatski klik"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"Nije povezano"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"Povezano"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"Aktivno"</string> @@ -1967,7 +1966,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Traži uzorak za otključavanje prije poništavanja kačenja"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Traži lozinku prije nego se otkači"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"Instalirao je vaš administrator.\nIdite u postavke da pregledate data odobrenja"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"Ažurirao je vaš administrator"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"Izbrisao je vaš administrator"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"Uredu"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Ušteda baterije uključuje tamnu temu i ograničava ili isključuje aktivnost u pozadini, određene vizuelne efekte i funkcije te neke mrežne veze."</string> diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml index 62f59f2570f9..cbfa521c96e4 100644 --- a/core/res/res/values-ca/strings.xml +++ b/core/res/res/values-ca/strings.xml @@ -1804,8 +1804,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Mode d\'una mà"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Atenuació extra"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"Audiòfons"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"Clic automàtic"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"Desconnectat"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"Connectat"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"Actiu"</string> @@ -1967,7 +1966,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Sol·licita el patró de desbloqueig per deixar de fixar"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Demana la contrasenya per deixar de fixar"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"Instal·lat per l\'administrador.\nVes a la configuració per veure els permisos concedits."</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"Actualitzat per l\'administrador"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"Suprimit per l\'administrador"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"D\'acord"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Estalvi de bateria activa el tema fosc i limita o desactiva l\'activitat en segon pla, alguns efectes visuals, determinades funcions i algunes connexions de xarxa."</string> diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml index 5e7a8aa7ee2a..356cdc71b54a 100644 --- a/core/res/res/values-cs/strings.xml +++ b/core/res/res/values-cs/strings.xml @@ -1805,8 +1805,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Režim jedné ruky"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Velmi tmavé zobrazení"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"Naslouchátka"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"Automatické kliknutí"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"Odpojeno"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"Připojeno"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"Aktivní"</string> @@ -1968,7 +1967,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Před uvolněním požádat o bezpečnostní gesto"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Před odepnutím požádat o heslo"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"Nainstalováno administrátorem.\nUdělená oprávnění si můžete prohlédnout v nastavení."</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"Aktualizováno administrátorem"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"Smazáno administrátorem"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Spořič baterie zapíná tmavý motiv a omezuje či vypíná aktivitu na pozadí, některé vizuální efekty, některé funkce a připojení k některým sítím."</string> diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml index ab7339491042..efe3e103d0e1 100644 --- a/core/res/res/values-da/strings.xml +++ b/core/res/res/values-da/strings.xml @@ -1803,8 +1803,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Enhåndstilstand"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Ekstra dæmpet belysning"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"Høreapparater"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"Autoklik"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"Ikke forbundet"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"Forbundet"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"Aktiv"</string> @@ -1966,7 +1965,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Bed om oplåsningsmønster ved deaktivering"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Bed om adgangskode inden frigørelse"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"Installeret af din administrator.\nGå til Indstillinger for at se de tilladelser, der er blevet givet"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"Opdateret af din administrator"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"Slettet af din administrator"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Batterisparefunktionen aktiverer Mørkt tema og begrænser eller deaktiverer aktivitet i baggrunden og visse visuelle effekter, funktioner og netværksforbindelser."</string> diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml index 8bff3505e78b..fc0e9ed19b9d 100644 --- a/core/res/res/values-de/strings.xml +++ b/core/res/res/values-de/strings.xml @@ -1803,8 +1803,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Einhandmodus"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Extradunkel"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"Hörgeräte"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"Automatischer Klick"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"Nicht verbunden"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"Verbunden"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"Aktiv"</string> @@ -1966,7 +1965,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Vor dem Beenden nach Entsperrungsmuster fragen"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Vor dem Beenden nach Passwort fragen"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"Von deinem Administrator installiert.\nRufe die Einstellungen auf, um gewährte Berechtigungen anzusehen."</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"Von deinem Administrator aktualisiert"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"Von deinem Administrator gelöscht"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Der Energiesparmodus aktiviert das dunkle Design. Hintergrundaktivitäten, einige Funktionen und optische Effekte und manche Netzwerkverbindungen werden eingeschränkt oder deaktiviert."</string> diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml index 0e968bd4da04..58f5d9db4d68 100644 --- a/core/res/res/values-el/strings.xml +++ b/core/res/res/values-el/strings.xml @@ -1803,8 +1803,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Λειτουργία ενός χεριού"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Επιπλέον μείωση φωτεινότητας"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"Συσκευές ακοής"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"Αυτόματο κλικ"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"Αποσυνδέθηκε"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"Συνδέθηκε"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"Ενεργή"</string> @@ -1966,7 +1965,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Να γίνεται ερώτηση για το μοτίβο ξεκλειδώματος, πριν από το ξεκαρφίτσωμα"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Να γίνεται ερώτηση για τον κωδικό πρόσβασης, πριν από το ξεκαρφίτσωμα"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"Εγκαταστάθηκε από τον διαχειριστή σας.\nΜεταβείτε στις ρυθμίσεις για να δείτε τις άδειες που έχουν εκχωρηθεί"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"Ενημερώθηκε από τον διαχειριστή σας"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"Διαγράφηκε από τον διαχειριστή σας"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Η Εξοικονόμηση μπαταρίας ενεργοποιεί το Σκούρο θέμα και περιορίζει ή απενεργοποιεί τη δραστηριότητα στο παρασκήνιο, ορισμένα οπτικά εφέ, συγκεκριμένες λειτουργίες και κάποιες συνδέσεις δικτύου."</string> diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml index 6c43f9a51dbd..320e63935fc9 100644 --- a/core/res/res/values-en-rAU/strings.xml +++ b/core/res/res/values-en-rAU/strings.xml @@ -1803,8 +1803,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"One-handed mode"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Extra dim"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"Hearing devices"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"Autoclick"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"Disconnected"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"Connected"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"Active"</string> @@ -1966,7 +1965,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Ask for unlock pattern before unpinning"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Ask for password before unpinning"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"Installed by your admin.\nGo to Settings to view granted permissions"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"Updated by your admin"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"Deleted by your admin"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Battery Saver turns on Dark theme and limits or turns off background activity, some visual effects, certain features and some network connections."</string> diff --git a/core/res/res/values-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml index 99abe8bfeb27..85471d8a5efb 100644 --- a/core/res/res/values-en-rCA/strings.xml +++ b/core/res/res/values-en-rCA/strings.xml @@ -1965,7 +1965,7 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Ask for unlock pattern before unpinning"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Ask for password before unpinning"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"Installed by your admin.\nGo to settings to view granted permissions"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"Updated by your admin"</string> + <string name="package_updated_device_owner" msgid="7770195449213776218">"Updated by your admin.\nGo to settings to view granted permissions"</string> <string name="package_deleted_device_owner" msgid="2292335928930293023">"Deleted by your admin"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Battery Saver turns on Dark theme and limits or turns off background activity, some visual effects, certain features, and some network connections."</string> diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml index 99040f3f56b9..730dd3b808e8 100644 --- a/core/res/res/values-en-rGB/strings.xml +++ b/core/res/res/values-en-rGB/strings.xml @@ -1803,8 +1803,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"One-handed mode"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Extra dim"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"Hearing devices"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"Autoclick"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"Disconnected"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"Connected"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"Active"</string> @@ -1966,7 +1965,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Ask for unlock pattern before unpinning"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Ask for password before unpinning"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"Installed by your admin.\nGo to Settings to view granted permissions"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"Updated by your admin"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"Deleted by your admin"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Battery Saver turns on Dark theme and limits or turns off background activity, some visual effects, certain features and some network connections."</string> diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml index cb1e92ed638f..a9654e525df4 100644 --- a/core/res/res/values-en-rIN/strings.xml +++ b/core/res/res/values-en-rIN/strings.xml @@ -1803,8 +1803,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"One-handed mode"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Extra dim"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"Hearing devices"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"Autoclick"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"Disconnected"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"Connected"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"Active"</string> @@ -1966,7 +1965,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Ask for unlock pattern before unpinning"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Ask for password before unpinning"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"Installed by your admin.\nGo to Settings to view granted permissions"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"Updated by your admin"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"Deleted by your admin"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Battery Saver turns on Dark theme and limits or turns off background activity, some visual effects, certain features and some network connections."</string> diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml index 18cbfaa5b1dd..036ecfc59d87 100644 --- a/core/res/res/values-es-rUS/strings.xml +++ b/core/res/res/values-es-rUS/strings.xml @@ -1804,8 +1804,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Modo de una mano"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Atenuación extra"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"Dispositivos auditivos"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"Clic automático"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"Desconectado"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"Conectado"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"Activo"</string> @@ -1967,7 +1966,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Solicitar desbloqueo para quitar fijación"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Solicitar contraseña para quitar fijación"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"Tu administrador realizó la instalación.\nVe a la configuración para ver los permisos otorgados"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"Tu administrador actualizó este paquete"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"Tu administrador borró este paquete"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"Aceptar"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"El Ahorro de batería activa el Tema oscuro y desactiva o restringe la actividad en segundo plano, algunos efectos visuales, algunas conexiones de red y otras funciones determinadas."</string> diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml index 852347388819..3553aea44c2d 100644 --- a/core/res/res/values-es/strings.xml +++ b/core/res/res/values-es/strings.xml @@ -1804,8 +1804,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Modo Una mano"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Atenuación extra"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"Audífonos"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"Clic automático"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"Desconectado"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"Conectado"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"Activo"</string> @@ -1967,7 +1966,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Pedir patrón de desbloqueo para dejar de fijar"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Solicitar contraseña para desactivar"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"Instalado por tu administrador.\nVe a Ajustes para ver los permisos concedidos."</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"Actualizado por el administrador"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"Eliminado por el administrador"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"Aceptar"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Ahorro de batería activa el tema oscuro y limita o desactiva la actividad en segundo plano, algunos efectos visuales, ciertas funciones y algunas conexiones de red."</string> diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml index a1ddf81c72a5..abecc1a76d3f 100644 --- a/core/res/res/values-et/strings.xml +++ b/core/res/res/values-et/strings.xml @@ -1803,8 +1803,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Ühekäerežiim"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Eriti tume"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"Kuuldeseadmed"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"Automaatklikk"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"Ühendus katkestatud"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"Ühendatud"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"Aktiivne"</string> @@ -1966,7 +1965,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Enne vabastamist küsi avamismustrit"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Enne vabastamist küsi parooli"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"Installis teie administraator.\nAntud õiguste vaatamiseks avage seaded"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"Administraator on seda värskendanud"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"Administraator on selle kustutanud"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Akusäästja lülitab sisse tumeda teema ja lülitab välja taustategevused, mõned visuaalsed efektid, teatud funktsioonid ja võrguühendused või piirab neid."</string> diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml index c63cfe666afe..64dc7303e8c1 100644 --- a/core/res/res/values-eu/strings.xml +++ b/core/res/res/values-eu/strings.xml @@ -1803,8 +1803,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Esku bakarreko modua"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Are ilunago"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"Entzumen-gailuak"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"Automatikoki klik egiteko eginbidea"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"Deskonektatuta"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"Konektatuta"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"Aktibo"</string> @@ -1966,7 +1965,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Eskatu desblokeatzeko eredua aingura kendu aurretik"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Eskatu pasahitza aingura kendu aurretik"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"Administratzaileak instalatu du.\nEmandako baimenak ikusteko, joan ezarpenetara."</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"Administratzaileak eguneratu du"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"Administratzaileak ezabatu du"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"Ados"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Bateria-aurreztaileak gai iluna aktibatzen du, eta atzeko planoko jarduerak, zenbait efektu bisual, eta eginbide jakin eta sareko konexio batzuk mugatzen edo desaktibatzen ditu."</string> diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml index 5e1caad2cf2b..13083d0c04d4 100644 --- a/core/res/res/values-fa/strings.xml +++ b/core/res/res/values-fa/strings.xml @@ -1803,8 +1803,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"حالت یکدستی"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"بسیار کمنور"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"دستگاههای کمکشنوایی"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"کلیک خودکار"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"متصل نیست"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"وصل شد"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"فعال"</string> @@ -1966,7 +1965,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"درخواست الگوی بازگشایی قفل قبلاز برداشتن سنجاق"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"درخواست گذرواژه قبل از برداشتن سنجاق"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"سرپرست شما آن را نصب کرده است.\nبرای مشاهده اجازههای اعطاشده به تنظیمات بروید"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"توسط سرپرست سیستم بهروزرسانی شد"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"توسط سرپرست سیستم حذف شد"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"تأیید"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"«بهینهسازی باتری» «زمینه تاریک» را روشن میکند و فعالیت پسزمینه، برخی از جلوههای بصری، ویژگیهایی خاص، و برخی از اتصالهای شبکه را محدود یا خاموش میکند."</string> diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml index f7115ebbffa3..145c5ee4bef1 100644 --- a/core/res/res/values-fi/strings.xml +++ b/core/res/res/values-fi/strings.xml @@ -1803,8 +1803,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Yhden käden moodi"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Erittäin himmeä"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"Kuulolaitteet"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"Automaattinen klikkaus"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"Yhteys katkaistu"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"Yhdistetty"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"Aktiivinen"</string> @@ -1966,7 +1965,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Pyydä lukituksenpoistokuvio ennen irrotusta"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Pyydä salasana ennen irrotusta"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"Järjestelmänvalvojan asentama.\nTarkista myönnetyt luvat asetuksista."</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"Järjestelmänvalvoja päivitti tämän."</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"Järjestelmänvalvoja poisti tämän."</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Virransäästö laittaa tumman teeman päälle ja rajoittaa tai laittaa pois päältä taustatoimintoja, tiettyjä ominaisuuksia sekä joitakin visuaalisia tehosteita ja verkkoyhteyksiä."</string> diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml index 56623a296c1c..0ee4112ed458 100644 --- a/core/res/res/values-fr-rCA/strings.xml +++ b/core/res/res/values-fr-rCA/strings.xml @@ -1804,8 +1804,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Mode Une main"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Très sombre"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"Appareils auditifs"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"Clic automatique"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"Déconnecté"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"Connecté"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"Actif"</string> @@ -1967,7 +1966,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Demander le schéma de déverrouillage avant d\'annuler l\'épinglage"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Demander le mot de passe avant d\'annuler l\'épinglage"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"Installé par votre administrateur.\nAccédez aux paramètres pour consulter les autorisations accordées"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"Mise à jour par votre administrateur"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"Supprimé par votre administrateur"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Le mode Économiseur de pile active le thème sombre et limite ou désactive l\'activité en arrière-plan, certains effets visuels, certaines fonctionnalités et certaines connexions réseau."</string> diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml index 90a0f441d0c9..a5b6f9a92dce 100644 --- a/core/res/res/values-fr/strings.xml +++ b/core/res/res/values-fr/strings.xml @@ -1804,8 +1804,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Mode une main"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Luminosité ultra-réduite"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"Appareils auditifs"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"Clic automatique"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"Déconnecté"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"Connecté"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"Actif"</string> @@ -1967,7 +1966,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Demander le schéma de déverrouillage avant de retirer l\'épingle"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Demander le mot de passe avant de retirer l\'épingle"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"Installé par votre administrateur.\nAllez dans les paramètres pour consulter les autorisations accordées."</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"Mis à jour par votre administrateur"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"Supprimé par votre administrateur"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"L\'économiseur de batterie active le thème sombre et limite ou désactive l\'activité en arrière-plan ainsi que certains effets visuels, fonctionnalités et connexions réseau."</string> diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml index a2b53818dec6..332387ad0131 100644 --- a/core/res/res/values-gl/strings.xml +++ b/core/res/res/values-gl/strings.xml @@ -1803,8 +1803,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Modo dunha soa man"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Atenuación extra"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"Dispositivos auditivos"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"Clic automático"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"Desconectado"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"Conectado"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"Activo"</string> @@ -1966,7 +1965,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Pedir padrón de desbloqueo antes de soltar a fixación"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Pedir contrasinal antes de soltar a fixación"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"Instalado pola persoa administradora.\nVai á configuración para ver os permisos concedidos"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"Actualizado polo teu administrador"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"Eliminado polo teu administrador"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"Aceptar"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Coa función Aforro de batería, actívase o tema escuro e restrínxense ou desactívanse a actividade en segundo plano, algúns efectos visuais e determinadas funcións e conexións de rede."</string> diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml index 9d57bde4ab4b..7a39d634508e 100644 --- a/core/res/res/values-gu/strings.xml +++ b/core/res/res/values-gu/strings.xml @@ -1966,7 +1966,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"અનપિન કરતા પહેલાં અનલૉક પૅટર્ન માટે પૂછો"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"અનપિન કરતાં પહેલાં પાસવર્ડ માટે પૂછો"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"તમારા ઍડમિન દ્વારા ઇન્સ્ટૉલ કરવામાં આવ્યું છે.\nઆપેલી પરવાનગીઓ જોવા માટે સેટિંગ પર જાઓ"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"તમારા વ્યવસ્થાપક દ્વારા અપડેટ કરવામાં આવેલ છે"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"તમારા વ્યવસ્થાપક દ્વારા કાઢી નાખવામાં આવેલ છે"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"ઓકે"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"બૅટરી સેવર ઘેરી થીમની સુવિધા ચાલુ કરે છે અને બૅકગ્રાઉન્ડ પ્રવૃત્તિ, અમુક વિઝ્યુઅલ ઇફેક્ટ, અમુક સુવિધાઓ અને કેટલાક નેટવર્ક કનેક્શન મર્યાદિત કે બંધ કરે છે."</string> diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml index 563b724292f4..2c43996064ea 100644 --- a/core/res/res/values-hi/strings.xml +++ b/core/res/res/values-hi/strings.xml @@ -1803,8 +1803,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"वन-हैंडेड मोड"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"स्क्रीन की रोशनी को सामान्य लेवल से और कम करने की सुविधा"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"कान की मशीन"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"अपने-आप क्लिक होने की सुविधा"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"डिसकनेक्ट हो गया"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"कनेक्ट हो गया"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"चालू है"</string> @@ -1966,7 +1965,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"अनपिन करने से पहले लॉक खोलने के पैटर्न के लिए पूछें"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"अनपिन करने से पहले पासवर्ड के लिए पूछें"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"इसे आपके एडमिन ने इंस्टॉल किया है.\nजिन अनुमतियों को मंज़ूरी मिली है उन्हें देखने के लिए, सेटिंग में जाएं"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"आपके व्यवस्थापक ने अपडेट किया है"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"आपके व्यवस्थापक ने हटा दिया है"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"ठीक है"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"बैटरी सेवर, गहरे रंग वाली थीम को चालू करता है. साथ ही, इस मोड में बैकग्राउंड की गतिविधि, कुछ विज़ुअल इफ़ेक्ट, और कुछ खास सुविधाएं कम या बंद हो जाती हैं. कुछ इंटरनेट कनेक्शन भी पूरी तरह काम नहीं करते."</string> diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml index fec624d94edc..d870a1dbda61 100644 --- a/core/res/res/values-hr/strings.xml +++ b/core/res/res/values-hr/strings.xml @@ -1804,8 +1804,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Način rada jednom rukom"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Još tamnije"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"Slušna pomagala"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"Automatski klik"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"Nije povezano"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"Povezano"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"Aktivno"</string> @@ -1967,7 +1966,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Traži uzorak za otključavanje radi otkvačivanja"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Traži zaporku radi otkvačivanja"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"Instalirao vaš administrator.\nOtvorite postavke da biste pregledali dodijeljena dopuštenja"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"Ažurirao administrator"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"Izbrisao administrator"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"U redu"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Štednja baterije uključuje tamnu temu i ograničava ili isključuje aktivnosti u pozadini, neke vizualne efekte, određene značajke i neke mrežne veze."</string> diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml index 7871aba30258..350d51dee68c 100644 --- a/core/res/res/values-hu/strings.xml +++ b/core/res/res/values-hu/strings.xml @@ -1803,8 +1803,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Egykezes mód"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Extrasötét"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"Hallásjavító eszközök"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"Automatikus kattintás"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"Leválasztva"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"Csatlakozva"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"Aktív"</string> @@ -1966,7 +1965,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Feloldási minta kérése a kitűzés feloldásához"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Jelszó kérése a rögzítés feloldásához"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"A rendszergazda által telepítve.\nLépjen a beállításokhoz a megadott engedélyek megtekintéséhez."</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"A rendszergazda által frissítve"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"A rendszergazda által törölve"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Az Akkumulátorkímélő mód bekapcsolja a Sötét témát, és korlátozza vagy kikapcsolja a háttérbeli tevékenységeket, valamint bizonyos vizuális effekteket, funkciókat és hálózati kapcsolatokat."</string> diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml index 01e51106ea3d..655af9395fa3 100644 --- a/core/res/res/values-hy/strings.xml +++ b/core/res/res/values-hy/strings.xml @@ -1803,8 +1803,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Մեկ ձեռքի ռեժիմ"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Հավելյալ խամրեցում"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"Լսողական սարքեր"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"Ավտոմատ սեղմում"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"Անջատված է"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"Միացված է"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"Ակտիվ է"</string> @@ -1966,7 +1965,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Հարցնել ապակողպող նախշը"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Հարցնել գաղտնաբառը"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"Տեղադրվել է ադմինիստրատորի կողմից։\nԱնցեք կարգավորումներ՝ տրամադրված թույլտվությունները դիտելու համար"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"Թարմացվել է ձեր ադմինիստրատորի կողմից"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"Ջնջվել է ձեր ադմինիստրատորի կողմից"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"Եղավ"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"«Մարտկոցի տնտեսում» գործառույթը միացնում է մուգ թեման և անջատում կամ սահմանափակում է աշխատանքը ֆոնային ռեժիմում, որոշ վիզուալ էֆեկտներ, ցանցային միացումներ և այլ գործառույթներ։"</string> diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml index df42e47e7028..305d23a15c00 100644 --- a/core/res/res/values-in/strings.xml +++ b/core/res/res/values-in/strings.xml @@ -1803,8 +1803,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Mode satu tangan"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Ekstra redup"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"Alat bantu dengar"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"Klik otomatis"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"Tidak terhubung"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"Terhubung"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"Aktif"</string> @@ -1966,7 +1965,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Meminta pola pembukaan kunci sebelum melepas sematan"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Meminta sandi sebelum melepas sematan"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"Diinstal oleh admin Anda.\nBuka setelan untuk melihat izin yang diberikan"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"Diupdate oleh admin Anda"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"Dihapus oleh admin Anda"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"Oke"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Penghemat Baterai akan mengaktifkan Tema gelap dan membatasi atau menonaktifkan aktivitas latar belakang, beberapa efek visual, fitur tertentu, dan beberapa koneksi jaringan."</string> diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml index 40d642d2fda4..cdf89ad4424d 100644 --- a/core/res/res/values-is/strings.xml +++ b/core/res/res/values-is/strings.xml @@ -1803,8 +1803,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Einhent stilling"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Mjög dökkt"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"Heyrnartæki"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"Sjálfvirkur smellur"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"Aftengt"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"Tengt"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"Virkt"</string> @@ -1966,7 +1965,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Biðja um opnunarmynstur til að losa"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Biðja um aðgangsorð til að losa"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"Sett upp af stjórnanda.\nFarðu í stillingar til að sjá heimildir"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"Kerfisstjóri uppfærði"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"Kerfisstjóri eyddi"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"Í lagi"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Rafhlöðusparnaður kveikir á dökku þema og takmarkar eða slekkur á bakgrunnsvirkni, sumum myndáhrifum, tilteknum eiginleikum og sumum nettengingum."</string> diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml index e501c572f94e..3b7d1ec750ee 100644 --- a/core/res/res/values-it/strings.xml +++ b/core/res/res/values-it/strings.xml @@ -1966,7 +1966,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Richiedi sequenza di sblocco prima di sbloccare"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Richiedi password prima di sbloccare"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"Installato dall\'amministratore.\nVai alle impostazioni per visualizzare le autorizzazioni concesse"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"Aggiornato dall\'amministratore"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"Eliminato dall\'amministratore"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"Ok"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Il risparmio energetico attiva il tema scuro e limita o disattiva l\'attività in background, nonché alcuni effetti visivi, funzionalità e connessioni di rete."</string> diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml index c9984e21c95e..cf7374868a50 100644 --- a/core/res/res/values-iw/strings.xml +++ b/core/res/res/values-iw/strings.xml @@ -1967,7 +1967,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"צריך לבקש קו ביטול נעילה לפני ביטול הצמדה"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"יש לבקש סיסמה לפני ביטול הצמדה"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"החבילה הותקנה על ידי האדמין.\nצריך לעבור להגדרות כדי לראות את ההרשאות שניתנו"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"עודכנה על ידי מנהל המערכת"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"נמחקה על ידי מנהל המערכת"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"אישור"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"התכונה \'חיסכון בסוללה\' מפעילה עיצוב כהה ומגבילה או מכבה פעילות ברקע, חלק מהאפקטים החזותיים, תכונות מסוימות וחלק מהחיבורים לרשתות."</string> diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml index 7ab896e6b247..f8bc2413fca0 100644 --- a/core/res/res/values-ja/strings.xml +++ b/core/res/res/values-ja/strings.xml @@ -1965,7 +1965,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"画面固定を解除する前にロック解除パターンの入力を求める"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"オフライン再生を解除する前にパスワードの入力を求める"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"管理者によりインストールされています。\n付与された権限を確認するには、設定に移動してください"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"管理者により更新されています"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"管理者により削除されています"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"バッテリー セーバーを ON にすると、ダークモードが ON になります。また、バックグラウンド アクティビティ、一部の視覚効果、特定の機能、一部のネットワーク接続が制限されるか OFF になります。"</string> diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml index 4d634d4fe752..8aa0af3efc44 100644 --- a/core/res/res/values-ka/strings.xml +++ b/core/res/res/values-ka/strings.xml @@ -1803,8 +1803,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"ცალი ხელის რეჟიმი"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"დამატებითი დაბინდვა"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"სმენის აპარატები"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"ავტოდაწკაპუნება"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"კავშირი გაწყვეტილია"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"დაკავშირებული"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"აქტიური"</string> @@ -1966,7 +1965,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"ფიქსაციის მოხსნამდე განბლოკვის ნიმუშის მოთხოვნა"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"ფიქსაციის მოხსნამდე პაროლის მოთხოვნა"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"დაინსტალირებულია თქვენი ადმინისტრატორის მიერ.\nდაშვებული ნებართვების სანახავად გადადით პარამეტრებზე"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"განახლებულია თქვენი ადმინისტრატორის მიერ"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"წაიშალა თქვენი ადმინისტრატორის მიერ"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"კარგი"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"ბატარეის დამზოგი ჩართავს მუქ თემას და შეზღუდავს ან გამორთავს ფონურ აქტივობას, ზოგიერთ ვიზუალურ ეფექტს, გარკვეულ ფუნქციებსა და ზოგიერთ ქსელთან კავშირს."</string> diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml index 8d54a32b625c..3908427d9f97 100644 --- a/core/res/res/values-kk/strings.xml +++ b/core/res/res/values-kk/strings.xml @@ -1803,8 +1803,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Бір қолмен басқару режимі"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Экранды қарайту"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"Есту аппараттары"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"Автоматты басу"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"Ажыратылды"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"Қосылды"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"Белсенді"</string> @@ -1966,7 +1965,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Босату алдында бекітпесін ашу өрнегін сұрау"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Босату алдында құпия сөзді сұрау"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"Әкімшіңіз орнатты.\nБерілген рұқсаттарды көру үшін параметрлерге өтіңіз."</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"Әкімші жаңартқан"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"Әкімші жойған"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"Жарайды"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Батареяны үнемдеу режимі қараңғы режимді іске қосады және фондық әрекеттерге, кейбір визуалдық әсерлерге, белгілі бір функциялар мен кейбір желі байланыстарына шектеу қояды немесе оларды өшіреді."</string> diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml index d7e02997714b..dee23f65e8ca 100644 --- a/core/res/res/values-km/strings.xml +++ b/core/res/res/values-km/strings.xml @@ -1803,8 +1803,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"មុខងារប្រើដៃម្ខាង"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"ងងឹតខ្លាំង"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"ឧបករណ៍ជំនួយការស្ដាប់"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"ចុចស្វ័យប្រវត្តិ"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"បានផ្ដាច់"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"បានភ្ជាប់"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"សកម្ម"</string> @@ -1966,7 +1965,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"សួររកលំនាំដោះសោមុនពេលដោះខ្ទាស់"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"សួររកពាក្យសម្ងាត់មុនពេលផ្ដាច់"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"បានដំឡើងដោយអ្នកគ្រប់គ្រងរបស់អ្នក។\nចូលទៅកាន់ការកំណត់ ដើម្បីមើលការអនុញ្ញាតដែលផ្ដល់ឱ្យ"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"ធ្វើបច្ចុប្បន្នភាពដោយអ្នកគ្រប់គ្រងរបស់អ្នក"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"លុបដោយអ្នកគ្រប់គ្រងរបស់អ្នក"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"យល់ព្រម"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"មុខងារសន្សំថ្មបើកទម្រង់រចនាងងឹត និងដាក់កំហិត ឬបិទសកម្មភាពផ្ទៃខាងក្រោយ បែបផែនរូបភាពមួយចំនួន មុខងារជាក់លាក់ និងការតភ្ជាប់បណ្ដាញមួយចំនួន។"</string> diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml index 9771def44b23..c1a70b0f7276 100644 --- a/core/res/res/values-kn/strings.xml +++ b/core/res/res/values-kn/strings.xml @@ -1803,8 +1803,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"ಒಂದು ಕೈ ಮೋಡ್"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"ಇನ್ನಷ್ಟು ಮಬ್ಬು"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"ಶ್ರವಣ ಸಾಧನಗಳು"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"ಆಟೋಕ್ಲಿಕ್"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"ಡಿಸ್ಕನೆಕ್ಟ್ ಆಗಿದೆ"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"ಕನೆಕ್ಟ್ ಆಗಿದೆ"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"ಸಕ್ರಿಯವಾಗಿದೆ"</string> @@ -1966,7 +1965,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"ಅನ್ಪಿನ್ ಮಾಡಲು ಅನ್ಲಾಕ್ ಪ್ಯಾಟರ್ನ್ ಕೇಳಿ"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"ಅನ್ಪಿನ್ ಮಾಡಲು ಪಾಸ್ವರ್ಡ್ ಕೇಳು"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"ನಿಮ್ಮ ನಿರ್ವಾಹಕರು ಇನ್ಸ್ಟಾಲ್ ಮಾಡಿದ್ದಾರೆ.\nನೀಡಲಾದ ಅನುಮತಿಗಳನ್ನು ವೀಕ್ಷಿಸಲು ಸೆಟ್ಟಿಂಗ್ಗಳಿಗೆ ಹೋಗಿ"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"ನಿಮ್ಮ ನಿರ್ವಾಹಕರಿಂದ ಅಪ್ಡೇಟ್ ಮಾಡಲ್ಪಟ್ಟಿದೆ"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"ನಿಮ್ಮ ನಿರ್ವಾಹಕರು ಅಳಿಸಿದ್ದಾರೆ"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"ಸರಿ"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"ಬ್ಯಾಟರಿ ಸೇವರ್, ಡಾರ್ಕ್ ಥೀಮ್ ಅನ್ನು ಆನ್ ಮಾಡುತ್ತದೆ ಮತ್ತು ಹಿನ್ನೆಲೆ ಚಟುವಟಿಕೆ, ಕೆಲವು ವಿಷುವಲ್ ಎಫೆಕ್ಟ್ಗಳು, ಕೆಲವು ಫೀಚರ್ಗಳು ಮತ್ತು ಇತರ ನೆಟ್ವರ್ಕ್ ಸಂಪರ್ಕಗಳನ್ನು ಮಿತಿಗೊಳಿಸುತ್ತದೆ ಅಥವಾ ಆಫ್ ಮಾಡುತ್ತದೆ."</string> diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml index a39db4b07f3e..be4a28423a93 100644 --- a/core/res/res/values-ko/strings.xml +++ b/core/res/res/values-ko/strings.xml @@ -1803,8 +1803,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"한 손 모드"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"더 어둡게"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"청각 보조 기기"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"자동 클릭"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"연결 끊김"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"연결됨"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"활성"</string> @@ -1966,7 +1965,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"고정 해제 시 잠금 해제 패턴 요청"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"고정 해제 이전에 비밀번호 요청"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"관리자에 의해 설치되었습니다.\n부여된 권한을 확인하려면 설정으로 이동하세요."</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"관리자에 의해 업데이트되었습니다."</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"관리자에 의해 삭제되었습니다."</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"확인"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"절전 모드는 어두운 테마를 사용하고 백그라운드 활동, 일부 시각 효과, 특정 기능 및 일부 네트워크 연결을 제한하거나 사용 중지합니다."</string> diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml index fbc3afdc573d..121908a416a4 100644 --- a/core/res/res/values-ky/strings.xml +++ b/core/res/res/values-ky/strings.xml @@ -1803,8 +1803,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Бир кол режими"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Кошумча караңгылатуу"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"Угуу түзмөктөрү"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"Авточыкылдатуу"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"Ажыратылды"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"Туташты"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"Жигердүү"</string> @@ -1966,7 +1965,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Бошотуудан мурун графикалык ачкыч суралсын"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Бошотуудан мурун сырсөз суралсын"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"Администраторуңуз орнотту.\nБерилген уруксаттарды көрүү үчүн параметрлерге өтүңүз"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"Администраторуңуз жаңыртып койгон"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"Администраторуңуз жок кылып салган"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"ЖАРАЙТ"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Батареяны үнөмдөөчү режимде Караңгы тема күйгүзүлүп, фондогу аракеттер, айрым визуалдык эффекттер, белгилүү бир функциялар жана айрым тармакка туташуулар чектелип же өчүрүлөт."</string> diff --git a/core/res/res/values-lo/strings.xml b/core/res/res/values-lo/strings.xml index a0a6b38644ea..ad62671d5e5b 100644 --- a/core/res/res/values-lo/strings.xml +++ b/core/res/res/values-lo/strings.xml @@ -1803,8 +1803,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"ໂໝດມືດຽວ"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"ຫຼຸດແສງເປັນພິເສດ"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"ອຸປະກອນຊ່ວຍຟັງ"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"ການຄລິກອັດຕະໂນມັດ"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"ຕັດການເຊື່ອມຕໍ່ແລ້ວ"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"ເຊື່ອມຕໍ່ແລ້ວ"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"ນຳໃຊ້ຢູ່"</string> @@ -1966,7 +1965,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"ຖາມຫາຮູບແບບປົດລັອກກ່ອນຍົກເລີກການປັກໝຸດ"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"ຖາມຫາລະຫັດຜ່ານກ່ອນຍົກເລີກການປັກໝຸດ"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"ຕິດຕັ້ງໂດຍຜູ້ເບິ່ງແຍງຂອງທ່ານ.\nເຂົ້າໄປການຕັ້ງຄ່າເພື່ອເບິ່ງສິດທີ່ໄດ້ຮັບອະນຸຍາດ"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"ຖືກອັບໂຫລດໂດຍຜູ້ເບິ່ງແຍງລະບົບຂອງທ່ານ"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"ຖືກລຶບອອກໂດຍຜູ້ເບິ່ງແຍງລະບົບຂອງທ່ານ"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"ຕົກລົງ"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"ຕົວປະຢັດແບັດເຕີຣີຈະເປີດໃຊ້ຮູບແບບສີສັນມືດ ແລະ ຈຳກັດ ຫຼື ປິດການເຄື່ອນໄຫວໃນພື້ນຫຼັງ, ເອັບເຟັກທາງພາບຈຳນວນໜຶ່ງ, ຄຸນສົມບັດບາງຢ່າງ ແລະ ການເຊື່ອມຕໍ່ເຄືອຂ່າຍບາງອັນ."</string> diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml index 0b4967a9ab92..27a08c25a84e 100644 --- a/core/res/res/values-lt/strings.xml +++ b/core/res/res/values-lt/strings.xml @@ -1805,8 +1805,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Vienos rankos režimas"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Itin blanku"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"Klausos įrenginiai"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"Automatinis paspaudimas"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"Atjungta"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"Prisijungta"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"Aktyvus"</string> @@ -1968,7 +1967,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Prašyti atrakinimo piešinio prieš atsegant"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Prašyti slaptažodžio prieš atsegant"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"Įdiegė administratorius.\nEikite į nustatymus ir peržiūrėkite suteiktus leidimus"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"Atnaujino administratorius"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"Ištrynė administratorius"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"Gerai"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Akumuliatoriaus tausojimo priemonė įjungia tamsiąją temą ir apriboja arba išjungia veiklą fone, kai kuriuos vaizdinius efektus, tam tikras funkcijas bei kai kuriuos tinklo ryšius."</string> diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml index 0d19dc26bb39..78c7ebdb4587 100644 --- a/core/res/res/values-lv/strings.xml +++ b/core/res/res/values-lv/strings.xml @@ -1804,8 +1804,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Vienas rokas režīms"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Papildu aptumšošana"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"Dzirdes aparāti"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"Automātiskā klikšķināšana"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"Atvienota"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"Pievienota"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"Aktīva"</string> @@ -1967,7 +1966,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Pirms atspraušanas pieprasīt atbloķēšanas kombināciju"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Pirms atspraušanas pieprasīt paroli"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"Instalēja jūsu administrators.\nPārejiet uz iestatījumiem, lai skatītu piešķirtās atļaujas."</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"Atjaunināja administrators"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"Dzēsa administrators"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"Labi"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Akumulatora enerģijas taupīšanas režīmā tiek ieslēgts tumšais motīvs un tiek ierobežotas vai izslēgtas darbības fonā, daži vizuālie efekti, noteiktas funkcijas un noteikti tīkla savienojumi."</string> diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml index 9889ea9d622b..146de01e7951 100644 --- a/core/res/res/values-mk/strings.xml +++ b/core/res/res/values-mk/strings.xml @@ -1531,7 +1531,7 @@ <string name="ext_media_status_unmounted" msgid="8145812017295835941">"Исфрлено"</string> <string name="ext_media_status_checking" msgid="159013362442090347">"Се проверува..."</string> <string name="ext_media_status_mounted" msgid="3459448555811203459">"Подготвено"</string> - <string name="ext_media_status_mounted_ro" msgid="1974809199760086956">"Само за читање"</string> + <string name="ext_media_status_mounted_ro" msgid="1974809199760086956">"Само за преглед"</string> <string name="ext_media_status_bad_removal" msgid="508448566481406245">"Отстранет небезбедно"</string> <string name="ext_media_status_unmountable" msgid="7043574843541087748">"Оштетено"</string> <string name="ext_media_status_unsupported" msgid="5460509911660539317">"Неподдржано"</string> @@ -1803,8 +1803,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Режим со една рака"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Дополнително затемнување"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"Слушни помагала"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"Автоматско кликнување"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"Не е поврзано"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"Поврзано"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"Активно"</string> @@ -1966,7 +1965,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Побарај шема за откл. пред откачување"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Побарај лозинка пред откачување"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"Инсталирано од администраторот.\nОдете во „Поставки“ за да ги прегледате доделените дозволи"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"Ажурирано од администраторот"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"Избришано од администраторот"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"Во ред"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"„Штедачот на батерија“ вклучува „Темна тема“ и ограничува или исклучува активност во заднина, некои визуелни ефекти, одредени функции и некои мрежни врски."</string> diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml index ff07aa08194b..3bb425ec9351 100644 --- a/core/res/res/values-ml/strings.xml +++ b/core/res/res/values-ml/strings.xml @@ -1965,7 +1965,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"അൺപിന്നിനുമുമ്പ് അൺലോക്ക് പാറ്റേൺ ആവശ്യപ്പെടൂ"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"അൺപിന്നിനുമുമ്പ് പാസ്വേഡ് ആവശ്യപ്പെടൂ"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"നിങ്ങളുടെ അഡ്മിൻ ഇൻസ്റ്റാൾ ചെയ്തത്.\nനൽകിയ അനുമതികൾ കാണാൻ ക്രമീകരണത്തിലേക്ക് പോകുക"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"നിങ്ങളുടെ അഡ്മിൻ അപ്ഡേറ്റ് ചെയ്യുന്നത്"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"നിങ്ങളുടെ അഡ്മിൻ ഇല്ലാതാക്കുന്നത്"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"ശരി"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"\'ബാറ്ററി സേവർ\' ഡാർക്ക് തീം ഓണാക്കുന്നു, ഒപ്പം പശ്ചാത്തല ആക്റ്റിവിറ്റിയും ചില വിഷ്വൽ ഇഫക്റ്റുകളും ചില ഫീച്ചറുകളും ചില നെറ്റ്വർക്ക് കണക്ഷനുകളും പരിമിതപ്പെടുത്തുകയോ ഓഫാക്കുകയോ ചെയ്യുന്നു."</string> diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml index 3a1e0a621508..1ad9685a23d1 100644 --- a/core/res/res/values-mn/strings.xml +++ b/core/res/res/values-mn/strings.xml @@ -1803,8 +1803,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Нэг гарын горим"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Хэт бүүдгэр"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"Сонсголын төхөөрөмжүүд"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"Автомат товшилт"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"Салсан"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"Холбогдсон"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"Идэвхтэй"</string> @@ -1966,7 +1965,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Бэхэлснийг болиулахаас өмнө түгжээ тайлах хээ асуух"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Тогтоосныг суллахаас өмнө нууц үг асуух"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"Танай админ суулгасан.\nОлгосон зөвшөөрлүүдийг харахын тулд тохиргоо руу очно уу"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"Таны админ шинэчилсэн"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"Таны админ устгасан"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"ОК"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Батарей хэмнэгч нь Бараан загварыг асааж, дэвсгэрийн үйл ажиллагаа, зарим визуал эффект, тодорхой онцлогууд болон зарим сүлжээний холболтыг хязгаарлах эсвэл унтраана."</string> diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml index d79dc6d96a3b..82f217ff21b2 100644 --- a/core/res/res/values-mr/strings.xml +++ b/core/res/res/values-mr/strings.xml @@ -1803,8 +1803,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"एकहाती मोड"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"आणखी डिम"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"श्रवणयंत्रे"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"ऑटोक्लिक"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"डिस्कनेक्ट केले आहे"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"कनेक्ट केले आहे"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"अॅक्टिव्ह"</string> @@ -1966,7 +1965,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"अनपिन करण्यापूर्वी अनलॉक नमुन्यासाठी विचारा"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"अनपिन करण्यापूर्वी संकेतशब्दासाठी विचारा"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"तुमच्या ॲडमिनने इंस्टॉल केले आहे.\nदिलेल्या परवानग्या पाहण्यासाठी सेटिंग्जवर जा"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"आपल्या प्रशासकाने अपडेट केले"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"आपल्या प्रशासकाने हटवले"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"ओके"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"बॅटरी सेव्हर गडद थीम सुरू करते आणि बॅकग्राउंड ॲक्टिव्हिटी, काही व्हिज्युअल इफेक्ट, ठरावीक वैशिष्ट्ये व काही नेटवर्क कनेक्शन मर्यादित किंवा बंद करते."</string> diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml index 76d402796e6f..bda439a187a2 100644 --- a/core/res/res/values-ms/strings.xml +++ b/core/res/res/values-ms/strings.xml @@ -1965,7 +1965,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Minta corak buka kunci sebelum menyahsemat"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Minta kata laluan sebelum menyahsemat"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"Dipasang oleh pentadbir anda.\nAkses tetapan untuk melihat kebenaran yang diberikan"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"Dikemas kini oleh pentadbir anda"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"Dipadamkan oleh pentadbir anda"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Penjimat Bateri menghidupkan tema Gelap dan mengehadkan atau mematikan aktiviti latar, sesetengah kesan visual, ciri tertentu dan sesetengah sambungan rangkaian."</string> diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml index fc6df7ac899c..9a8041644d5f 100644 --- a/core/res/res/values-my/strings.xml +++ b/core/res/res/values-my/strings.xml @@ -1803,8 +1803,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"လက်တစ်ဖက်သုံးမုဒ်"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"ပိုမှိန်ခြင်း"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"နားကြားကိရိယာ"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"အော်တိုနှိပ်ခြင်း"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"ချိတ်ဆက်မထားပါ"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"ချိတ်ဆက်ထားသည်"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"သုံးနေသည်"</string> @@ -1966,7 +1965,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"ပင်မဖြုတ်မီ လော့ခ်ဖွင့်ပုံစံကို မေးရန်"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"ပင်မဖြုတ်မီမှာ စကားဝှက်ကို မေးကြည့်ရန်"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"သင့်စီမံခန့်ခွဲသူက ထည့်သွင်းထားသည်။\nပေးထားသည့် ခွင့်ပြုချက်များကို ကြည့်ရန် ဆက်တင်များသို့ သွားပါ"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"သင်၏ စီမံခန့်ခွဲသူက အပ်ဒိတ်လုပ်ထားသည်"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"သင်၏ စီမံခန့်ခွဲသူက ဖျက်လိုက်ပါပြီ"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"‘ဘက်ထရီ အားထိန်း’ က ‘အမှောင်နောက်ခံ’ ကို ဖွင့်ပြီး နောက်ခံလုပ်ဆောင်ချက်၊ ဖန်တီးပြသချက်အချို့၊ ဝန်ဆောင်မှုအချို့နှင့် ကွန်ရက်ချိတ်ဆက်မှုအချို့တို့ကို ကန့်သတ်သည် သို့မဟုတ် ပိတ်သည်။"</string> diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml index b8cc711fcf46..d8e785f742d5 100644 --- a/core/res/res/values-nb/strings.xml +++ b/core/res/res/values-nb/strings.xml @@ -1803,8 +1803,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Enhåndsmodus"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Ekstra dimmet"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"Høreapparater"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"Autoklikk"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"Frakoblet"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"Tilkoblet"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"Aktiv"</string> @@ -1966,7 +1965,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Krev opplåsingsmønster for å løsne apper"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Krev passord for å løsne apper"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"Installert av administratoren din.\nGå til innstillingene for å se hvilke tillatelser som er gitt"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"Oppdatert av administratoren din"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"Slettet av administratoren din"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Batterisparing slår på mørkt tema og begrenser eller slår av bakgrunnsaktivitet, enkelte visuelle effekter, noen funksjoner og noen nettverkstilkoblinger."</string> diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml index 59071826720d..88e93709453e 100644 --- a/core/res/res/values-ne/strings.xml +++ b/core/res/res/values-ne/strings.xml @@ -1803,8 +1803,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"एक हाते मोड"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"अझै मधुरो"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"हियरिङ डिभाइसहरू"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"अटोक्लिक"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"डिस्कनेक्ट गरिएको"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"कनेक्ट गरिएको"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"सक्रिय"</string> @@ -1966,7 +1965,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"अनपिन गर्नअघि अनलक प्याटर्न माग्नुहोस्"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"पिन निकाल्नुअघि पासवर्ड सोध्नुहोस्"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"यो प्याकेज तपाईंका एड्मिनले इन्स्टल गर्नुभएको हो।\nप्रदान गरिएका अनुमतिसम्बन्धी जानकारी हेर्न सेटिङमा जानुहोस्"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"तपाईंका प्रशासकले अद्यावधिक गर्नुभएको"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"तपाईंका प्रशासकले मेट्नुभएको"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"ठिक छ"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"ब्याट्री सेभरले अँध्यारो थिम अन गर्छ र ब्याकग्राउन्डमा हुने क्रियाकलाप, केही भिजुअल इफेक्ट, निश्चित सुविधा र केही नेटवर्क कनेक्सनहरू अफ गर्छ वा सीमित रूपमा मात्र चल्न दिन्छ।"</string> diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml index 98ce463186ba..a43c0c443c8c 100644 --- a/core/res/res/values-nl/strings.xml +++ b/core/res/res/values-nl/strings.xml @@ -1803,8 +1803,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Bediening met 1 hand"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Extra dimmen"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"Hoortoestellen"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"Automatisch klikken"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"Verbinding verbroken"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"Verbonden"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"Actief"</string> @@ -1966,7 +1965,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Ontgrendelingspatroon vragen om app los te maken"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Vraag wachtwoord voor losmaken"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"Geïnstalleerd door je beheerder.\nGa naar instellingen om verleende rechten te bekijken."</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"Geüpdatet door je beheerder"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"Verwijderd door je beheerder"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Met Batterijbesparing wordt het donkere thema aangezet en worden achtergrondactiviteit, bepaalde visuele effecten, bepaalde functies en sommige netwerkverbindingen beperkt of uitgezet."</string> diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml index 4931bd2e90bf..f61e10919209 100644 --- a/core/res/res/values-or/strings.xml +++ b/core/res/res/values-or/strings.xml @@ -1965,7 +1965,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"ଅନପିନ୍ କରିବା ପୂର୍ବରୁ ଲକ୍ ଖୋଲିବା ପାଟର୍ନ ପଚାରନ୍ତୁ"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"ଅନପିନ୍ କରିବା ପୂର୍ବରୁ ପାସ୍ୱର୍ଡ ପଚାରନ୍ତୁ"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"ଆପଣଙ୍କ ଆଡମିନଙ୍କ ଦ୍ୱାରା ଇନଷ୍ଟଲ କରାଯାଇଛି।\nଅନୁମୋଦିତ ଅମୁମତିଗୁଡ଼ିକ ଭ୍ୟୁ କରିବା ପାଇଁ ସେଟିଂସକୁ ଯାଆନ୍ତୁ"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"ଆପଣଙ୍କ ଆଡମିନ୍ ଅପଡେଟ୍ କରିଛନ୍ତି"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"ଆପଣଙ୍କ ଆଡମିନ୍ ଡିଲିଟ୍ କରିଛନ୍ତି"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"ଠିକ ଅଛି"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"ବେଟେରୀ ସେଭର ଗାଢ଼ା ଥିମକୁ ଚାଲୁ କରେ ଏବଂ ପୃଷ୍ଠପଟ କାର୍ଯ୍ୟକଳାପ, କିଛି ଭିଜୁଆଲ ଇଫେକ୍ଟ, କିଛି ଫିଚର ଏବଂ କିଛି ନେଟୱାର୍କ ସଂଯୋଗକୁ ସୀମିତ କିମ୍ବା ବନ୍ଦ କରେ।"</string> diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml index 80ed484e2523..5d2cc7be3039 100644 --- a/core/res/res/values-pa/strings.xml +++ b/core/res/res/values-pa/strings.xml @@ -1965,7 +1965,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"ਅਨਪਿੰਨ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ ਅਣਲਾਕ ਪੈਟਰਨ ਵਾਸਤੇ ਪੁੱਛੋ"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"ਅਣਪਿੰਨ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ ਪਾਸਵਰਡ ਮੰਗੋ"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"ਤੁਹਾਡੇ ਪ੍ਰਸ਼ਾਸਕ ਵੱਲੋਂ ਸਥਾਪਤ ਕੀਤਾ ਗਿਆ।\nਦਿੱਤੀਆਂ ਗਈਆਂ ਇਜਾਜ਼ਤਾਂ ਨੂੰ ਦੇਖਣ ਲਈ ਸੈਟਿੰਗਾਂ \'ਤੇ ਜਾਓ"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"ਤੁਹਾਡੇ ਪ੍ਰਸ਼ਾਸਕ ਵੱਲੋਂ ਅੱਪਡੇਟ ਕੀਤਾ ਗਿਆ"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"ਤੁਹਾਡੇ ਪ੍ਰਸ਼ਾਸਕ ਵੱਲੋਂ ਮਿਟਾਇਆ ਗਿਆ"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"ਠੀਕ ਹੈ"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"ਬੈਟਰੀ ਸੇਵਰ ਗੂੜ੍ਹੇ ਥੀਮ ਨੂੰ ਚਾਲੂ ਕਰਦਾ ਹੈ ਅਤੇ ਬੈਕਗ੍ਰਾਊਂਡ ਸਰਗਰਮੀ, ਕੁਝ ਦ੍ਰਿਸ਼ਟੀਗਤ ਪ੍ਰਭਾਵਾਂ, ਕੁਝ ਖਾਸ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਅਤੇ ਕੁਝ ਨੈੱਟਵਰਕ ਕਨੈਕਸ਼ਨਾਂ ਨੂੰ ਸੀਮਤ ਜਾਂ ਬੰਦ ਕਰਦਾ ਹੈ।"</string> diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml index efae947b3793..bb7edb8a79ad 100644 --- a/core/res/res/values-pl/strings.xml +++ b/core/res/res/values-pl/strings.xml @@ -1805,8 +1805,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Tryb jednej ręki"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Dodatkowe przyciemnienie"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"Urządzenia słuchowe"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"Automatyczne kliknięcie"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"Rozłączone"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"Połączone"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"Aktywne"</string> @@ -1968,7 +1967,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Aby odpiąć, poproś o wzór odblokowania"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Aby odpiąć, poproś o hasło"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"Zainstalowany przez administratora.\nOtwórz ustawienia, aby wyświetlić przyznane uprawnienia"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"Zaktualizowany przez administratora"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"Usunięty przez administratora"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Oszczędzanie baterii uruchamia ciemny motyw oraz wyłącza lub ogranicza aktywność w tle, niektóre efekty wizualne, pewne funkcje oraz wybrane połączenia sieciowe."</string> diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml index 4036d636ae5c..0440e39a9dd5 100644 --- a/core/res/res/values-pt-rBR/strings.xml +++ b/core/res/res/values-pt-rBR/strings.xml @@ -1804,8 +1804,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Modo para uma mão"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Tela ainda mais escura"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"Aparelhos auditivos"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"Clique automático"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"Desconectado"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"Conectado"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"Ativo"</string> @@ -1967,7 +1966,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Pedir padrão de desbloqueio antes de liberar"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Pedir senha antes de liberar"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"Instalado pelo administrador.\nAcesse as configurações para conferir as permissões concedidas"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"Atualizado pelo seu administrador"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"Excluído pelo seu administrador"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"A Economia de bateria ativa o tema escuro e limita ou desativa atividades em segundo plano, alguns efeitos visuais, recursos específicos e algumas conexões de rede."</string> diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml index df3f9c6e2b63..745e17be6c28 100644 --- a/core/res/res/values-pt-rPT/strings.xml +++ b/core/res/res/values-pt-rPT/strings.xml @@ -1804,8 +1804,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Modo para uma mão"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Mais escuro"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"Dispositivos auditivos"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"Clique automático"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"Desligado"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"Ligado"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"Ativo"</string> @@ -1967,7 +1966,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Pedir padrão de desbloqueio antes de soltar"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Pedir palavra-passe antes de soltar"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"Instalado pelo seu administrador.\nAceda às definições para ver as autorizações concedidas"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"Atualizado pelo seu gestor"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"Eliminado pelo seu gestor"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"A Poupança de bateria ativa o tema escuro e limita ou desativa a atividade em segundo plano, alguns efeitos visuais, determinadas funcionalidades e algumas ligações de rede."</string> diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml index 4036d636ae5c..0440e39a9dd5 100644 --- a/core/res/res/values-pt/strings.xml +++ b/core/res/res/values-pt/strings.xml @@ -1804,8 +1804,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Modo para uma mão"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Tela ainda mais escura"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"Aparelhos auditivos"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"Clique automático"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"Desconectado"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"Conectado"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"Ativo"</string> @@ -1967,7 +1966,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Pedir padrão de desbloqueio antes de liberar"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Pedir senha antes de liberar"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"Instalado pelo administrador.\nAcesse as configurações para conferir as permissões concedidas"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"Atualizado pelo seu administrador"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"Excluído pelo seu administrador"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"A Economia de bateria ativa o tema escuro e limita ou desativa atividades em segundo plano, alguns efeitos visuais, recursos específicos e algumas conexões de rede."</string> diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml index b12af3960d65..4ef0423b327b 100644 --- a/core/res/res/values-ro/strings.xml +++ b/core/res/res/values-ro/strings.xml @@ -1804,8 +1804,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Modul cu o mână"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Luminozitate redusă suplimentar"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"Aparate auditive"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"Clic automat"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"Deconectat"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"Conectat"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"Activ"</string> @@ -1967,7 +1966,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Solicită mai întâi modelul pentru deblocare"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Solicită parola înainte de a anula fixarea"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"Instalat de administrator.\nAccesează setările ca să vezi permisiunile acordate."</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"Actualizat de administrator"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"Șters de administrator"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Economisirea bateriei activează tema întunecată și restricționează sau dezactivează activitatea în fundal, unele efecte vizuale, alte funcții și câteva conexiuni la rețea."</string> diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml index 4911fc60d23e..a03963ef9924 100644 --- a/core/res/res/values-ru/strings.xml +++ b/core/res/res/values-ru/strings.xml @@ -1805,8 +1805,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Режим управления одной рукой"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Дополнительное уменьшение яркости"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"Слуховые аппараты"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"Автонажатие"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"Отключено"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"Подключено"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"Активно"</string> @@ -1968,7 +1967,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Запрашивать графический ключ"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Запрашивать пароль"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"Установлено администратором.\nЧтобы посмотреть предоставленные разрешения, перейдите в настройки."</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"Обновлено администратором"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"Удалено администратором"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"ОК"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"В режиме энергосбережения включается тёмная тема, ограничиваются или отключаются фоновые процессы, а также некоторые визуальные эффекты, часть функций и сетевых подключений."</string> diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml index b11e442bfeb8..571706e325c9 100644 --- a/core/res/res/values-si/strings.xml +++ b/core/res/res/values-si/strings.xml @@ -1803,8 +1803,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"තනි අත් ප්රකාරය"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"තවත් අඳුරු"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"ශ්රවණ උපාංග"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"ස්වයං ක්ලික් කිරීම"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"විසන්ධි විය"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"සම්බන්ධිතයි"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"සක්රිය"</string> @@ -1966,7 +1965,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"ගැලවීමට පෙර අගුළු අරින රටාව සඳහා අසන්න"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"ගැලවීමට පෙර මුරපදය විමසන්න"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"ඔබේ පරිපාලකයා විසින් ස්ථාපන කරනු ලබයි.\nදෙන ලද අවසර බැලීමට සැකසීම් වෙත යන්න"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"ඔබගේ පරිපාලක මඟින් යාවත්කාලීන කර ඇත"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"ඔබගේ පරිපාලක මඟින් මකා දමා ඇත"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"හරි"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"බැටරි සුරැකුම අඳුරු තේමාව ක්රියාත්මක කර පසුබිම් ක්රියාකාරකම්, සමහර දෘශ්ය ප්රයෝග, යම් විශේෂාංග සහ සමහර ජාල සම්බන්ධතා සීමා හෝ ක්රියාවිරහිත කරයි."</string> diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml index 47bee0ad1bc9..bcbd66f55412 100644 --- a/core/res/res/values-sk/strings.xml +++ b/core/res/res/values-sk/strings.xml @@ -1805,8 +1805,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Režim jednej ruky"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Mimoriadne stmavenie"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"Načúvacie zariadenia"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"Automatické kliknutie"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"Odpojené"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"Pripojené"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"Aktívne"</string> @@ -1968,7 +1967,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Pred uvoľnením požiadať o bezpečnostný vzor"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Pred odopnutím požiadať o heslo"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"Nainštaloval správca.\nAk si chcete zobraziť udelené povolenia, prejdite do nastavení."</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"Aktualizoval správca"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"Odstránil správca"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Šetrič batérie zapne tmavý motív a obmedzí alebo vypne aktivitu na pozadí, niektoré vizuálne efekty, určité funkcie a niektoré pripojenia k sieti."</string> diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml index 208c17463ce4..c0d18ed0a067 100644 --- a/core/res/res/values-sl/strings.xml +++ b/core/res/res/values-sl/strings.xml @@ -1805,8 +1805,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Enoročni način"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Zelo zatemnjen zaslon"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"Slušni pripomočki"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"Samodejni klik"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"Brez povezave"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"Povezano"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"Aktivno"</string> @@ -1968,7 +1967,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Pred odpenjanjem vprašaj za vzorec za odklepanje"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Pred odpenjanjem vprašaj za geslo"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"Namestil skrbnik.\nV nastavitvah si oglejte odobrena dovoljenja."</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"Posodobil skrbnik"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"Izbrisal skrbnik"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"V redu"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Funkcija varčevanja z energijo baterije vklopi temno temo ter omeji ali izklopi dejavnost v ozadju, nekatere vizualne učinke, določene funkcije in nekatere omrežne povezave."</string> diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml index 5f8490882bec..ddccedeaa36c 100644 --- a/core/res/res/values-sq/strings.xml +++ b/core/res/res/values-sq/strings.xml @@ -1803,8 +1803,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Modaliteti i përdorimit me një dorë"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Shumë më i zbehtë"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"Pajisjet e dëgjimit"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"Klikimi automatik"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"Shkëputur"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"Lidhur"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"Aktive"</string> @@ -1966,7 +1965,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Kërko motivin e shkyçjes para heqjes së gozhdimit"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Kërko fjalëkalim para heqjes nga gozhdimi."</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"Instaluar nga administratori.\nShko te cilësimet për të shikuar lejet e dhëna"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"Përditësuar nga administratori"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"Fshirë nga administratori"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"Në rregull"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"\"Kursyesi i baterisë\" aktivizon \"Temën e errët\" dhe kufizon ose çaktivizon aktivitetin në sfond, disa efekte vizuale, veçori të caktuara dhe disa lidhje të rrjetit."</string> diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml index 61102d3bb076..397a39458580 100644 --- a/core/res/res/values-sr/strings.xml +++ b/core/res/res/values-sr/strings.xml @@ -1966,7 +1966,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Тражи шаблон за откључавање пре откачињања"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Тражи лозинку пре откачињања"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"Инсталирао је администратор.\nИдите у подешавања да бисте видели одобрене дозволе"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"Ажурирао је администратор"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"Избрисао је администратор"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"Потврди"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Уштеда батерије укључује тамну тему и ограничава или искључује активности у позадини, неке визуелне ефекте, одређене функције и неке мрежне везе."</string> diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml index d5fc6948aa6f..71062addb40f 100644 --- a/core/res/res/values-sv/strings.xml +++ b/core/res/res/values-sv/strings.xml @@ -1803,8 +1803,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Enhandsläge"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Extradimmat"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"Hörhjälpmedel"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"Automatiskt klick"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"Frånkopplad"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"Ansluten"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"Aktiv"</string> @@ -1966,7 +1965,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Be om upplåsningsmönster innan skärmen slutar fästas"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Be om lösenord innan skärmen slutar fästas"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"Har installerats av administratören.\nÖppna inställningarna för att se behörigheter som beviljats"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"Administratören uppdaterade paketet"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"Administratören raderade paketet"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"I batterisparläget aktiveras mörkt tema medan bakgrundsaktivitet, vissa visuella effekter och funktioner samt vissa nätverksanslutningar begränsas eller inaktiveras."</string> diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml index 49e9f1cd64d5..e74ac1858d76 100644 --- a/core/res/res/values-sw/strings.xml +++ b/core/res/res/values-sw/strings.xml @@ -1803,8 +1803,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Hali ya kutumia kwa mkono mmoja"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Kipunguza mwangaza zaidi"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"Vifaa vya kusaidia kusikia"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"Kubofya kiotomatiki"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"Haijaunganishwa"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"Imeunganishwa"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"Inatumika"</string> @@ -1966,7 +1965,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Omba mchoro wa kufungua kabla hujabandua"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Omba nenosiri kabla hujabandua"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"Kimewekwa na msimamizi wako.\nNenda kwenye mipangilio ili uone ruhusa zilizotolewa"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"Imesasishwa na msimamizi wako"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"Imefutwa na msimamizi wako"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"Sawa"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Kiokoa Betri huwasha Mandhari meusi na kudhibiti au kuzima shughuli za chinichini, baadhi ya madoido yanayoonekana, vipengele fulani na baadhi ya miunganisho ya mtandao."</string> diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml index dad40dc8f4c9..7737d8f6da45 100644 --- a/core/res/res/values-ta/strings.xml +++ b/core/res/res/values-ta/strings.xml @@ -1803,8 +1803,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"ஒற்றைக் கைப் பயன்முறை"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"மிகக் குறைவான வெளிச்சம்"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"செவித்துணைக் கருவிகள்"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"ஆட்டோ கிளிக்"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"இணைப்புநீக்கப்பட்டது"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"இணைக்கப்பட்டுள்ளது"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"செயலில் உள்ளது"</string> @@ -1966,7 +1965,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"அகற்றும் முன் அன்லாக் பேட்டர்னைக் கேள்"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"அகற்றும் முன் கடவுச்சொல்லைக் கேள்"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"உங்கள் நிர்வாகி நிறுவியுள்ளார்.\nவழங்கப்பட்டுள்ள அனுமதிகளை பார்க்க அமைப்புகளுக்குச் செல்லவும்"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"உங்கள் நிர்வாகி புதுப்பித்துள்ளார்"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"உங்கள் நிர்வாகி நீக்கியுள்ளார்"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"சரி"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"பேட்டரி சேமிப்பான் அம்சம் டார்க் தீமை இயக்குவதோடு பின்னணிச் செயல்பாடு, சில விஷுவல் எஃபக்ட்கள், குறிப்பிட்ட அம்சங்கள், சில நெட்வொர்க் இணைப்புகள் ஆகியவற்றைக் கட்டுப்படுத்தும் அல்லது முடக்கும்."</string> diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml index 322a703ccdbe..265fb8c9b382 100644 --- a/core/res/res/values-te/strings.xml +++ b/core/res/res/values-te/strings.xml @@ -1803,8 +1803,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"వన్-హ్యాండెడ్ మోడ్"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"ఎక్స్ట్రా డిమ్"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"వినికిడి పరికరాలు"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"ఆటో-క్లిక్"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"డిస్కనెక్ట్ అయింది"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"కనెక్ట్ చేయబడింది"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"యాక్టివ్"</string> @@ -1966,7 +1965,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"అన్పిన్ చేయడానికి ముందు అన్లాక్ ఆకృతి కోసం అడుగు"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"అన్పిన్ చేయడానికి ముందు పాస్వర్డ్ కోసం అడుగు"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"మీ అడ్మిన్ ఇన్స్టాల్ చేశారు.\nసెట్టింగ్లకు వెళ్లి, మంజూరు చేసిన అనుమతులు చూడండి"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"మీ నిర్వాహకులు అప్డేట్ చేశారు"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"మీ నిర్వాహకులు తొలగించారు"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"సరే"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"బ్యాటరీ సేవర్ ముదురు రంగు రూపాన్ని ఆన్ చేసి, బ్యాక్గ్రౌండ్ యాక్టివిటీ, కొన్ని విజువల్ ఎఫెక్ట్లు, నిర్దిష్ట ఫీచర్లు, ఇంకా కొన్ని నెట్వర్క్ కనెక్షన్లను పరిమితం చేస్తుంది లేదా ఆఫ్ చేస్తుంది."</string> diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml index 107a59fb2829..a922943f54c7 100644 --- a/core/res/res/values-th/strings.xml +++ b/core/res/res/values-th/strings.xml @@ -1965,7 +1965,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"ขอรูปแบบการปลดล็อกก่อนเลิกปักหมุด"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"ขอรหัสผ่านก่อนเลิกปักหมุด"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"ติดตั้งโดยผู้ดูแลระบบของคุณ\nไปที่การตั้งค่าเพื่อดูสิทธิ์ที่ได้รับอนุญาต"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"อัปเดตโดยผู้ดูแลระบบ"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"ลบโดยผู้ดูแลระบบ"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"ตกลง"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"โหมดประหยัดแบตเตอรี่จะเปิดธีมมืดและจำกัดหรือปิดกิจกรรมในเบื้องหลัง เอฟเฟกต์ภาพบางอย่าง ฟีเจอร์บางส่วน และการเชื่อมต่อบางเครือข่าย"</string> diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml index 97883270cda7..5e3e1e86c713 100644 --- a/core/res/res/values-tl/strings.xml +++ b/core/res/res/values-tl/strings.xml @@ -1803,8 +1803,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"One-Hand mode"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Extra dim"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"Mga hearing device"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"Autoclick"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"Nadiskonekta"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"Nakakonekta"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"Aktibo"</string> @@ -1966,7 +1965,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Humingi ng pattern sa pag-unlock bago mag-unpin"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Humingi ng password bago mag-unpin"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"Na-install ng iyong admin.\nPumunta sa mga setting para makita ang mga ibinigay na pahintulot"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"Na-update ng iyong admin"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"Na-delete ng iyong admin"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Ino-on ng Pantipid ng Baterya ang Madilim na tema at nililimitahan o ino-off nito ang aktibidad sa background, ilang visual effect, ilang partikular na feature, at ilang koneksyon sa network."</string> diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml index 095a3da72ae1..72d62d5b2499 100644 --- a/core/res/res/values-tr/strings.xml +++ b/core/res/res/values-tr/strings.xml @@ -1803,8 +1803,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Tek El modu"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Ekstra loş"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"İşitme cihazları"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"Otomatik tıklama"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"Bağlı değil"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"Bağlı"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"Etkin"</string> @@ -1966,7 +1965,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Sabitlemeyi kaldırmadan önce kilit açma desenini sor"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Sabitlemeyi kaldırmadan önce şifre sor"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"Yöneticiniz tarafından yüklendi.\nVerilen izinleri görüntülemek için ayarlara gidin"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"Yöneticiniz tarafından güncellendi"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"Yöneticiniz tarafından silindi"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"Tamam"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Pil Tasarrufu, Koyu temayı açıp arka plan etkinliğini, bazı görsel efektleri, belirli özellikleri ve bazı ağ bağlantılarını sınırlandırır veya kapatır."</string> diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml index bc05f49e0e6f..e4e1e99be263 100644 --- a/core/res/res/values-uk/strings.xml +++ b/core/res/res/values-uk/strings.xml @@ -1805,8 +1805,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Режим керування однією рукою"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Додаткове зменшення яскравості"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"Слухові апарати"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"Автоматичне натискання"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"Від’єднано"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"Під’єднано"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"Активний"</string> @@ -1968,7 +1967,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Запитувати ключ розблокування перед відкріпленням"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Запитувати пароль перед відкріпленням"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"Установлено адміністратором.\nПерейдіть у налаштування, щоб переглянути надані дозволи."</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"Оновлено адміністратором"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"Видалено адміністратором"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"ОК"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"У режимі енергозбереження вмикається темна тема й обмежуються чи вимикаються дії у фоновому режимі, а також деякі візуальні ефекти, функції та з’єднання з мережами."</string> diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml index 7794c9af1f1f..a98179a3064b 100644 --- a/core/res/res/values-ur/strings.xml +++ b/core/res/res/values-ur/strings.xml @@ -1803,8 +1803,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"ایک ہاتھ کی وضع"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"اضافی مدھم"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"سماعتی آلات"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"خودکار کلک"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"غیر منسلک ہے"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"منسلک ہے"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"فعال"</string> @@ -1966,7 +1965,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"پن ہٹانے سے پہلے غیر مقفل کرنے کا پیٹرن طلب کریں"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"پن ہٹانے سے پہلے پاس ورڈ طلب کریں"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"آپ کے منتظم نے انسٹال کیا ہے۔\nدی گئی اجازتیں دیکھنے کیلئے ترتیبات پر جائیں"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"آپ کے منتظم کے ذریعے اپ ڈیٹ کیا گیا"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"آپ کے منتظم کے ذریعے حذف کیا گیا"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"ٹھیک ہے"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"بیٹری سیور گہری تھیم کو آن کرتی ہے اور پس منظر کی سرگرمی، کچھ بصری اثرات، مخصوص خصوصیات اور کچھ نیٹ ورک کنکشنز کو محدود یا آف کرتی ہے۔"</string> diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml index 95a44bbd5c3d..81ebe5fa7c2b 100644 --- a/core/res/res/values-uz/strings.xml +++ b/core/res/res/values-uz/strings.xml @@ -1803,8 +1803,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Ixcham rejim"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Juda xira"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"Eshitish qurilmalari"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"Avtoklik"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"Uzildi"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"Ulandi"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"Faol"</string> @@ -1966,7 +1965,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Yechishdan oldin grafik kalit so‘ralsin"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Bo‘shatishdan oldin parol so‘ralsin"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"Administrator oʻrnatgan.\nBerilgan ruxsatlarni koʻrish uchun sozlamalarni oching"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"Administrator tomonidan yangilangan"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"Administrator tomonidan o‘chirilgan"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Quvvat tejash funksiyasi Tungi mavzuni va cheklovlarni yoqadi hamda fondagi harakatlar, vizual effektlar, ayrim funksiyalar va tarmoq aloqalari kabi boshqa funksiyalarni faolsizlantiradi yoki cheklaydi."</string> diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml index bb9b797e9ec2..4dda8cc0c8c1 100644 --- a/core/res/res/values-vi/strings.xml +++ b/core/res/res/values-vi/strings.xml @@ -1803,8 +1803,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Chế độ một tay"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Siêu tối"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"Thiết bị trợ thính"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"Tự động nhấp"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"Đã ngắt kết nối"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"Đã kết nối"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"Đang hoạt động"</string> @@ -1966,7 +1965,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Hỏi hình mở khóa trước khi bỏ ghim"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Hỏi mật khẩu trước khi bỏ ghim"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"Do quản trị viên của bạn cài đặt.\nChuyển đến phần cài đặt để xem các quyền được cấp"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"Do quản trị viên của bạn cập nhật"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"Do quản trị viên của bạn xóa"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Trình tiết kiệm pin sẽ bật Giao diện tối, đồng thời hạn chế hoặc tắt hoạt động chạy trong nền, một số hiệu ứng hình ảnh, các tính năng nhất định và một số đường kết nối mạng."</string> diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml index 108d62eb072c..851195acf204 100644 --- a/core/res/res/values-zh-rCN/strings.xml +++ b/core/res/res/values-zh-rCN/strings.xml @@ -1803,8 +1803,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"单手模式"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"极暗"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"助听装置"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"自动点击"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"已断开连接"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"已连接"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"活跃"</string> @@ -1966,7 +1965,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"取消固定前要求绘制解锁图案"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"取消时要求输入密码"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"由您的管理员安装。\n前往设置可查看已授予的权限"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"已由您的管理员更新"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"已由您的管理员删除"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"确定"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"在省电模式下,系统会启用深色主题,并限制或关闭后台活动、某些视觉效果、特定功能和部分网络连接。"</string> diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml index fc9e6e8e506d..87b403d39207 100644 --- a/core/res/res/values-zh-rHK/strings.xml +++ b/core/res/res/values-zh-rHK/strings.xml @@ -1803,8 +1803,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"單手模式"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"超暗"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"助聽器"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"自動點擊"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"已中斷連線"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"已連線"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"運作中"</string> @@ -1966,7 +1965,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"取消固定時必須提供解鎖圖案"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"取消固定時必須輸入密碼"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"已由你的管理員安裝。\n請前往設定查看已授予的權限"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"已由你的管理員更新"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"已由你的管理員刪除"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"好"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"「慳電模式」會開啟深色主題背景,並限制或關閉背景活動、部分視覺效果、特定功能和部分網絡連線。"</string> diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml index d2ca941d7dd2..72d2e7ce3cc1 100644 --- a/core/res/res/values-zh-rTW/strings.xml +++ b/core/res/res/values-zh-rTW/strings.xml @@ -1803,8 +1803,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"單手模式"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"超暗"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"助聽器"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"自動點選"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"連線中斷"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"已連線"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"運作中"</string> @@ -1966,7 +1965,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"取消固定時必須畫出解鎖圖案"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"取消固定時必須輸入密碼"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"這是管理員安裝的套件。\n你可以前往設定查看授予的權限"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"已由你的管理員更新"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"已由你的管理員刪除"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"確定"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"省電模式會開啟深色主題,並限制或關閉背景活動、某些視覺效果、特定功能和部分網路連線。"</string> diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml index e11683200d30..13356cbcd356 100644 --- a/core/res/res/values-zu/strings.xml +++ b/core/res/res/values-zu/strings.xml @@ -1803,8 +1803,7 @@ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Imodi yesandla esisodwa"</string> <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Ukufiphaza okwengeziwe"</string> <string name="hearing_aids_feature_name" msgid="1125892105105852542">"Amadivayizi okuzwa"</string> - <!-- no translation found for autoclick_feature_name (8149248738736949630) --> - <skip /> + <string name="autoclick_feature_name" msgid="8149248738736949630">"Chofoza ngokuzenzekelayo"</string> <string name="hearing_device_status_disconnected" msgid="497547752953543832">"Inqamukile"</string> <string name="hearing_device_status_connected" msgid="2149385149669918764">"Ixhunyiwe"</string> <string name="hearing_device_status_active" msgid="4770378695482566032">"Kuyasebenza"</string> @@ -1966,7 +1965,8 @@ <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Cela iphethini yokuvula ngaphambi kokususa ukuphina"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Cela iphasiwedi ngaphambi kokususa ukuphina"</string> <string name="package_installed_device_owner" msgid="8684974629306529138">"Kufakwe ngumphathi wakho.\nIya kumasethingi ukuze ubuke izimvume ezinikeziwe"</string> - <string name="package_updated_device_owner" msgid="7560272363805506941">"Kubuyekezwe umlawuli wakho"</string> + <!-- no translation found for package_updated_device_owner (7770195449213776218) --> + <skip /> <string name="package_deleted_device_owner" msgid="2292335928930293023">"Kususwe umlawuli wakho"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"KULUNGILE"</string> <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Isilondolozi Sebhethri sivula ingqikithi emnyama futhi sibeke umkhawulo noma sivale umsebenzi ongemuva, imiphumela ethile yokubuka, izici ezithile, nokuxhumeka okuthile kwenethiwekhi."</string> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 9773f557dfaa..7a38dce296de 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2812,6 +2812,20 @@ <integer name="config_dreamsBatteryLevelDrainCutoff">5</integer> <!-- Limit of how long the device can remain unlocked due to attention checking. --> <integer name="config_attentionMaximumExtension">900000</integer> <!-- 15 minutes. --> + + <!-- Enables or disables the 'prevent screen timeout' feature, where when a user manually + undims the screen, the feature acquires a wakelock to prevent screen timeout. + false = disabled, true = enabled. Disabled by default. --> + <bool name="config_defaultPreventScreenTimeoutEnabled">false</bool> + <!-- Default value (in milliseconds) to prevent the screen timeout after user undims it + manually between screen dims, a sign the user is interacting with the device. --> + <integer name="config_defaultPreventScreenTimeoutForMillis">300000</integer> <!-- 5 minutes. --> + <!-- Default max duration (in milliseconds) of the time between undims to still consider them + consecutive. --> + <integer name="config_defaultMaxDurationBetweenUndimsMillis">600000</integer> <!-- 10 min. --> + <!-- Default number of user undims required to trigger preventing screen sleep. --> + <integer name="config_defaultUndimsRequired">2</integer> + <!-- Whether there is to be a chosen Dock User who is the only user allowed to dream. --> <bool name="config_dreamsOnlyEnabledForDockUser">false</bool> <!-- Whether dreams are disabled when ambient mode is suppressed. --> @@ -4170,6 +4184,11 @@ <!-- Whether device supports double tap to wake --> <bool name="config_supportDoubleTapWake">false</bool> + <!-- Whether device supports double tap to sleep. This will allow the user to enable/disable + double tap gestures in non-action areas in the lock screen and launcher workspace to go to + sleep. --> + <bool name="config_supportDoubleTapSleep">false</bool> + <!-- The RadioAccessFamilies supported by the device. Empty is viewed as "all". Only used on devices which don't support RIL_REQUEST_GET_RADIO_CAPABILITY diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index a1961aedf6b7..465e318511c6 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -390,6 +390,16 @@ <!-- The absolute size of the notification expand icon. --> <dimen name="notification_header_expand_icon_size">56dp</dimen> + <!-- Margin to allow space for the expand button when showing the right icon in expanded --> + <!-- notifications. This is equal to notification_2025_expand_button_pill_width --> + <!-- + notification_2025_margin (end padding for expand button) --> + <!-- + notification_2025_expand_button_right_icon_spacing (space between pill and icon) --> + <dimen name="notification_2025_right_icon_expanded_margin_end">52dp</dimen> + + <!-- The large icon has a smaller vertical margin than most other notification content, to --> + <!-- allow it to grow up to 48dp. --> + <dimen name="notification_2025_right_icon_vertical_margin">12dp</dimen> + <!-- the height of the expand button pill --> <dimen name="notification_expand_button_pill_height">24dp</dimen> @@ -411,15 +421,24 @@ <!-- the padding of the expand icon in the notification header --> <dimen name="notification_2025_expand_button_horizontal_icon_padding">6dp</dimen> - <!-- a smaller padding for the end of the expand button, for use when showing the number --> + <!-- smaller padding for the end of the expand icon, for use when showing the number --> <dimen name="notification_2025_expand_button_reduced_end_padding">4dp</dimen> + <!-- the space needed between the expander pill and the large icon when visible --> + <dimen name="notification_2025_expand_button_right_icon_spacing">8dp</dimen> + <!-- the size of the notification close button --> <dimen name="notification_close_button_size">16dp</dimen> <!-- Margin for all notification content --> <dimen name="notification_2025_margin">16dp</dimen> + <!-- A smaller version of the margin to be used when we need more space for the content --> + <dimen name="notification_2025_reduced_margin">12dp</dimen> + + <!-- The difference between the usual margin and the reduced margin --> + <dimen name="notification_2025_additional_margin">4dp</dimen> + <!-- Vertical margin for the headerless notification content, when content has 1 line --> <!-- 16 * 2 (margins) + 24 (1 line) = 56 (notification) --> <dimen name="notification_headerless_margin_oneline">16dp</dimen> @@ -438,7 +457,7 @@ <dimen name="notification_collapsed_height_with_summarization">156dp</dimen> <!-- Max height of a collapsed (headerless) notification with one or two lines --> - <!-- 16 * 2 (margins) + 48 (min content height) = 72 (notification) --> + <!-- 14 * 2 (reduced margins) + 48 (max collapsed content height) = 72 (notification) --> <dimen name="notification_2025_min_height">72dp</dimen> <!-- Height of a headerless notification with one line --> @@ -729,6 +748,9 @@ <!-- The accessibility autoclick panel divider height --> <dimen name="accessibility_autoclick_type_panel_divider_height">24dp</dimen> + <!-- The accessibility autoclick scroll panel button width and height --> + <dimen name="accessibility_autoclick_scroll_panel_button_size">36dp</dimen> + <!-- Margin around the various security views --> <dimen name="keyguard_muliuser_selector_margin">8dp</dimen> @@ -893,6 +915,8 @@ <dimen name="notification_right_icon_size">48dp</dimen> <!-- The margin between the right icon and the content. --> <dimen name="notification_right_icon_content_margin">12dp</dimen> + <!-- The margin between the right icon and the content. (2025 redesign version) --> + <dimen name="notification_2025_right_icon_content_margin">16dp</dimen> <!-- The top and bottom margin of the right icon in the normal notification states --> <dimen name="notification_right_icon_headerless_margin">20dp</dimen> <!-- The top margin of the right icon in the "big" notification states --> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index b3cc8837e0bf..d94d659446ac 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -6226,6 +6226,18 @@ <string name="accessibility_autoclick_pause">Pause</string> <!-- Label for autoclick position button [CHAR LIMIT=NONE] --> <string name="accessibility_autoclick_position">Position</string> + <!-- Label for autoclick scroll up button [CHAR LIMIT=NONE] --> + <string name="accessibility_autoclick_scroll_up">Scroll Up</string> + <!-- Label for autoclick scroll down button [CHAR LIMIT=NONE] --> + <string name="accessibility_autoclick_scroll_down">Scroll Down</string> + <!-- Label for autoclick scroll left button [CHAR LIMIT=NONE] --> + <string name="accessibility_autoclick_scroll_left">Scroll Left</string> + <!-- Label for autoclick scroll right button [CHAR LIMIT=NONE] --> + <string name="accessibility_autoclick_scroll_right">Scroll Right</string> + <!-- Label for autoclick scroll exit button [CHAR LIMIT=NONE] --> + <string name="accessibility_autoclick_scroll_exit">Exit Scroll Mode</string> + <!-- Label for autoclick scroll panel title [CHAR LIMIT=NONE] --> + <string name="accessibility_autoclick_scroll_panel_title">Scroll Panel</string> <!-- Text to tell the user that a package has been forced by themselves in the RESTRICTED bucket. [CHAR LIMIT=NONE] --> <string name="as_app_forced_to_restricted_bucket"> @@ -6698,6 +6710,8 @@ ul.</string> <string name="profile_label_test">Test</string> <!-- Communal profile label on a screen. This can be used as a tab label for this profile in tabbed views and can be used to represent the profile in sharing surfaces, etc. [CHAR LIMIT=20] --> <string name="profile_label_communal">Communal</string> + <!-- Supervising profile label on a screen. This can be used as a tab label for this profile in tabbed views and can be used to represent the profile in sharing surfaces, etc. [CHAR LIMIT=20] --> + <string name="profile_label_supervising">Supervising</string> <!-- Accessibility label for managed profile user type [CHAR LIMIT=30] --> <string name="accessibility_label_managed_profile">Work profile</string> diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml index ee1edda838fd..1c3529fa7258 100644 --- a/core/res/res/values/styles.xml +++ b/core/res/res/values/styles.xml @@ -1761,6 +1761,19 @@ please see styles_device_defaults.xml. <item name="android:tint">@color/materialColorPrimary</item> </style> + <style name="AccessibilityAutoclickScrollPanelButtonLayoutStyle"> + <item name="android:gravity">center</item> + <item name="android:layout_width">@dimen/accessibility_autoclick_scroll_panel_button_size </item> + <item name="android:layout_height">@dimen/accessibility_autoclick_scroll_panel_button_size</item> + </style> + + <style name="AccessibilityAutoclickScrollPanelImageButtonStyle"> + <item name="android:gravity">center</item> + <item name="android:layout_width">wrap_content</item> + <item name="android:layout_height">wrap_content</item> + <item name="android:scaleType">center</item> + </style> + <!-- TODO(b/309578419): Make activities go edge-to-edge properly and then remove this. --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 2b7261b62c64..46d18e3d3302 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1129,6 +1129,7 @@ <java-symbol type="string" name="profile_label_work_3" /> <java-symbol type="string" name="profile_label_test" /> <java-symbol type="string" name="profile_label_communal" /> + <java-symbol type="string" name="profile_label_supervising" /> <java-symbol type="string" name="accessibility_label_managed_profile" /> <java-symbol type="string" name="accessibility_label_private_profile" /> <java-symbol type="string" name="accessibility_label_clone_profile" /> @@ -3141,6 +3142,7 @@ <java-symbol type="color" name="chooser_row_divider" /> <java-symbol type="layout" name="chooser_row_direct_share" /> <java-symbol type="bool" name="config_supportDoubleTapWake" /> + <java-symbol type="bool" name="config_supportDoubleTapSleep" /> <java-symbol type="drawable" name="ic_perm_device_info" /> <java-symbol type="string" name="config_radio_access_family" /> <java-symbol type="string" name="notification_inbox_ellipsis" /> @@ -3267,6 +3269,8 @@ <java-symbol type="dimen" name="notification_2025_content_margin_start" /> <java-symbol type="dimen" name="notification_2025_expand_button_horizontal_icon_padding" /> <java-symbol type="dimen" name="notification_2025_expand_button_reduced_end_padding" /> + <java-symbol type="dimen" name="notification_2025_expand_button_right_icon_spacing" /> + <java-symbol type="dimen" name="notification_2025_right_icon_expanded_margin_end" /> <java-symbol type="dimen" name="notification_progress_margin_horizontal" /> <java-symbol type="dimen" name="notification_header_background_height" /> <java-symbol type="dimen" name="notification_header_touchable_height" /> @@ -3957,6 +3961,7 @@ <java-symbol type="dimen" name="notification_big_picture_max_width"/> <java-symbol type="dimen" name="notification_right_icon_size"/> <java-symbol type="dimen" name="notification_right_icon_content_margin"/> + <java-symbol type="dimen" name="notification_2025_right_icon_content_margin"/> <java-symbol type="dimen" name="notification_actions_icon_drawable_size"/> <java-symbol type="dimen" name="notification_custom_view_max_image_height"/> <java-symbol type="dimen" name="notification_custom_view_max_image_width"/> @@ -4442,6 +4447,11 @@ <!-- For Attention Service --> <java-symbol type="integer" name="config_attentionMaximumExtension" /> + <java-symbol type="bool" name="config_defaultPreventScreenTimeoutEnabled" /> + <java-symbol type="integer" name="config_defaultPreventScreenTimeoutForMillis" /> + <java-symbol type="integer" name="config_defaultMaxDurationBetweenUndimsMillis" /> + <java-symbol type="integer" name="config_defaultUndimsRequired" /> + <java-symbol type="string" name="config_incidentReportApproverPackage" /> <java-symbol type="array" name="config_restrictedImagesServices" /> @@ -5666,6 +5676,32 @@ <java-symbol type="drawable" name="accessibility_autoclick_resume" /> <java-symbol type="drawable" name="ic_accessibility_autoclick" /> + <!-- Accessibility autoclick scroll panel related --> + <java-symbol type="layout" name="accessibility_autoclick_scroll_panel" /> + <java-symbol type="dimen" name="accessibility_autoclick_scroll_panel_button_size" /> + <java-symbol type="drawable" name="accessibility_autoclick_scroll_up" /> + <java-symbol type="drawable" name="accessibility_autoclick_scroll_down" /> + <java-symbol type="drawable" name="accessibility_autoclick_scroll_left" /> + <java-symbol type="drawable" name="accessibility_autoclick_scroll_right" /> + <java-symbol type="drawable" name="accessibility_autoclick_scroll_exit" /> + <java-symbol type="string" name="accessibility_autoclick_scroll_up" /> + <java-symbol type="string" name="accessibility_autoclick_scroll_down" /> + <java-symbol type="string" name="accessibility_autoclick_scroll_left" /> + <java-symbol type="string" name="accessibility_autoclick_scroll_right" /> + <java-symbol type="string" name="accessibility_autoclick_scroll_exit" /> + <java-symbol type="string" name="accessibility_autoclick_scroll_panel_title" /> + <java-symbol type="id" name="accessibility_autoclick_scroll_panel" /> + <java-symbol type="id" name="scroll_up_layout" /> + <java-symbol type="id" name="scroll_down_layout" /> + <java-symbol type="id" name="scroll_left_layout" /> + <java-symbol type="id" name="scroll_right_layout" /> + <java-symbol type="id" name="scroll_exit_layout" /> + <java-symbol type="id" name="scroll_up" /> + <java-symbol type="id" name="scroll_down" /> + <java-symbol type="id" name="scroll_left" /> + <java-symbol type="id" name="scroll_right" /> + <java-symbol type="id" name="scroll_exit" /> + <!-- For HapticFeedbackConstants configurability defined at HapticFeedbackCustomization --> <java-symbol type="string" name="config_hapticFeedbackCustomizationFile" /> <java-symbol type="xml" name="haptic_feedback_customization" /> diff --git a/core/tests/coretests/src/android/animation/AnimatorSetCallsTest.java b/core/tests/coretests/src/android/animation/AnimatorSetCallsTest.java index 33a46d0fde17..6d86bd209a3d 100644 --- a/core/tests/coretests/src/android/animation/AnimatorSetCallsTest.java +++ b/core/tests/coretests/src/android/animation/AnimatorSetCallsTest.java @@ -20,6 +20,8 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import android.os.Handler; +import android.os.Looper; import android.util.PollingCheck; import android.view.View; @@ -486,6 +488,42 @@ public class AnimatorSetCallsTest { }); } + @Test + public void testCancelOnPendingEndListener() throws Throwable { + final CountDownLatch endLatch = new CountDownLatch(1); + final Handler handler = new Handler(Looper.getMainLooper()); + final boolean[] endCalledRightAfterCancel = new boolean[2]; + final AnimatorSet set = new AnimatorSet(); + final ValueAnimatorTests.MyListener asListener = new ValueAnimatorTests.MyListener(); + final ValueAnimatorTests.MyListener vaListener = new ValueAnimatorTests.MyListener(); + final ValueAnimator va = new ValueAnimator(); + va.setFloatValues(0f, 1f); + va.setDuration(30); + va.addUpdateListener(animation -> { + if (animation.getAnimatedFraction() == 1f) { + handler.post(() -> { + set.cancel(); + endCalledRightAfterCancel[0] = vaListener.endCalled; + endCalledRightAfterCancel[1] = asListener.endCalled; + endLatch.countDown(); + }); + } + }); + set.addListener(asListener); + va.addListener(vaListener); + set.play(va); + + ValueAnimator.setPostNotifyEndListenerEnabled(true); + try { + handler.post(set::start); + assertTrue(endLatch.await(1, TimeUnit.SECONDS)); + assertTrue(endCalledRightAfterCancel[0]); + assertTrue(endCalledRightAfterCancel[1]); + } finally { + ValueAnimator.setPostNotifyEndListenerEnabled(false); + } + } + private void waitForOnUiThread(PollingCheck.PollingCheckCondition condition) { final boolean[] value = new boolean[1]; PollingCheck.waitFor(() -> { diff --git a/core/tests/coretests/src/android/animation/ValueAnimatorTests.java b/core/tests/coretests/src/android/animation/ValueAnimatorTests.java index 04698465e971..a55909f0c193 100644 --- a/core/tests/coretests/src/android/animation/ValueAnimatorTests.java +++ b/core/tests/coretests/src/android/animation/ValueAnimatorTests.java @@ -922,6 +922,36 @@ public class ValueAnimatorTests { } @Test + public void testCancelOnPendingEndListener() throws Throwable { + final CountDownLatch endLatch = new CountDownLatch(1); + final Handler handler = new Handler(Looper.getMainLooper()); + final boolean[] endCalledRightAfterCancel = new boolean[1]; + final MyListener listener = new MyListener(); + final ValueAnimator va = new ValueAnimator(); + va.setFloatValues(0f, 1f); + va.setDuration(30); + va.addUpdateListener(animation -> { + if (animation.getAnimatedFraction() == 1f) { + handler.post(() -> { + va.cancel(); + endCalledRightAfterCancel[0] = listener.endCalled; + endLatch.countDown(); + }); + } + }); + va.addListener(listener); + + ValueAnimator.setPostNotifyEndListenerEnabled(true); + try { + handler.post(va::start); + assertThat(endLatch.await(1, TimeUnit.SECONDS)).isTrue(); + assertThat(endCalledRightAfterCancel[0]).isTrue(); + } finally { + ValueAnimator.setPostNotifyEndListenerEnabled(false); + } + } + + @Test public void testZeroDuration() throws Throwable { // Run two animators with zero duration, with one running forward and the other one // backward. Check that the animations start and finish with the correct end fractions. @@ -1182,6 +1212,7 @@ public class ValueAnimatorTests { assertEquals(A1_START_VALUE, a1.getAnimatedValue()); }); } + class MyUpdateListener implements ValueAnimator.AnimatorUpdateListener { boolean wasRunning = false; long firstRunningFrameTime = -1; @@ -1207,7 +1238,7 @@ public class ValueAnimatorTests { } } - class MyListener implements Animator.AnimatorListener { + static class MyListener implements Animator.AnimatorListener { boolean startCalled = false; boolean cancelCalled = false; boolean endCalled = false; diff --git a/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java b/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java index ee4761b9d024..dccbf4036b3e 100644 --- a/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java +++ b/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java @@ -245,6 +245,12 @@ public class RegisteredServicesCacheTest extends AndroidTestCase { SERVICE_INTERFACE, SERVICE_META_DATA, ATTRIBUTES_NAME, new TestSerializer()); } + TestServicesCache(Injector<TestServiceType> injector, + XmlSerializerAndParser<TestServiceType> serializerAndParser) { + super(injector, SERVICE_INTERFACE, SERVICE_META_DATA, ATTRIBUTES_NAME, + serializerAndParser); + } + @Override public TestServiceType parseServiceAttributes(Resources res, String packageName, AttributeSet attrs) { diff --git a/core/tests/coretests/src/android/text/LayoutTest.java b/core/tests/coretests/src/android/text/LayoutTest.java index 9e78af57b470..11ec9f8e1912 100644 --- a/core/tests/coretests/src/android/text/LayoutTest.java +++ b/core/tests/coretests/src/android/text/LayoutTest.java @@ -16,6 +16,9 @@ package android.text; +import static android.text.Layout.HIGH_CONTRAST_TEXT_BACKGROUND_CORNER_RADIUS_FACTOR; +import static android.text.Layout.HIGH_CONTRAST_TEXT_BACKGROUND_CORNER_RADIUS_MIN_DP; + import static com.android.graphics.hwui.flags.Flags.FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT; import static org.junit.Assert.assertArrayEquals; @@ -1073,6 +1076,68 @@ public class LayoutTest { } } + @Test + @RequiresFlagsEnabled(FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT) + public void highContrastTextEnabled_testRoundedRectSize_belowMinimum_usesMinimumValue() { + mTextPaint.setColor(Color.BLACK); + mTextPaint.setTextSize(8); // Value chosen so that N * RADIUS_FACTOR < RADIUS_MIN_DP + Layout layout = new StaticLayout("Test text", mTextPaint, mWidth, + mAlign, mSpacingMult, mSpacingAdd, /* includePad= */ false); + + MockCanvas c = new MockCanvas(/* width= */ 256, /* height= */ 256); + c.setHighContrastTextEnabled(true); + layout.draw( + c, + /* highlightPaths= */ null, + /* highlightPaints= */ null, + /* selectionPath= */ null, + /* selectionPaint= */ null, + /* cursorOffsetVertical= */ 0 + ); + + final float expectedRoundedRectSize = + mTextPaint.density * HIGH_CONTRAST_TEXT_BACKGROUND_CORNER_RADIUS_MIN_DP; + List<MockCanvas.DrawCommand> drawCommands = c.getDrawCommands(); + for (int i = 0; i < drawCommands.size(); i++) { + MockCanvas.DrawCommand drawCommand = drawCommands.get(i); + if (drawCommand.rect != null) { + expect.that(drawCommand.rX).isEqualTo(expectedRoundedRectSize); + expect.that(drawCommand.rY).isEqualTo(expectedRoundedRectSize); + } + } + } + + @Test + @RequiresFlagsEnabled(FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT) + public void highContrastTextEnabled_testRoundedRectSize_aboveMinimum_usesScaledValue() { + mTextPaint.setColor(Color.BLACK); + mTextPaint.setTextSize(50); // Value chosen so that N * RADIUS_FACTOR > RADIUS_MIN_DP + Layout layout = new StaticLayout("Test text", mTextPaint, mWidth, + mAlign, mSpacingMult, mSpacingAdd, /* includePad= */ false); + + MockCanvas c = new MockCanvas(/* width= */ 256, /* height= */ 256); + c.setHighContrastTextEnabled(true); + layout.draw( + c, + /* highlightPaths= */ null, + /* highlightPaints= */ null, + /* selectionPath= */ null, + /* selectionPaint= */ null, + /* cursorOffsetVertical= */ 0 + ); + + final float expectedRoundedRectSize = + mTextPaint.getTextSize() * HIGH_CONTRAST_TEXT_BACKGROUND_CORNER_RADIUS_FACTOR; + List<MockCanvas.DrawCommand> drawCommands = c.getDrawCommands(); + for (int i = 0; i < drawCommands.size(); i++) { + MockCanvas.DrawCommand drawCommand = drawCommands.get(i); + if (drawCommand.rect != null) { + expect.that(drawCommand.rX).isEqualTo(expectedRoundedRectSize); + expect.that(drawCommand.rY).isEqualTo(expectedRoundedRectSize); + } + } + } + private int removeAlpha(int color) { return Color.rgb( Color.red(color), @@ -1087,6 +1152,8 @@ public class LayoutTest { public final String text; public final float x; public final float y; + public final float rX; + public final float rY; public final Path path; public final RectF rect; public final Paint paint; @@ -1098,6 +1165,8 @@ public class LayoutTest { this.paint = new Paint(paint); path = null; rect = null; + this.rX = 0; + this.rY = 0; } DrawCommand(Path path, Paint paint) { @@ -1107,15 +1176,19 @@ public class LayoutTest { x = 0; text = null; rect = null; + this.rX = 0; + this.rY = 0; } - DrawCommand(RectF rect, Paint paint) { + DrawCommand(RectF rect, Paint paint, float rX, float rY) { this.rect = new RectF(rect); this.paint = new Paint(paint); path = null; y = 0; x = 0; text = null; + this.rX = rX; + this.rY = rY; } @Override @@ -1189,12 +1262,12 @@ public class LayoutTest { @Override public void drawRect(RectF rect, Paint p) { - mDrawCommands.add(new DrawCommand(rect, p)); + mDrawCommands.add(new DrawCommand(rect, p, 0, 0)); } @Override public void drawRoundRect(@NonNull RectF rect, float rx, float ry, @NonNull Paint paint) { - mDrawCommands.add(new DrawCommand(rect, paint)); + mDrawCommands.add(new DrawCommand(rect, paint, rx, ry)); } List<DrawCommand> getDrawCommands() { diff --git a/core/tests/coretests/src/android/util/ArrayMapTest.java b/core/tests/coretests/src/android/util/ArrayMapTest.java index 711ff9458e11..c7efe6f93f0d 100644 --- a/core/tests/coretests/src/android/util/ArrayMapTest.java +++ b/core/tests/coretests/src/android/util/ArrayMapTest.java @@ -14,14 +14,18 @@ package android.util; +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.fail; import android.platform.test.annotations.IgnoreUnderRavenwood; +import android.platform.test.annotations.Presubmit; import android.platform.test.ravenwood.RavenwoodRule; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.LargeTest; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -32,6 +36,7 @@ import java.util.ConcurrentModificationException; * Unit tests for ArrayMap that don't belong in CTS. */ @LargeTest +@Presubmit @RunWith(AndroidJUnit4.class) public class ArrayMapTest { private static final String TAG = "ArrayMapTest"; @@ -51,6 +56,7 @@ public class ArrayMapTest { * @throws Exception */ @Test + @Ignore("Failing; b/399137661") @IgnoreUnderRavenwood(reason = "Long test runtime") public void testConcurrentModificationException() throws Exception { final int TEST_LEN_MS = 5000; @@ -118,4 +124,49 @@ public class ArrayMapTest { } } } + + @Test + public void testToString() { + map.put("1", "One"); + map.put("2", "Two"); + map.put("3", "Five"); + map.put("3", "Three"); + + assertThat(map.toString()).isEqualTo("{1=One, 2=Two, 3=Three}"); + assertThat(map.keySet().toString()).isEqualTo("[1, 2, 3]"); + assertThat(map.values().toString()).isEqualTo("[One, Two, Three]"); + } + + @Test + public void testToStringRecursive() { + ArrayMap<Object, Object> weird = new ArrayMap<>(); + weird.put("1", weird); + + assertThat(weird.toString()).isEqualTo("{1=(this Map)}"); + assertThat(weird.keySet().toString()).isEqualTo("[1]"); + assertThat(weird.values().toString()).isEqualTo("[{1=(this Map)}]"); + } + + @Test + public void testToStringRecursiveKeySet() { + ArrayMap<Object, Object> weird = new ArrayMap<>(); + weird.put("1", weird.keySet()); + weird.put(weird.keySet(), "2"); + + assertThat(weird.toString()).isEqualTo("{1=[1, (this KeySet)], [1, (this KeySet)]=2}"); + assertThat(weird.keySet().toString()).isEqualTo("[1, (this KeySet)]"); + assertThat(weird.values().toString()).isEqualTo("[[1, (this KeySet)], 2]"); + } + + @Test + public void testToStringRecursiveValues() { + ArrayMap<Object, Object> weird = new ArrayMap<>(); + weird.put("1", weird.values()); + weird.put(weird.values(), "2"); + + assertThat(weird.toString()).isEqualTo( + "{1=[(this ValuesCollection), 2], [(this ValuesCollection), 2]=2}"); + assertThat(weird.keySet().toString()).isEqualTo("[1, [(this ValuesCollection), 2]]"); + assertThat(weird.values().toString()).isEqualTo("[(this ValuesCollection), 2]"); + } } diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java index 4d6c30ebbe2b..215c1623a530 100644 --- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java +++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java @@ -541,24 +541,30 @@ public class WindowOnBackInvokedDispatcherTest { throws RemoteException, InterruptedException { // Setup a callback that unregisters itself after the gesture is finished but before the // progress is animated back to 0f - final AtomicBoolean unregisterOnProgressUpdate = new AtomicBoolean(false); + final AtomicBoolean unregisterOnNextCallbackInvocation = new AtomicBoolean(false); final AtomicInteger onBackInvokedCalled = new AtomicInteger(0); final CountDownLatch onBackCancelledCalled = new CountDownLatch(1); OnBackAnimationCallback onBackAnimationCallback = new OnBackAnimationCallback() { @Override public void onBackProgressed(@NonNull BackEvent backEvent) { - if (unregisterOnProgressUpdate.get()) { + if (unregisterOnNextCallbackInvocation.getAndSet(false)) { mDispatcher.unregisterOnBackInvokedCallback(this); } } @Override public void onBackInvoked() { + if (unregisterOnNextCallbackInvocation.getAndSet(false)) { + mDispatcher.unregisterOnBackInvokedCallback(this); + } onBackInvokedCalled.getAndIncrement(); } @Override public void onBackCancelled() { + if (unregisterOnNextCallbackInvocation.getAndSet(false)) { + mDispatcher.unregisterOnBackInvokedCallback(this); + } onBackCancelledCalled.countDown(); } }; @@ -572,7 +578,7 @@ public class WindowOnBackInvokedDispatcherTest { // simulate back gesture finished and onBackCancelled() called, which starts the progress // animation back to 0f. On the first progress emission, the callback will unregister itself - unregisterOnProgressUpdate.set(true); + unregisterOnNextCallbackInvocation.set(true); callbackInfo.getCallback().onBackCancelled(); waitForIdle(); onBackCancelledCalled.await(1000, TimeUnit.MILLISECONDS); diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityWorkProfileTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityWorkProfileTest.java index db69cf2397fc..02a296892c53 100644 --- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityWorkProfileTest.java +++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityWorkProfileTest.java @@ -72,7 +72,8 @@ public class ChooserActivityWorkProfileTest { private static final UserHandle PERSONAL_USER_HANDLE = InstrumentationRegistry .getInstrumentation().getTargetContext().getUser(); - private static final UserHandle WORK_USER_HANDLE = UserHandle.of(10); + private static final UserHandle WORK_USER_HANDLE = + UserHandle.of(PERSONAL_USER_HANDLE.getIdentifier() + 1); @Rule public ActivityTestRule<ChooserWrapperActivity> mActivityRule = diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java index e141f70d8abb..da25da1256b0 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -54,6 +54,8 @@ import static androidx.window.extensions.embedding.SplitPresenter.sanitizeBounds import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSplit; import static androidx.window.extensions.embedding.TaskFragmentContainer.OverlayContainerRestoreParams; +import static com.android.window.flags.Flags.activityEmbeddingDelayTaskFragmentFinishForActivityLaunch; + import android.annotation.CallbackExecutor; import android.app.Activity; import android.app.ActivityClient; @@ -815,11 +817,17 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen .setOriginType(TASK_FRAGMENT_TRANSIT_CLOSE); mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */); } else if (!container.isWaitingActivityAppear()) { - // Do not finish the container before the expected activity appear until - // timeout. - mTransactionManager.getCurrentTransactionRecord() - .setOriginType(TASK_FRAGMENT_TRANSIT_CLOSE); - mPresenter.cleanupContainer(wct, container, true /* shouldFinishDependent */); + if (activityEmbeddingDelayTaskFragmentFinishForActivityLaunch() + && container.hasActivityLaunchHint()) { + // If we have recently attempted to launch a new activity into this + // TaskFragment, we schedule delayed cleanup. If the new activity appears in + // this TaskFragment, we no longer need to finish the TaskFragment. + container.scheduleDelayedTaskFragmentCleanup(); + } else { + mTransactionManager.getCurrentTransactionRecord() + .setOriginType(TASK_FRAGMENT_TRANSIT_CLOSE); + mPresenter.cleanupContainer(wct, container, true /* shouldFinishDependent */); + } } } else if (wasInPip && isInPip) { // No update until exit PIP. @@ -3164,6 +3172,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // TODO(b/229680885): skip override launching TaskFragment token by split-rule options.putBinder(KEY_LAUNCH_TASK_FRAGMENT_TOKEN, launchedInTaskFragment.getTaskFragmentToken()); + if (activityEmbeddingDelayTaskFragmentFinishForActivityLaunch()) { + launchedInTaskFragment.setActivityLaunchHint(); + } mCurrentIntent = intent; } else { transactionRecord.abort(); diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java index b3e003e7ad95..6fa855e8b6d6 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java @@ -18,6 +18,8 @@ package androidx.window.extensions.embedding; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static com.android.window.flags.Flags.activityEmbeddingDelayTaskFragmentFinishForActivityLaunch; + import android.app.Activity; import android.app.ActivityThread; import android.app.WindowConfiguration.WindowingMode; @@ -53,6 +55,8 @@ import java.util.Objects; class TaskFragmentContainer { private static final int APPEAR_EMPTY_TIMEOUT_MS = 3000; + private static final int DELAYED_TASK_FRAGMENT_CLEANUP_TIMEOUT_MS = 500; + /** Parcelable data of this TaskFragmentContainer. */ @NonNull private final ParcelableTaskFragmentContainerData mParcelableData; @@ -165,6 +169,18 @@ class TaskFragmentContainer { */ private boolean mLastDimOnTask; + /** The timestamp of the latest pending activity launch attempt. 0 means no pending launch. */ + private long mLastActivityLaunchTimestampMs = 0; + + /** + * The scheduled runnable for delayed TaskFragment cleanup. This is used when the TaskFragment + * becomes empty, but we expect a new activity to appear in it soon. + * + * It should be {@code null} when not scheduled. + */ + @Nullable + private Runnable mDelayedTaskFragmentCleanupRunnable; + /** * Creates a container with an existing activity that will be re-parented to it in a window * container transaction. @@ -540,6 +556,10 @@ class TaskFragmentContainer { mAppearEmptyTimeout = null; } + if (activityEmbeddingDelayTaskFragmentFinishForActivityLaunch()) { + clearActivityLaunchHintIfNecessary(mInfo, info); + } + mHasCrossProcessActivities = false; mInfo = info; if (mInfo == null || mInfo.isEmpty()) { @@ -1064,6 +1084,89 @@ class TaskFragmentContainer { return isOverlay() && mParcelableData.mAssociatedActivityToken != null; } + /** + * Indicates whether there is possibly a pending activity launching into this TaskFragment. + * + * This should only be used as a hint because we cannot reliably determine if the new activity + * is going to appear into this TaskFragment. + * + * TODO(b/293800510) improve activity launch tracking in TaskFragment. + */ + boolean hasActivityLaunchHint() { + if (mLastActivityLaunchTimestampMs == 0) { + return false; + } + if (System.currentTimeMillis() > mLastActivityLaunchTimestampMs + APPEAR_EMPTY_TIMEOUT_MS) { + // The hint has expired after APPEAR_EMPTY_TIMEOUT_MS. + mLastActivityLaunchTimestampMs = 0; + return false; + } + return true; + } + + /** Records the latest activity launch attempt. */ + void setActivityLaunchHint() { + mLastActivityLaunchTimestampMs = System.currentTimeMillis(); + } + + /** + * If we get a new info showing that the TaskFragment has more activities than the previous + * info, we clear the new activity launch hint. + * + * Note that this is not a reliable way and cannot cover situations when the attempted + * activity launch did not cause TaskFragment info activity count changes, such as trampoline + * launches or single top launches. + * + * TODO(b/293800510) improve activity launch tracking in TaskFragment. + */ + private void clearActivityLaunchHintIfNecessary( + @Nullable TaskFragmentInfo oldInfo, @NonNull TaskFragmentInfo newInfo) { + final int previousActivityCount = oldInfo == null ? 0 : oldInfo.getRunningActivityCount(); + if (newInfo.getRunningActivityCount() > previousActivityCount) { + mLastActivityLaunchTimestampMs = 0; + cancelDelayedTaskFragmentCleanup(); + } + } + + /** + * Schedules delayed TaskFragment cleanup due to pending activity launch. The scheduled cleanup + * will be canceled if a new activity appears in this TaskFragment. + */ + void scheduleDelayedTaskFragmentCleanup() { + if (mDelayedTaskFragmentCleanupRunnable != null) { + // Remove the previous callback if there is already one scheduled. + mController.getHandler().removeCallbacks(mDelayedTaskFragmentCleanupRunnable); + } + mDelayedTaskFragmentCleanupRunnable = new Runnable() { + @Override + public void run() { + synchronized (mController.mLock) { + if (mDelayedTaskFragmentCleanupRunnable != this) { + // The scheduled cleanup runnable has been canceled or rescheduled, so + // skipping. + return; + } + if (isEmpty()) { + mLastActivityLaunchTimestampMs = 0; + mController.onTaskFragmentAppearEmptyTimeout( + TaskFragmentContainer.this); + } + mDelayedTaskFragmentCleanupRunnable = null; + } + } + }; + mController.getHandler().postDelayed( + mDelayedTaskFragmentCleanupRunnable, DELAYED_TASK_FRAGMENT_CLEANUP_TIMEOUT_MS); + } + + private void cancelDelayedTaskFragmentCleanup() { + if (mDelayedTaskFragmentCleanupRunnable == null) { + return; + } + mController.getHandler().removeCallbacks(mDelayedTaskFragmentCleanupRunnable); + mDelayedTaskFragmentCleanupRunnable = null; + } + @Override public String toString() { return toString(true /* includeContainersToFinishOnExit */); diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig index 1e72d64397d7..ab2804626361 100644 --- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig +++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig @@ -194,3 +194,13 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "enable_gsf" + namespace: "multitasking" + description: "Applies GSF font styles to multitasking." + bug: "400534660" + metadata { + purpose: PURPOSE_BUGFIX + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/src/com/android/wm/shell/shared/bubbles/DragZoneFactoryScreenshotTest.kt b/libs/WindowManager/Shell/multivalentScreenshotTests/src/com/android/wm/shell/shared/bubbles/DragZoneFactoryScreenshotTest.kt index 24f43d347163..5777cb0cc8c5 100644 --- a/libs/WindowManager/Shell/multivalentScreenshotTests/src/com/android/wm/shell/shared/bubbles/DragZoneFactoryScreenshotTest.kt +++ b/libs/WindowManager/Shell/multivalentScreenshotTests/src/com/android/wm/shell/shared/bubbles/DragZoneFactoryScreenshotTest.kt @@ -94,6 +94,7 @@ class DragZoneFactoryScreenshotTest(private val param: Param) { private val splitScreenModeName = when (splitScreenMode) { + SplitScreenMode.UNSUPPORTED -> "_split_unsupported" SplitScreenMode.NONE -> "" SplitScreenMode.SPLIT_50_50 -> "_split_50_50" SplitScreenMode.SPLIT_10_90 -> "_split_10_90" diff --git a/libs/WindowManager/Shell/multivalentTests/Android.bp b/libs/WindowManager/Shell/multivalentTests/Android.bp index 03076c0940a4..50666911e313 100644 --- a/libs/WindowManager/Shell/multivalentTests/Android.bp +++ b/libs/WindowManager/Shell/multivalentTests/Android.bp @@ -51,6 +51,7 @@ android_robolectric_test { "androidx.test.ext.junit", "mockito-robolectric-prebuilt", "mockito-kotlin2", + "platform-parametric-runner-lib", "truth", "flag-junit-base", "flag-junit", @@ -74,6 +75,7 @@ android_test { "frameworks-base-testutils", "mockito-kotlin2", "mockito-target-extended-minus-junit4", + "platform-parametric-runner-lib", "truth", "platform-test-annotations", "platform-test-rules", diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleExpandedViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleExpandedViewTest.kt new file mode 100644 index 000000000000..bdfaef2c6960 --- /dev/null +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleExpandedViewTest.kt @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.bubbles + +import android.content.ComponentName +import android.content.Context +import android.platform.test.flag.junit.FlagsParameterization +import android.platform.test.flag.junit.SetFlagsRule +import androidx.test.core.app.ApplicationProvider +import androidx.test.filters.SmallTest +import com.android.wm.shell.Flags +import com.android.wm.shell.taskview.TaskView +import com.google.common.truth.Truth.assertThat +import com.google.common.util.concurrent.MoreExecutors.directExecutor +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.mock +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters + +/** Tests for [BubbleExpandedView] */ +@SmallTest +@RunWith(ParameterizedAndroidJunit4::class) +class BubbleExpandedViewTest(flags: FlagsParameterization) { + + @get:Rule + val setFlagsRule = SetFlagsRule(flags) + + private val context = ApplicationProvider.getApplicationContext<Context>() + private val componentName = ComponentName(context, "TestClass") + + @Test + fun getTaskId_onTaskCreated_returnsCorrectTaskId() { + val bubbleTaskView = BubbleTaskView(mock<TaskView>(), directExecutor()) + val expandedView = BubbleExpandedView(context).apply { + initialize( + mock<BubbleExpandedViewManager>(), + mock<BubbleStackView>(), + mock<BubblePositioner>(), + false /* isOverflow */, + bubbleTaskView, + ) + setAnimating(true) // Skips setContentVisibility for testing. + } + + bubbleTaskView.listener.onTaskCreated(123, componentName) + + assertThat(expandedView.getTaskId()).isEqualTo(123) + } + + companion object { + @JvmStatic + @Parameters(name = "{0}") + fun getParams() = FlagsParameterization.allCombinationsOf( + Flags.FLAG_ENABLE_BUBBLE_TASK_VIEW_LISTENER, + ) + } +} diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleTaskViewListenerTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleTaskViewListenerTest.kt index 9087da34d259..636ff669d6b4 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleTaskViewListenerTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleTaskViewListenerTest.kt @@ -266,8 +266,6 @@ class BubbleTaskViewListenerTest { optionsCaptor.capture(), any()) - assertThat((intentCaptor.lastValue.flags - and Intent.FLAG_ACTIVITY_MULTIPLE_TASK) != 0).isTrue() assertThat(optionsCaptor.lastValue.launchedFromBubble).isFalse() // chat only assertThat(optionsCaptor.lastValue.isApplyActivityFlagsForBubbles).isFalse() // chat only assertThat(optionsCaptor.lastValue.taskAlwaysOnTop).isTrue() @@ -295,8 +293,6 @@ class BubbleTaskViewListenerTest { optionsCaptor.capture(), any()) - assertThat((intentCaptor.lastValue.flags - and Intent.FLAG_ACTIVITY_MULTIPLE_TASK) != 0).isTrue() assertThat(optionsCaptor.lastValue.launchedFromBubble).isFalse() // chat only assertThat(optionsCaptor.lastValue.isApplyActivityFlagsForBubbles).isFalse() // chat only assertThat(optionsCaptor.lastValue.taskAlwaysOnTop).isTrue() @@ -324,8 +320,6 @@ class BubbleTaskViewListenerTest { optionsCaptor.capture(), any()) - assertThat((intentCaptor.lastValue.flags - and Intent.FLAG_ACTIVITY_MULTIPLE_TASK) != 0).isTrue() assertThat(optionsCaptor.lastValue.launchedFromBubble).isFalse() // chat only assertThat(optionsCaptor.lastValue.isApplyActivityFlagsForBubbles).isFalse() // chat only assertThat(optionsCaptor.lastValue.taskAlwaysOnTop).isTrue() diff --git a/libs/WindowManager/Shell/res/values-af/strings.xml b/libs/WindowManager/Shell/res/values-af/strings.xml index 4dffce59aec6..61dbb69761ad 100644 --- a/libs/WindowManager/Shell/res/values-af/strings.xml +++ b/libs/WindowManager/Shell/res/values-af/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Links 50%"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Links 30%"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Volskerm regs"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Ruil boonste app met onderste een"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Ruil linkerapp met regterapp"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Volskerm bo"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Bo 70%"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Bo 50%"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Herbegin"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Moenie weer wys nie"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Dubbeltik om\nhierdie app te skuif"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"Maksimeer <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="restore_button_text" msgid="5377571986086775288">"Stel <xliff:g id="APP_NAME">%1$s</xliff:g> terug"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"Minimeer <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="close_button_text" msgid="4544839489310949894">"Maak <xliff:g id="APP_NAME">%1$s</xliff:g> toe"</string> <string name="back_button_text" msgid="1469718707134137085">"Terug"</string> <string name="handle_text" msgid="4419667835599523257">"Apphandvatsel"</string> <string name="app_icon_text" msgid="2823268023931811747">"Appikoon"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Verander grootte van linkerkantse venster"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Verander grootte van regterkantse venster"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Maksimeer of stel venstergrootte terug"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Maksimeer appvenstergrootte"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Stel venstergrootte terug"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Minimeer appvenster"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Maak appvenster toe"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"Maak By Verstek Oop-instellings"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Kies hoe om webskakels vir hierdie app oop te maak"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"In die app"</string> diff --git a/libs/WindowManager/Shell/res/values-am/strings.xml b/libs/WindowManager/Shell/res/values-am/strings.xml index 0881e778fa52..52008a6ff952 100644 --- a/libs/WindowManager/Shell/res/values-am/strings.xml +++ b/libs/WindowManager/Shell/res/values-am/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"ግራ 50%"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"ግራ 30%"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"የቀኝ ሙሉ ማያ ገፅ"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"ከላይ ያለውን መተግበሪያ ከታች ባለው ቀይር"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"በግራ ያለውን መተግበሪያን በቀኝ ባለው ቀይር"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"የላይ ሙሉ ማያ ገፅ"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"ከላይ 70%"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"ከላይ 50%"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"እንደገና ያስጀምሩ"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"ዳግም አታሳይ"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"ይህን መተግበሪያ\nለማንቀሳቀስ ሁለቴ መታ ያድርጉ"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"<xliff:g id="APP_NAME">%1$s</xliff:g>ን አሳድግ"</string> + <string name="restore_button_text" msgid="5377571986086775288">"<xliff:g id="APP_NAME">%1$s</xliff:g>ን ወደነበረበት መልስ"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"<xliff:g id="APP_NAME">%1$s</xliff:g>ን አሳንስ"</string> + <string name="close_button_text" msgid="4544839489310949894">"<xliff:g id="APP_NAME">%1$s</xliff:g>ን ዝጋ"</string> <string name="back_button_text" msgid="1469718707134137085">"ተመለስ"</string> <string name="handle_text" msgid="4419667835599523257">"የመተግበሪያ መያዣ"</string> <string name="app_icon_text" msgid="2823268023931811747">"የመተግበሪያ አዶ"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"መስኮትን ወደ ግራ መጠን ቀይር"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"መስኮትን ወደ ቀኝ መጠን ቀይር"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"የመስኮት መጠንን አሳድግ ወይም ወደነበረበት መልስ"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"የመተግበሪያ መስኮት መጠንን አሳድግ"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"የመስኮት መጠንን ወደነበረበት መልስ"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"የመተግበሪያ መስኮትን አሳንስ"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"የመተግበሪያ መስኮትን ዝጋ"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"በነባሪ ቅንብሮች ክፈት"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"ለዚህ የድር መተግበሪያ አገናኙን እንዴት እንደሚከፍቱ ይምረጡ"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"በመተግበሪያው ውስጥ"</string> diff --git a/libs/WindowManager/Shell/res/values-ar/strings.xml b/libs/WindowManager/Shell/res/values-ar/strings.xml index 9cc49aa144b5..65dd965afbf1 100644 --- a/libs/WindowManager/Shell/res/values-ar/strings.xml +++ b/libs/WindowManager/Shell/res/values-ar/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"ضبط حجم النافذة اليسرى ليكون ٥٠%"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"ضبط حجم النافذة اليسرى ليكون ٣٠%"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"عرض النافذة اليمنى بملء الشاشة"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"تبديل التطبيق العلوي بالسفلي"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"تبديل التطبيق الأيسر بالأيمن"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"عرض النافذة العلوية بملء الشاشة"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"ضبط حجم النافذة العلوية ليكون ٧٠%"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"ضبط حجم النافذة العلوية ليكون ٥٠%"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"إعادة التشغيل"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"عدم عرض مربّع حوار التأكيد مجددًا"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"انقر مرّتَين لنقل\nهذا التطبيق."</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"تكبير \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string> + <string name="restore_button_text" msgid="5377571986086775288">"استعادة \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string> + <string name="minimize_button_text" msgid="5213953162664451152">"تصغير \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string> + <string name="close_button_text" msgid="4544839489310949894">"إغلاق \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string> <string name="back_button_text" msgid="1469718707134137085">"رجوع"</string> <string name="handle_text" msgid="4419667835599523257">"مقبض التطبيق"</string> <string name="app_icon_text" msgid="2823268023931811747">"رمز التطبيق"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"تغيير حجم النافذة بمحاذاتها إلى اليمين"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"تغيير حجم النافذة بمحاذاتها إلى اليسار"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"تكبير حجم النافذة أو استعادته"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"تكبير نافذة التطبيق"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"استعادة حجم النافذة"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"تصغير نافذة التطبيق"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"إغلاق نافذة التطبيق"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"إعدادات الفتح تلقائيًا"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"اختيار طريقة فتح روابط الويب لهذا التطبيق"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"في التطبيق"</string> diff --git a/libs/WindowManager/Shell/res/values-as/strings.xml b/libs/WindowManager/Shell/res/values-as/strings.xml index c59753c7803f..919447e125a1 100644 --- a/libs/WindowManager/Shell/res/values-as/strings.xml +++ b/libs/WindowManager/Shell/res/values-as/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"বাওঁফালৰ স্ক্ৰীনখন ৫০% কৰক"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"বাওঁফালৰ স্ক্ৰীনখন ৩০% কৰক"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"সোঁফালৰ স্ক্ৰীনখন সম্পূৰ্ণ স্ক্ৰীন কৰক"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"একেবাৰে তলৰ সৈতে একেবাৰে ওপৰৰ এপ্টো সলনাসলনি কৰক"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"সোঁফালৰ সৈতে বাওঁফালৰ এপ্টো সলনাসলনি কৰক"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"শীৰ্ষ স্ক্ৰীনখন সম্পূৰ্ণ স্ক্ৰীন কৰক"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"শীর্ষ স্ক্ৰীনখন ৭০% কৰক"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"শীর্ষ স্ক্ৰীনখন ৫০% কৰক"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"ৰিষ্টাৰ্ট কৰক"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"পুনৰাই নেদেখুৱাব"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"এই এপ্টো\nস্থানান্তৰ কৰিবলৈ দুবাৰ টিপক"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"<xliff:g id="APP_NAME">%1$s</xliff:g> মেক্সিমাইজ কৰক"</string> + <string name="restore_button_text" msgid="5377571986086775288">"<xliff:g id="APP_NAME">%1$s</xliff:g>ক পুনঃস্থাপন কৰক"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"<xliff:g id="APP_NAME">%1$s</xliff:g> মিনিমাইজ কৰক"</string> + <string name="close_button_text" msgid="4544839489310949894">"<xliff:g id="APP_NAME">%1$s</xliff:g> বন্ধ কৰক"</string> <string name="back_button_text" msgid="1469718707134137085">"উভতি যাওক"</string> <string name="handle_text" msgid="4419667835599523257">"এপৰ হেণ্ডেল"</string> <string name="app_icon_text" msgid="2823268023931811747">"এপৰ চিহ্ন"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"সোঁফাললৈ ৱিণ্ড’ৰ আকাৰ সলনি কৰক"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"বাওঁফাললৈ ৱিণ্ড’ৰ আকাৰ সলনি কৰক"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"ৱিণ্ড’ৰ আকাৰ মেক্সিমাইজ বা পুনঃস্থাপন কৰক"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"এপ্ ৱিণ্ড’ৰ আকাৰ মেক্সিমাইজ কৰক"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"ৱিণ্ড’ৰ আকাৰ পুনঃস্থাপন কৰক"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"এপ্ ৱিণ্ড’ মিনিমাইজ কৰক"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"এপ্ ৱিণ্ড’ বন্ধ কৰক"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"ডিফ’ল্ট ছেটিং খোলক"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"এই এপ্টোৰ বাবে কিদৰে ৱেব লিংক খুলিব পাৰি সেয়া বাছনি কৰক"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"এপ্টোত"</string> diff --git a/libs/WindowManager/Shell/res/values-az/strings.xml b/libs/WindowManager/Shell/res/values-az/strings.xml index 63e610b53420..846240f80037 100644 --- a/libs/WindowManager/Shell/res/values-az/strings.xml +++ b/libs/WindowManager/Shell/res/values-az/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Sol 50%"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Sol 30%"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Sağ tam ekran"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Yuxarıdakı tətbiqi aşağıdakı ilə dəyişdirin"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Soldakı tətbiqi sağdakı ilə dəyişdirin"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Yuxarı tam ekran"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Yuxarı 70%"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Yuxarı 50%"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Yenidən başladın"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Yenidən göstərməyin"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Tətbiqi köçürmək üçün\niki dəfə toxunun"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"Böyüdün: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="restore_button_text" msgid="5377571986086775288">"Bərpa edin: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"Kiçildin: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="close_button_text" msgid="4544839489310949894">"Bağlayın: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="back_button_text" msgid="1469718707134137085">"Geriyə"</string> <string name="handle_text" msgid="4419667835599523257">"Tətbiq ləqəbi"</string> <string name="app_icon_text" msgid="2823268023931811747">"Tətbiq ikonası"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Pəncərə ölçüsünü sola dəyişin"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Pəncərə ölçüsünü sağa dəyişin"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Pəncərə ölçüsünü artırın və ya bərpa edin"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Tətbiq pəncərəsi ölçüsünü böyüdün"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Pəncərə ölçüsünü bərpa edin"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Tətbiq pəncərəsini kiçildin"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Tətbiq pəncərəsini bağlayın"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"Defolt ayarlarla açın"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Bu tətbiq üçün veb-linklərin necə açılacağını seçin"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Tətbiqdə"</string> diff --git a/libs/WindowManager/Shell/res/values-be/strings.xml b/libs/WindowManager/Shell/res/values-be/strings.xml index 4f3da2b2f5d8..d686da96d3d8 100644 --- a/libs/WindowManager/Shell/res/values-be/strings.xml +++ b/libs/WindowManager/Shell/res/values-be/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Левы экран – 50%"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Левы экран – 30%"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Правы экран – поўнаэкранны рэжым"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Памяняць месцамі верхнюю і ніжнюю праграмы"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Памяняць месцамі левую і правую праграмы"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Верхні экран – поўнаэкранны рэжым"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Верхні экран – 70%"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Верхні экран – 50%"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Перазапусціць"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Больш не паказваць"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Каб перамясціць праграму,\nнацісніце двойчы"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"Разгарнуць праграму \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string> + <string name="restore_button_text" msgid="5377571986086775288">"Аднавіць праграму \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string> + <string name="minimize_button_text" msgid="5213953162664451152">"Згарнуць праграму \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string> + <string name="close_button_text" msgid="4544839489310949894">"Закрыць праграму \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string> <string name="back_button_text" msgid="1469718707134137085">"Назад"</string> <string name="handle_text" msgid="4419667835599523257">"Маркер праграмы"</string> <string name="app_icon_text" msgid="2823268023931811747">"Значок праграмы"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Змяніць памер акна і перамясціць да левага краю"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Змяніць памер акна і перамясціць да правага краю"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Разгарнуць акно ці аднавіць яго памер"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Разгарнуць акно праграмы"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Аднавіць памер акна"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Згарнуць акно праграмы"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Закрыць акно праграмы"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"Налады параметра \"Адкрываць стандартна\""</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Выберыце, як гэта праграма будзе адкрываць вэб-спасылкі"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"У праграме"</string> diff --git a/libs/WindowManager/Shell/res/values-bg/strings.xml b/libs/WindowManager/Shell/res/values-bg/strings.xml index 3f867a22e13b..984c20ff5dba 100644 --- a/libs/WindowManager/Shell/res/values-bg/strings.xml +++ b/libs/WindowManager/Shell/res/values-bg/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Ляв екран: 50%"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Ляв екран: 30%"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Десен екран: Показване на цял екран"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Размяна на горното и долното приложение"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Размяна на лявото и дясното приложение"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Горен екран: Показване на цял екран"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Горен екран: 70%"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Горен екран: 50%"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Рестартиране"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Да не се показва отново"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Докоснете двукратно, за да\nпреместите това приложение"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"Увеличаване на <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="restore_button_text" msgid="5377571986086775288">"Възстановяване на <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"Намаляване на <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="close_button_text" msgid="4544839489310949894">"Затваряне на <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="back_button_text" msgid="1469718707134137085">"Назад"</string> <string name="handle_text" msgid="4419667835599523257">"Манипулатор за приложението"</string> <string name="app_icon_text" msgid="2823268023931811747">"Икона на приложението"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Преоразмеряване на прозореца наляво"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Преоразмеряване на прозореца надясно"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Увеличаване или възстановяване на размера на прозореца"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Увеличаване на размера на прозореца на приложението"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Възстановяване на размера на прозореца"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Намаляване на прозореца на приложението"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Затваряне на прозореца на приложението"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"Отваряне на настройките по подразбиране"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Изберете как да се отварят уеб връзките за това приложение"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"В приложението"</string> diff --git a/libs/WindowManager/Shell/res/values-bn/strings.xml b/libs/WindowManager/Shell/res/values-bn/strings.xml index 3967d4bfa591..dd5653c48464 100644 --- a/libs/WindowManager/Shell/res/values-bn/strings.xml +++ b/libs/WindowManager/Shell/res/values-bn/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"৫০% বাকি আছে"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"৩০% বাকি আছে"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"ডান দিকের অংশ নিয়ে পূর্ণ স্ক্রিন"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"নিচেরটির মাধ্যমে উপরের অ্যাপ অদল বদল করে নিন"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"ডানদিকের মাধ্যমে বাঁদিকের অ্যাপ অদল বদল করে নিন"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"উপর দিকের অংশ নিয়ে পূর্ণ স্ক্রিন"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"শীর্ষ ৭০%"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"শীর্ষ ৫০%"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"রিস্টার্ট করুন"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"আর দেখতে চাই না"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"এই অ্যাপ সরাতে\nডবল ট্যাপ করুন"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"<xliff:g id="APP_NAME">%1$s</xliff:g> বড় করুন"</string> + <string name="restore_button_text" msgid="5377571986086775288">"<xliff:g id="APP_NAME">%1$s</xliff:g> আবার ফিরিয়ে আনুন"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"<xliff:g id="APP_NAME">%1$s</xliff:g> ছোট করুন"</string> + <string name="close_button_text" msgid="4544839489310949894">"<xliff:g id="APP_NAME">%1$s</xliff:g> বন্ধ করুন"</string> <string name="back_button_text" msgid="1469718707134137085">"ফিরে যান"</string> <string name="handle_text" msgid="4419667835599523257">"অ্যাপের হ্যান্ডেল"</string> <string name="app_icon_text" msgid="2823268023931811747">"অ্যাপ আইকন"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"বাঁদিকে উইন্ডো রিসাইজ করুন"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"ডানদিকে উইন্ডো রিসাইজ করুন"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"উইন্ডো সাইজ বড় বা রিস্টোর করুন"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"অ্যাপ উইন্ডোর সাইজ বাড়ান"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"উইন্ডোর সাইজ ফিরিয়ে আনুন"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"অ্যাপ উইন্ডো ছোট করুন"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"অ্যাপ উইন্ডো বন্ধ করুন"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"ডিফল্ট হিসেবে থাকা সেটিংস খুলুন"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"এই অ্যাপের জন্য কীভাবে ওয়েব লিঙ্ক খুলবেন তা বেছে নিন"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"অ্যাপের মধ্যে"</string> diff --git a/libs/WindowManager/Shell/res/values-bs/strings.xml b/libs/WindowManager/Shell/res/values-bs/strings.xml index 6b59d91e741f..01e0515dfa16 100644 --- a/libs/WindowManager/Shell/res/values-bs/strings.xml +++ b/libs/WindowManager/Shell/res/values-bs/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Lijevo 50%"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Lijevo 30%"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Desno cijeli ekran"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Zamjena gornje aplikacije donjom"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Zamjena lijeve aplikacije desnom"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Gore cijeli ekran"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Gore 70%"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Gore 50%"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Ponovo pokreni"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ne prikazuj ponovo"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Dodirnite dvaput da\npomjerite aplikaciju"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"Maksimiziranje aplikacije <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="restore_button_text" msgid="5377571986086775288">"Vraćanje aplikacije <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"Minimiziranje aplikacije <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="close_button_text" msgid="4544839489310949894">"Zatvaranje aplikacije <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="back_button_text" msgid="1469718707134137085">"Nazad"</string> <string name="handle_text" msgid="4419667835599523257">"Ručica aplikacije"</string> <string name="app_icon_text" msgid="2823268023931811747">"Ikona aplikacije"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Promjena veličine prozora i poravnanje lijevo"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Promjena veličine prozora i poravnanje desno"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Maksimiziranje ili vraćanje veličine prozora"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Maksimiziranje veličine prozora aplikacije"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Vraćanje veličine prozora"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Minimiziranje prozora aplikacije"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Zatvaranje prozora aplikacije"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"Otvaranje prema zadanim postavkama"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Odaberite način otvaranja web linkova za ovu aplikaciju"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"U aplikaciji"</string> diff --git a/libs/WindowManager/Shell/res/values-ca/strings.xml b/libs/WindowManager/Shell/res/values-ca/strings.xml index 955e5cc6ecbc..77cf94b84450 100644 --- a/libs/WindowManager/Shell/res/values-ca/strings.xml +++ b/libs/WindowManager/Shell/res/values-ca/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Pantalla esquerra al 50%"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Pantalla esquerra al 30%"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Pantalla dreta completa"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Intercanvia l\'aplicació superior amb la inferior"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Intercanvia l\'aplicació esquerra amb la dreta"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Pantalla superior completa"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Pantalla superior al 70%"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Pantalla superior al 50%"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Reinicia"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"No ho tornis a mostrar"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Fes doble toc per\nmoure aquesta aplicació"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"Maximitza <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="restore_button_text" msgid="5377571986086775288">"Restaura <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"Minimitza <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="close_button_text" msgid="4544839489310949894">"Tanca <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="back_button_text" msgid="1469718707134137085">"Enrere"</string> <string name="handle_text" msgid="4419667835599523257">"Identificador de l\'aplicació"</string> <string name="app_icon_text" msgid="2823268023931811747">"Icona de l\'aplicació"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Canvia la mida de la finestra a l\'esquerra"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Canvia la mida de la finestra a la dreta"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Maximitza o restaura la mida de la finestra"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Maximitza la mida de la finestra de l\'aplicació"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Restaura la mida de la finestra"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Minimitza la finestra de l\'aplicació"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Tanca la finestra de l\'aplicació"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"Configuració d\'obertura predeterminada"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Tria com vols obrir els enllaços web per a aquesta aplicació"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"A l\'aplicació"</string> diff --git a/libs/WindowManager/Shell/res/values-cs/strings.xml b/libs/WindowManager/Shell/res/values-cs/strings.xml index 673f7fc2f8c1..1a414ba89fdc 100644 --- a/libs/WindowManager/Shell/res/values-cs/strings.xml +++ b/libs/WindowManager/Shell/res/values-cs/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"50 % vlevo"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"30 % vlevo"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Pravá část na celou obrazovku"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Prohodit horní a dolní aplikaci"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Prohodit levou a pravou aplikaci"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Horní část na celou obrazovku"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"70 % nahoře"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"50 % nahoře"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Restartovat"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Tuto zprávu příště nezobrazovat"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Dvojitým klepnutím\npřesunete aplikaci"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"Maximalizovat aplikaci <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="restore_button_text" msgid="5377571986086775288">"Obnovit aplikaci <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"Minimalizovat aplikaci <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="close_button_text" msgid="4544839489310949894">"Zavřít aplikaci <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="back_button_text" msgid="1469718707134137085">"Zpět"</string> <string name="handle_text" msgid="4419667835599523257">"Popisovač aplikace"</string> <string name="app_icon_text" msgid="2823268023931811747">"Ikona aplikace"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Přichytit okno vlevo"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Přichytit okno vpravo"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Maximalizovat nebo obnovit velikost okna"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Maximalizovat velikost okna aplikace"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Obnovit velikost okna"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Minimalizovat okno aplikace"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Zavřít okno aplikace"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"Otevírat podle výchozího nastavení"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Určete, jak se v této aplikaci mají otevírat webové odkazy"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"V aplikaci"</string> diff --git a/libs/WindowManager/Shell/res/values-da/strings.xml b/libs/WindowManager/Shell/res/values-da/strings.xml index 635df334f9ff..07f1969ec9c6 100644 --- a/libs/WindowManager/Shell/res/values-da/strings.xml +++ b/libs/WindowManager/Shell/res/values-da/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Venstre 50 %"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Venstre 30 %"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Vis højre del i fuld skærm"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Byt om på den øverste og nederste app"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Byt om på den venstre og højre app"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Vis øverste del i fuld skærm"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Øverste 70 %"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Øverste 50 %"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Genstart"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Vis ikke igen"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Tryk to gange\nfor at flytte appen"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"Maksimer <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="restore_button_text" msgid="5377571986086775288">"Gendan <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"Minimer <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="close_button_text" msgid="4544839489310949894">"Luk <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="back_button_text" msgid="1469718707134137085">"Tilbage"</string> <string name="handle_text" msgid="4419667835599523257">"Apphåndtag"</string> <string name="app_icon_text" msgid="2823268023931811747">"Appikon"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Juster størrelsen på vinduet til venstre"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Juster størrelsen på vinduet til højre"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Maksimer eller gendan vinduesstørrelse"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Maksimer størrelsen på appvinduet"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Gendan størrelsen på vinduet"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Minimer appvindue"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Luk appvindue"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"Indstillinger for automatisk åbning"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Vælg, hvordan denne app skal åben weblinks"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"I appen"</string> diff --git a/libs/WindowManager/Shell/res/values-de/strings.xml b/libs/WindowManager/Shell/res/values-de/strings.xml index 5be8b0b34993..7a8f2d984457 100644 --- a/libs/WindowManager/Shell/res/values-de/strings.xml +++ b/libs/WindowManager/Shell/res/values-de/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"50 % links"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"30 % links"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Vollbild rechts"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Obere und untere App vertauschen"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Linke und rechte App vertauschen"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Vollbild oben"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"70 % oben"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"50 % oben"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Neu starten"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Nicht mehr anzeigen"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Zum Verschieben\ndoppeltippen"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"<xliff:g id="APP_NAME">%1$s</xliff:g> maximieren"</string> + <string name="restore_button_text" msgid="5377571986086775288">"<xliff:g id="APP_NAME">%1$s</xliff:g> wiederherstellen"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"<xliff:g id="APP_NAME">%1$s</xliff:g> minimieren"</string> + <string name="close_button_text" msgid="4544839489310949894">"<xliff:g id="APP_NAME">%1$s</xliff:g> schließen"</string> <string name="back_button_text" msgid="1469718707134137085">"Zurück"</string> <string name="handle_text" msgid="4419667835599523257">"App-Ziehpunkt"</string> <string name="app_icon_text" msgid="2823268023931811747">"App-Symbol"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Fenstergröße nach links anpassen"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Fenstergröße nach rechts anpassen"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Fenstergröße maximieren oder wiederherstellen"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"App-Fenster maximieren"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Fenstergröße wiederherstellen"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"App-Fenster minimieren"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"App-Fenster schließen"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"Einstellungen für die Option „Standardmäßig öffnen“"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Festlegen, wie Weblinks für diese App geöffnet werden"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"In der App"</string> diff --git a/libs/WindowManager/Shell/res/values-el/strings.xml b/libs/WindowManager/Shell/res/values-el/strings.xml index bd3cf053836a..e2477d35881c 100644 --- a/libs/WindowManager/Shell/res/values-el/strings.xml +++ b/libs/WindowManager/Shell/res/values-el/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Αριστερή 50%"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Αριστερή 30%"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Δεξιά πλήρης οθόνη"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Εναλλαγή της εφαρμογής στην κορυφή με αυτή στο κάτω μέρος"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Εναλλαγή της εφαρμογής στα αριστερά με αυτή στα δεξιά"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Πάνω πλήρης οθόνη"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Πάνω 70%"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Πάνω 50%"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Επανεκκίνηση"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Να μην εμφανιστεί ξανά"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Πατήστε δύο φορές για\nμετακίνηση αυτής της εφαρμογής"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"Μεγιστοποίηση <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="restore_button_text" msgid="5377571986086775288">"Επαναφορά <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"Ελαχιστοποίηση <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="close_button_text" msgid="4544839489310949894">"Κλείσιμο <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="back_button_text" msgid="1469718707134137085">"Πίσω"</string> <string name="handle_text" msgid="4419667835599523257">"Λαβή εφαρμογής"</string> <string name="app_icon_text" msgid="2823268023931811747">"Εικονίδιο εφαρμογής"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Αλλαγή μεγέθους παραθύρου προς τα αριστερά"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Αλλαγή μεγέθους παραθύρου προς τα δεξιά"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Μεγιστοποίηση ή επαναφορά μεγέθους παραθύρου"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Μεγιστοποίηση μεγέθους παραθύρου εφαρμογής"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Επαναφορά μεγέθους παραθύρου"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Ελαχιστοποίηση παραθύρου εφαρμογής"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Κλείσιμο παραθύρου εφαρμογής"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"Άνοιγμα ρυθμίσεων από προεπιλογή"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Επιλογή τρόπου ανοίγματος συνδέσμων ιστού για την εφαρμογή"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Στην εφαρμογή"</string> diff --git a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml index b137d80dcd2b..bed6e5058a9f 100644 --- a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml +++ b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Left 50%"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Left 30%"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Right full screen"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Swap top app with bottom"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Swap left app with right"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Top full screen"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Top 70%"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Top 50%"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Restart"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Don\'t show again"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Double-tap to\nmove this app"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"Maximise <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="restore_button_text" msgid="5377571986086775288">"Restore <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"Minimise <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="close_button_text" msgid="4544839489310949894">"Close <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="back_button_text" msgid="1469718707134137085">"Back"</string> <string name="handle_text" msgid="4419667835599523257">"App handle"</string> <string name="app_icon_text" msgid="2823268023931811747">"App icon"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Resize window to left"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Resize window to right"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Maximise or restore window size"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Maximise app window size"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Restore window size"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Minimise app window"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Close app window"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"Open by default settings"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Choose how to open web links for this app"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"In the app"</string> diff --git a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml index b137d80dcd2b..bed6e5058a9f 100644 --- a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml +++ b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Left 50%"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Left 30%"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Right full screen"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Swap top app with bottom"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Swap left app with right"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Top full screen"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Top 70%"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Top 50%"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Restart"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Don\'t show again"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Double-tap to\nmove this app"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"Maximise <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="restore_button_text" msgid="5377571986086775288">"Restore <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"Minimise <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="close_button_text" msgid="4544839489310949894">"Close <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="back_button_text" msgid="1469718707134137085">"Back"</string> <string name="handle_text" msgid="4419667835599523257">"App handle"</string> <string name="app_icon_text" msgid="2823268023931811747">"App icon"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Resize window to left"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Resize window to right"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Maximise or restore window size"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Maximise app window size"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Restore window size"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Minimise app window"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Close app window"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"Open by default settings"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Choose how to open web links for this app"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"In the app"</string> diff --git a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml index b137d80dcd2b..bed6e5058a9f 100644 --- a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml +++ b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Left 50%"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Left 30%"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Right full screen"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Swap top app with bottom"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Swap left app with right"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Top full screen"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Top 70%"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Top 50%"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Restart"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Don\'t show again"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Double-tap to\nmove this app"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"Maximise <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="restore_button_text" msgid="5377571986086775288">"Restore <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"Minimise <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="close_button_text" msgid="4544839489310949894">"Close <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="back_button_text" msgid="1469718707134137085">"Back"</string> <string name="handle_text" msgid="4419667835599523257">"App handle"</string> <string name="app_icon_text" msgid="2823268023931811747">"App icon"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Resize window to left"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Resize window to right"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Maximise or restore window size"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Maximise app window size"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Restore window size"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Minimise app window"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Close app window"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"Open by default settings"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Choose how to open web links for this app"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"In the app"</string> diff --git a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml index 1d5d3855bd23..2b7769d19168 100644 --- a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml +++ b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Izquierda: 50%"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Izquierda: 30%"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Pantalla derecha completa"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Intercambiar la app superior con la inferior"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Intercambiar la app de la izquierda con la de la derecha"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Pantalla superior completa"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Superior: 70%"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Superior: 50%"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Reiniciar"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"No volver a mostrar"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Presiona dos veces\npara mover esta app"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"Maximizar <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="restore_button_text" msgid="5377571986086775288">"Restablecer <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"Minimizar <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="close_button_text" msgid="4544839489310949894">"Cerrar <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="back_button_text" msgid="1469718707134137085">"Atrás"</string> <string name="handle_text" msgid="4419667835599523257">"Controlador de la app"</string> <string name="app_icon_text" msgid="2823268023931811747">"Ícono de la app"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Ajustar el tamaño de la ventana hacia la izquierda"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Ajustar el tamaño de la ventana hacia la derecha"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Maximizar o restablecer el tamaño de la ventana"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Maximizar el tamaño de la ventana de la app"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Restablecer el tamaño de la ventana"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Minimizar ventana de la app"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Cerrar ventana de la app"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"Abrir con la configuración predeterminada"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Elige cómo abrir vínculos web para esta app"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"En la app"</string> diff --git a/libs/WindowManager/Shell/res/values-es/strings.xml b/libs/WindowManager/Shell/res/values-es/strings.xml index dfa7869434bc..afb1034551b3 100644 --- a/libs/WindowManager/Shell/res/values-es/strings.xml +++ b/libs/WindowManager/Shell/res/values-es/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Izquierda 50%"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Izquierda 30%"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Pantalla derecha completa"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Intercambiar aplicación superior con inferior"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Intercambiar aplicación izquierda con derecha"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Pantalla superior completa"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Superior 70%"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Superior 50%"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Reiniciar"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"No volver a mostrar"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Toca dos veces para\nmover esta aplicación"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"Maximizar <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="restore_button_text" msgid="5377571986086775288">"Restaurar <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"Minimizar <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="close_button_text" msgid="4544839489310949894">"Cerrar <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="back_button_text" msgid="1469718707134137085">"Atrás"</string> <string name="handle_text" msgid="4419667835599523257">"Controlador de la aplicación"</string> <string name="app_icon_text" msgid="2823268023931811747">"Icono de la aplicación"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Cambiar tamaño de la ventana a la izquierda"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Cambiar tamaño de la ventana a la derecha"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Maximizar o restaurar tamaño de la ventana"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Maximizar tamaño de la ventana de la aplicación"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Restaurar tamaño de la ventana"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Minimizar ventana de la aplicación"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Cerrar ventana de la aplicación"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"Abrir con los ajustes predeterminados"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Elige cómo quieres abrir los enlaces web de esta aplicación"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"En la aplicación"</string> diff --git a/libs/WindowManager/Shell/res/values-et/strings.xml b/libs/WindowManager/Shell/res/values-et/strings.xml index 294b7fee9954..61ca3e0db920 100644 --- a/libs/WindowManager/Shell/res/values-et/strings.xml +++ b/libs/WindowManager/Shell/res/values-et/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Vasak: 50%"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Vasak: 30%"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Parem täisekraan"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Ülemise ja alumise rakenduse vahetamine"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Vasaku ja parema rakenduse vahetamine"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Ülemine täisekraan"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Ülemine: 70%"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Ülemine: 50%"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Taaskäivita"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ära kuva uuesti"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Rakenduse teisaldamiseks\ntopeltpuudutage"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"Rakenduse <xliff:g id="APP_NAME">%1$s</xliff:g> maksimeerimine"</string> + <string name="restore_button_text" msgid="5377571986086775288">"Rakenduse <xliff:g id="APP_NAME">%1$s</xliff:g> taastamine"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"Rakenduse <xliff:g id="APP_NAME">%1$s</xliff:g> minimeerimine"</string> + <string name="close_button_text" msgid="4544839489310949894">"Rakenduse <xliff:g id="APP_NAME">%1$s</xliff:g> sulgemine"</string> <string name="back_button_text" msgid="1469718707134137085">"Tagasi"</string> <string name="handle_text" msgid="4419667835599523257">"Rakenduse element"</string> <string name="app_icon_text" msgid="2823268023931811747">"Rakenduse ikoon"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Akna suuruse muutmine, vasakule"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Akna suuruse muutmine, paremale"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Akna suuruse maksimeerimine või taastamine"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Rakenduse akna suuruse maksimeerimine"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Akna suuruse taastamined"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Rakenduse akna minimeerimine"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Rakenduse akna sulgemine"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"Avamisviisi vaikeseaded"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Valige, kuidas avada selle rakenduse puhul veebilinke"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Rakenduses"</string> diff --git a/libs/WindowManager/Shell/res/values-eu/strings.xml b/libs/WindowManager/Shell/res/values-eu/strings.xml index a8f92128a603..af175bc9d08e 100644 --- a/libs/WindowManager/Shell/res/values-eu/strings.xml +++ b/libs/WindowManager/Shell/res/values-eu/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Ezarri ezkerraldea % 50en"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Ezarri ezkerraldea % 30en"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Ezarri eskuinaldea pantaila osoan"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Aldatu goiko aplikazioa behekoagatik"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Aldatu ezkerreko aplikazioa eskuinekoagatik"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Ezarri goialdea pantaila osoan"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Ezarri goialdea % 70en"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Ezarri goialdea % 50en"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Berrabiarazi"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ez erakutsi berriro"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Sakatu birritan\naplikazioa mugitzeko"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"Maximizatu <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="restore_button_text" msgid="5377571986086775288">"Leheneratu <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"Minimizatu <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="close_button_text" msgid="4544839489310949894">"Itxi <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="back_button_text" msgid="1469718707134137085">"Atzera"</string> <string name="handle_text" msgid="4419667835599523257">"Aplikazioaren kontrol-puntua"</string> <string name="app_icon_text" msgid="2823268023931811747">"Aplikazioaren ikonoa"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Aldatu leihoaren tamaina eta eraman ezkerrera"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Aldatu leihoaren tamaina eta eraman eskuinera"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Maximizatu edo leheneratu leihoaren tamaina"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Maximizatu aplikazioaren leihoaren tamaina"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Leheneratu leihoaren tamaina"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Minimizatu aplikazioaren leihoa"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Itxi aplikazioaren leihoa"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"Modu lehenetsian irekitzearen ezarpenak"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Aukeratu nola ireki sareko estekak aplikazio honetan"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Aplikazioan"</string> diff --git a/libs/WindowManager/Shell/res/values-fa/strings.xml b/libs/WindowManager/Shell/res/values-fa/strings.xml index 59affd7887be..4df47c1dd2cf 100644 --- a/libs/WindowManager/Shell/res/values-fa/strings.xml +++ b/libs/WindowManager/Shell/res/values-fa/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"٪۵۰ چپ"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"٪۳۰ چپ"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"تمامصفحه راست"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"جابهجا کردن برنامه بالا با پایین"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"جابهجا کردن برنامه چپ با راست"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"تمامصفحه بالا"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"٪۷۰ بالا"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"٪۵۰ بالا"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"بازراهاندازی"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"دوباره نشان داده نشود"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"برای جابهجا کردن این برنامه\nدو تکضرب بزنید"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"بزرگ کردن <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="restore_button_text" msgid="5377571986086775288">"بازیابی <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"کوچک کردن <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="close_button_text" msgid="4544839489310949894">"بستن <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="back_button_text" msgid="1469718707134137085">"برگشتن"</string> <string name="handle_text" msgid="4419667835599523257">"دستگیره برنامه"</string> <string name="app_icon_text" msgid="2823268023931811747">"نماد برنامه"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"تغییر اندازه پنجره به چپ"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"تغییر اندازه پنجره به راست"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"بیشینهسازی یا بازیابی اندازه پنجره"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"بزرگ کردن اندازه پنجره برنامه"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"بازیابی اندازه پنجره"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"کوچک کردن پنجره برنامه"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"بستن پنجره برنامه"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"تنظیمات باز کردن بهطور پیشفرض"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"انتخاب روش باز کردن پیوندهای وب مربوط به این برنامه"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"در برنامه"</string> diff --git a/libs/WindowManager/Shell/res/values-fi/strings.xml b/libs/WindowManager/Shell/res/values-fi/strings.xml index b1d8431b57d9..7da06bd50378 100644 --- a/libs/WindowManager/Shell/res/values-fi/strings.xml +++ b/libs/WindowManager/Shell/res/values-fi/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Vasen 50 %"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Vasen 30 %"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Oikea koko näytölle"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Vaihda ylä- ja alareunan sovellukset"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Vaihda vasen sovellus oikealla olevaan sovellukseen"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Yläosa koko näytölle"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Yläosa 70 %"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Yläosa 50 %"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Käynnistä uudelleen"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Älä näytä uudelleen"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Kaksoisnapauta, jos\nhaluat siirtää sovellusta"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"Suurenna <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="restore_button_text" msgid="5377571986086775288">"Palauta <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"Pienennä <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="close_button_text" msgid="4544839489310949894">"Sulje <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="back_button_text" msgid="1469718707134137085">"Takaisin"</string> <string name="handle_text" msgid="4419667835599523257">"Sovelluksen tunnus"</string> <string name="app_icon_text" msgid="2823268023931811747">"Sovelluskuvake"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Muuta vasemmanpuoleisen ikkunan kokoa"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Muuta vasemmanpuoleisen ikkunan kokoa"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Suurenna ikkuna tai palauta ikkunan koko"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Suurenna sovellusikkunan koko"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Palauta ikkunan koko"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Pienennä sovellusikkuna"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Sulje sovellusikkuna"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"Avaa oletusasetusten mukaan"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Valitse, miten verkkolinkit avataan tässä sovelluksessa"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Sovelluksessa"</string> diff --git a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml index c58f4b0610f1..d0fac37dc8ed 100644 --- a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml +++ b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"50 % à la gauche"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"30 % à la gauche"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Plein écran à la droite"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Inverser l\'appli du haut avec celle du bas"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Inverser l\'appli de gauche avec celle de droite"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Plein écran dans le haut"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"70 % dans le haut"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"50 % dans le haut"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Redémarrer"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ne plus afficher"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Toucher deux fois pour\ndéplacer cette appli"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"Agrandir <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="restore_button_text" msgid="5377571986086775288">"Restaurer <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"Réduire <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="close_button_text" msgid="4544839489310949894">"Fermer <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="back_button_text" msgid="1469718707134137085">"Retour"</string> <string name="handle_text" msgid="4419667835599523257">"Poignée de l\'appli"</string> <string name="app_icon_text" msgid="2823268023931811747">"Icône de l\'appli"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Redimensionner la fenêtre vers la gauche"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Redimensionner la fenêtre vers la droite"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Agrandir ou restaurer la taille de la fenêtre"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Agrandir la taille de la fenêtre de l\'appli"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Restaurer la taille de la fenêtre"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Réduire la fenêtre de l\'appli"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Fermer la fenêtre de l\'appli"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"Ouvrir les paramètres par défaut"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Choisissez comment ouvrir les liens Web pour cette appli"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Dans l\'appli"</string> diff --git a/libs/WindowManager/Shell/res/values-fr/strings.xml b/libs/WindowManager/Shell/res/values-fr/strings.xml index e8db4c929561..91dc6de3a27f 100644 --- a/libs/WindowManager/Shell/res/values-fr/strings.xml +++ b/libs/WindowManager/Shell/res/values-fr/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Écran de gauche à 50 %"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Écran de gauche à 30 %"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Écran de droite en plein écran"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Inverser les applis du haut et du bas"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Inverser les applis de gauche et de droite"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Écran du haut en plein écran"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Écran du haut à 70 %"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Écran du haut à 50 %"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Redémarrer"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ne plus afficher"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Appuyez deux fois\npour déplacer cette appli"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"Agrandir <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="restore_button_text" msgid="5377571986086775288">"Restaurer <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"Réduire <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="close_button_text" msgid="4544839489310949894">"Fermer <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="back_button_text" msgid="1469718707134137085">"Retour"</string> <string name="handle_text" msgid="4419667835599523257">"Poignée de l\'appli"</string> <string name="app_icon_text" msgid="2823268023931811747">"Icône d\'application"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Redimensionner la fenêtre vers la gauche"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Redimensionner la fenêtre vers la droite"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Agrandir ou restaurer la taille de la fenêtre"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Agrandir la taille de la fenêtre de l\'application"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Restaurer la taille de la fenêtre"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Réduire la fenêtre de l\'application"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Fermer la fenêtre de l\'application"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"Ouvrir les paramètres par défaut"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Choisir comment ouvrir les liens Web pour cette appli"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Dans l\'application"</string> diff --git a/libs/WindowManager/Shell/res/values-gl/strings.xml b/libs/WindowManager/Shell/res/values-gl/strings.xml index ba1c4a4b73c6..e315648c598d 100644 --- a/libs/WindowManager/Shell/res/values-gl/strings.xml +++ b/libs/WindowManager/Shell/res/values-gl/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"50 % á esquerda"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"30 % á esquerda"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Pantalla completa á dereita"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Cambiar a aplicación de arriba pola de abaixo"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Cambiar a aplicación da esquerda pola da dereita"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Pantalla completa arriba"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"70 % arriba"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"50 % arriba"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Reiniciar"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Non mostrar outra vez"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Toca dúas veces para\nmover esta aplicación"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"Maximizar <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="restore_button_text" msgid="5377571986086775288">"Restaurar <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"Minimizar <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="close_button_text" msgid="4544839489310949894">"Pechar <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="back_button_text" msgid="1469718707134137085">"Atrás"</string> <string name="handle_text" msgid="4419667835599523257">"Controlador da aplicación"</string> <string name="app_icon_text" msgid="2823268023931811747">"Icona de aplicación"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Axustar o tamaño da ventá á esquerda"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Axustar o tamaño da ventá á dereita"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Maximizar ou restaurar o tamaño da ventá"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Maximizar o tamaño da ventá da aplicación"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Restaurar o tamaño da ventá"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Minimizar a ventá da aplicación"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Pechar a ventá da aplicación"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"Abrir coa configuración predeterminada"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Escoller como abrir as ligazóns web para esta aplicación"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Na aplicación"</string> diff --git a/libs/WindowManager/Shell/res/values-hi/strings.xml b/libs/WindowManager/Shell/res/values-hi/strings.xml index d0be7d560961..cbcb2674c6b4 100644 --- a/libs/WindowManager/Shell/res/values-hi/strings.xml +++ b/libs/WindowManager/Shell/res/values-hi/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"बाईं स्क्रीन को 50% बनाएं"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"बाईं स्क्रीन को 30% बनाएं"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"दाईं स्क्रीन को फ़ुल स्क्रीन बनाएं"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"सबसे ऊपर मौजूद ऐप्लिकेशन को सबसे नीचे मौजूद ऐप्लिकेशन से बदलें"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"बाईं ओर मौजूद ऐप्लिकेशन को दाईं ओर मौजूद ऐप्लिकेशन से बदलें"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"ऊपर की स्क्रीन को फ़ुल स्क्रीन बनाएं"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"ऊपर की स्क्रीन को 70% बनाएं"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"ऊपर की स्क्रीन को 50% बनाएं"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"रीस्टार्ट करें"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"फिर से न दिखाएं"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"ऐप्लिकेशन की जगह बदलने के लिए\nदो बार टैप करें"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"<xliff:g id="APP_NAME">%1$s</xliff:g> की विंडो को बड़ा करें"</string> + <string name="restore_button_text" msgid="5377571986086775288">"<xliff:g id="APP_NAME">%1$s</xliff:g> की विंडो को पहले जैसा करें"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"<xliff:g id="APP_NAME">%1$s</xliff:g> की विंडो को छोटा करें"</string> + <string name="close_button_text" msgid="4544839489310949894">"<xliff:g id="APP_NAME">%1$s</xliff:g> को बंद करें"</string> <string name="back_button_text" msgid="1469718707134137085">"वापस जाएं"</string> <string name="handle_text" msgid="4419667835599523257">"ऐप्लिकेशन का हैंडल"</string> <string name="app_icon_text" msgid="2823268023931811747">"ऐप्लिकेशन आइकॉन"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"विंडो का साइज़ बाईं ओर से बदलें"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"विंडो का साइज़ दाईं ओर से बढ़ाएं"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"विंडो को बड़ा करें या उसका साइज़ पहले जैसा करें"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"ऐप्लिकेशन की विंडो का साइज़ बड़ा करें"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"विंडो का साइज़ पहले जैसा करें"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"ऐप्लिकेशन की विंडो को छोटा करें"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"ऐप्लिकेशन की विंडो बंद करें"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"डिफ़ॉल्ट सेटिंग के हिसाब से खोलें"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"इस ऐप्लिकेशन के लिए वेब लिंक खोलने का तरीका चुनें"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"ऐप्लिकेशन में"</string> diff --git a/libs/WindowManager/Shell/res/values-hr/strings.xml b/libs/WindowManager/Shell/res/values-hr/strings.xml index d3e7599e1dc9..0eed0a422cf1 100644 --- a/libs/WindowManager/Shell/res/values-hr/strings.xml +++ b/libs/WindowManager/Shell/res/values-hr/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Lijevi zaslon na 50%"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Lijevi zaslon na 30%"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Desni zaslon u cijeli zaslon"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Zamijeni gornju aplikaciju donjom"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Zamijeni lijevu aplikaciju desnom"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Gornji zaslon u cijeli zaslon"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Gornji zaslon na 70%"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Gornji zaslon na 50%"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Pokreni ponovno"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ne prikazuj ponovno"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Dvaput dodirnite da biste\npremjestili ovu aplikaciju"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"Maksimiziraj aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="restore_button_text" msgid="5377571986086775288">"Vrati aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"Minimiziraj aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="close_button_text" msgid="4544839489310949894">"Zatvori aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="back_button_text" msgid="1469718707134137085">"Natrag"</string> <string name="handle_text" msgid="4419667835599523257">"Pokazivač aplikacije"</string> <string name="app_icon_text" msgid="2823268023931811747">"Ikona aplikacije"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Promijeni veličinu prozora ulijevo"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Promijeni veličinu prozora udesno"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Maksimiziraj ili vrati veličinu prozora"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Maksimiziraj prozor aplikacije"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Vrati veličinu prozora"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Minimiziraj prozor aplikacije"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Zatvori prozor aplikacije"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"Otvori prema zadanim postavkama"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Odaberite način otvaranja web-veza za ovu aplikaciju"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"U aplikaciji"</string> diff --git a/libs/WindowManager/Shell/res/values-hu/strings.xml b/libs/WindowManager/Shell/res/values-hu/strings.xml index 2f7a21834eb8..48d5afd778c3 100644 --- a/libs/WindowManager/Shell/res/values-hu/strings.xml +++ b/libs/WindowManager/Shell/res/values-hu/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Bal oldali 50%-ra"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Bal oldali 30%-ra"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Jobb oldali teljes képernyőre"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"A felső alkalmazás és az alsó alkalmazás felcserélése"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Bal és jobb alkalmazás felcserélése"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Felső teljes képernyőre"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Felső 70%-ra"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Felső 50%-ra"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Újraindítás"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ne jelenjen meg többé"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Koppintson duplán\naz alkalmazás áthelyezéséhez"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"<xliff:g id="APP_NAME">%1$s</xliff:g> teljes méretűre állítása"</string> + <string name="restore_button_text" msgid="5377571986086775288">"<xliff:g id="APP_NAME">%1$s</xliff:g> visszaállítása"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"<xliff:g id="APP_NAME">%1$s</xliff:g> kis méretűre állítása"</string> + <string name="close_button_text" msgid="4544839489310949894">"<xliff:g id="APP_NAME">%1$s</xliff:g> bezárása"</string> <string name="back_button_text" msgid="1469718707134137085">"Vissza"</string> <string name="handle_text" msgid="4419667835599523257">"App fogópontja"</string> <string name="app_icon_text" msgid="2823268023931811747">"Alkalmazásikon"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Ablak átméretezése balra"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Ablak átméretezése jobbra"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Ablak teljes méretre állítása vagy visszaállítása"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Alkalmazásablak teljes méretre állítása"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Ablak méretének visszaállítása"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Alkalmazásablak kis méretre állítása"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Alkalmazás ablakának bezárása"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"Alapértelmezett beállítások megnyitása"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Az app webes linkjeinek megnyitásához használt módszer"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Az alkalmazásban"</string> diff --git a/libs/WindowManager/Shell/res/values-hy/strings.xml b/libs/WindowManager/Shell/res/values-hy/strings.xml index 2898dcc33b35..bf1e840179d5 100644 --- a/libs/WindowManager/Shell/res/values-hy/strings.xml +++ b/libs/WindowManager/Shell/res/values-hy/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Ձախ էկրանը՝ 50%"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Ձախ էկրանը՝ 30%"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Աջ էկրանը՝ լիաէկրան"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Փոխեք վերևի հավելվածը վերևից"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Փոխեք ձախ հավելվածը աջից"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Վերևի էկրանը՝ լիաէկրան"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Վերևի էկրանը՝ 70%"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Վերևի էկրանը՝ 50%"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Վերագործարկել"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Այլևս ցույց չտալ"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Կրկնակի հպեք՝\nհավելվածը տեղափոխելու համար"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"Ծավալել <xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածը"</string> + <string name="restore_button_text" msgid="5377571986086775288">"Վերականգնել «<xliff:g id="APP_NAME">%1$s</xliff:g>» միջոցառումը"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"Ծալել <xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածը"</string> + <string name="close_button_text" msgid="4544839489310949894">"Փակել՝ <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="back_button_text" msgid="1469718707134137085">"Հետ"</string> <string name="handle_text" msgid="4419667835599523257">"Հավելվածի կեղծանուն"</string> <string name="app_icon_text" msgid="2823268023931811747">"Հավելվածի պատկերակ"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Ձգել պատուհանը դեպի ձախ"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Ձգել պատուհանը դեպի աջ"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Ծավալել կամ վերականգնել պատուհանի չափսը"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Առավելագույնի հասցրեք հավելվածի պատուհանի չափը"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Վերականգնել պատուհանի չափը"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Ծալել հավելվածի պատուհանը"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Փակեք հավելվածի պատուհանը"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"Բացել կարգավորումներն ըստ կանխադրման"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Ընտրեք՝ ինչպես բացել այս հավելվածի վեբ հղումները"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Հավելվածում"</string> diff --git a/libs/WindowManager/Shell/res/values-in/strings.xml b/libs/WindowManager/Shell/res/values-in/strings.xml index 617f9c99de01..d658f59ec9e3 100644 --- a/libs/WindowManager/Shell/res/values-in/strings.xml +++ b/libs/WindowManager/Shell/res/values-in/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Kiri 50%"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Kiri 30%"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Layar penuh di kanan"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Tukar aplikasi atas dengan aplikasi bawah"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Tukar aplikasi kiri dengan aplikasi kanan"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Layar penuh di atas"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Atas 70%"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Atas 50%"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Mulai ulang"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Jangan tampilkan lagi"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Ketuk dua kali untuk\nmemindahkan aplikasi ini"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"Maksimalkan <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="restore_button_text" msgid="5377571986086775288">"Pulihkan <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"Minimalkan <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="close_button_text" msgid="4544839489310949894">"Tutup <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="back_button_text" msgid="1469718707134137085">"Kembali"</string> <string name="handle_text" msgid="4419667835599523257">"Penanganan aplikasi"</string> <string name="app_icon_text" msgid="2823268023931811747">"Ikon Aplikasi"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Ubah ukuran jendela ke kiri"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Ubah ukuran jendela ke kanan"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Maksimalkan atau pulihkan ukuran jendela"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Maksimalkan ukuran jendela aplikasi"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Pulihkan ukuran jendela"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Minimalkan jendela aplikasi"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Tutup jendela aplikasi"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"Buka dengan setelan default"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Pilih cara membuka link web untuk aplikasi ini"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Di aplikasi"</string> diff --git a/libs/WindowManager/Shell/res/values-is/strings.xml b/libs/WindowManager/Shell/res/values-is/strings.xml index 797a4cc8d035..f7b78d2222f5 100644 --- a/libs/WindowManager/Shell/res/values-is/strings.xml +++ b/libs/WindowManager/Shell/res/values-is/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Vinstri 50%"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Vinstri 30%"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Hægri á öllum skjánum"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Víxla efsta og neðsta forriti"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Víxla hægra og vinstra forriti"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Efri á öllum skjánum"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Efri 70%"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Efri 50%"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Endurræsa"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ekki sýna þetta aftur"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Ýttu tvisvar til\nað færa þetta forrit"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"Stækka <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="restore_button_text" msgid="5377571986086775288">"Endurheimta <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"Minnka <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="close_button_text" msgid="4544839489310949894">"Loka <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="back_button_text" msgid="1469718707134137085">"Til baka"</string> <string name="handle_text" msgid="4419667835599523257">"Handfang forrits"</string> <string name="app_icon_text" msgid="2823268023931811747">"Tákn forrits"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Breyta stærð glugga til vinstri"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Breyta stærð glugga til hægri"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Hámarka eða endurheimta stærð glugga"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Hámarka stærð forritsglugga"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Endurheimta gluggastærð"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Lágmarka stærð forritsglugga"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Loka forritsglugga"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"Stillingar sjálfvirkrar opnunar"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Veldu hvernig veftenglar opnast í forritinu"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Í forritinu"</string> diff --git a/libs/WindowManager/Shell/res/values-ka/strings.xml b/libs/WindowManager/Shell/res/values-ka/strings.xml index 2fbe1a786687..369c019726c7 100644 --- a/libs/WindowManager/Shell/res/values-ka/strings.xml +++ b/libs/WindowManager/Shell/res/values-ka/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"მარცხენა ეკრანი — 50%"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"მარცხენა ეკრანი — 30%"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"მარჯვენა ნაწილის სრულ ეკრანზე გაშლა"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"ზედა და ქვედა აპების მდებარეობის გაცვლა"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"მარცხენა და მარჯვენა აპების მდებარეობის გაცვლა"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"ზედა ნაწილის სრულ ეკრანზე გაშლა"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"ზედა ეკრანი — 70%"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"ზედა ეკრანი — 50%"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"გადატვირთვა"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"აღარ გამოჩნდეს"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"ამ აპის გადასატანად\nორმაგად შეეხეთ მას"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"<xliff:g id="APP_NAME">%1$s</xliff:g>-ის მაქსიმალურად გაშლა"</string> + <string name="restore_button_text" msgid="5377571986086775288">"<xliff:g id="APP_NAME">%1$s</xliff:g>-ის აღდგენა"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"<xliff:g id="APP_NAME">%1$s</xliff:g>-ის ჩაკეცვა"</string> + <string name="close_button_text" msgid="4544839489310949894">"<xliff:g id="APP_NAME">%1$s</xliff:g>-ის დახურვა"</string> <string name="back_button_text" msgid="1469718707134137085">"უკან"</string> <string name="handle_text" msgid="4419667835599523257">"აპის იდენტიფიკატორი"</string> <string name="app_icon_text" msgid="2823268023931811747">"აპის ხატულა"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"ფანჯრის ზომის შეცვლა მარცხნივ"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"ფანჯრის ზომის შეცვლა მარჯვნივ"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"ფანჯრის მაქსიმალურ ზომამდე გაზრდა ან აღდგენა"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"აპის ფანჯრის მაქსიმალურ ზომამდე გაზრდა"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"ფანჯრის ზომის აღდგენა"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"აპის ფანჯრის ზომის შემცირება"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"აპის ფანჯრის დახურვა"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"პარამეტრების ნაგულისხმევად გახსნა"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"ამ აპისთვის ვებ ბმულების გახსნის წესის არჩევა"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"აპში"</string> diff --git a/libs/WindowManager/Shell/res/values-kk/strings.xml b/libs/WindowManager/Shell/res/values-kk/strings.xml index c494b16687ef..1de32d4ae834 100644 --- a/libs/WindowManager/Shell/res/values-kk/strings.xml +++ b/libs/WindowManager/Shell/res/values-kk/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"50% сол жақта"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"30% сол жақта"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Оң жағын толық экранға шығару"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Жоғарыдағы қолданбаны төмендегімен орнын ауыстыру"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Сол жақтағы қолданбаны оң жақтағымен орнын ауыстыру"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Жоғарғы жағын толық экранға шығару"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"70% жоғарғы жақта"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"50% жоғарғы жақта"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Өшіріп қосу"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Қайта көрсетілмесін"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Бұл қолданбаны басқа орынға\nжылжыту үшін екі рет түртіңіз."</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"<xliff:g id="APP_NAME">%1$s</xliff:g> қолданбасын ұлғайту"</string> + <string name="restore_button_text" msgid="5377571986086775288">"<xliff:g id="APP_NAME">%1$s</xliff:g> қолданбасын қалпына келтіру"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"<xliff:g id="APP_NAME">%1$s</xliff:g> қолданбасын кішірейту"</string> + <string name="close_button_text" msgid="4544839489310949894">"<xliff:g id="APP_NAME">%1$s</xliff:g> қолданбасын жабу"</string> <string name="back_button_text" msgid="1469718707134137085">"Артқа"</string> <string name="handle_text" msgid="4419667835599523257">"Қолданба идентификаторы"</string> <string name="app_icon_text" msgid="2823268023931811747">"Қолданба белгішесі"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Терезе өлшемін сол жаққа өзгерту"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Терезе өлшемін оң жаққа өзгерту"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Терезе өлшемін ұлғайту не қалпына келтіру"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Қолданба терезесінің өлшемін ұлғайту"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Терезе өлшемін қалпына келтіру"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Қолданба терезесін кішірейту"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Қолданба терезесін жабу"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"Әдепкісінше ашу параметрлері"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Осы қолданбадағы веб-сілтемелерді ашу жолын таңдаңыз"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Қолданбада"</string> diff --git a/libs/WindowManager/Shell/res/values-km/strings.xml b/libs/WindowManager/Shell/res/values-km/strings.xml index 234c3552b7de..6c7faf1469eb 100644 --- a/libs/WindowManager/Shell/res/values-km/strings.xml +++ b/libs/WindowManager/Shell/res/values-km/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"ឆ្វេង 50%"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"ឆ្វេង 30%"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"អេក្រង់ពេញខាងស្តាំ"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"ប្ដូរកម្មវិធីខាងលើគេទៅខាងក្រោម"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"ប្ដូរកម្មវិធីខាងឆ្វេងទៅខាងស្ដាំ"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"អេក្រង់ពេញខាងលើ"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"ខាងលើ 70%"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"ខាងលើ 50%"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"ចាប់ផ្ដើមឡើងវិញ"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"កុំបង្ហាញម្ដងទៀត"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"ចុចពីរដងដើម្បី\nផ្លាស់ទីកម្មវិធីនេះ"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"ពង្រីក <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="restore_button_text" msgid="5377571986086775288">"ស្ដារ <xliff:g id="APP_NAME">%1$s</xliff:g> ឡើងវិញ"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"បង្រួម <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="close_button_text" msgid="4544839489310949894">"បិទ <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="back_button_text" msgid="1469718707134137085">"ថយក្រោយ"</string> <string name="handle_text" msgid="4419667835599523257">"ឈ្មោះអ្នកប្រើប្រាស់កម្មវិធី"</string> <string name="app_icon_text" msgid="2823268023931811747">"រូបកម្មវិធី"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"ប្ដូរទំហំវិនដូទៅឆ្វេង"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"ប្ដូរទំហំវិនដូទៅស្ដាំ"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"ស្ដារ ឬបង្កើនទំហំវិនដូជាអតិបរមា"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"ពង្រីកទំហំវិនដូកម្មវិធី"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"ស្ដារទំហំវិនដូ"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"បង្រួមវិនដូកម្មវិធី"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"បិទវិនដូកម្មវិធី"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"ការកំណត់បើកតាមលំនាំដើម"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"ជ្រើសរើសរបៀបបើកតំណបណ្ដាញសម្រាប់កម្មវិធីនេះ"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"នៅក្នុងកម្មវិធី"</string> diff --git a/libs/WindowManager/Shell/res/values-kn/strings.xml b/libs/WindowManager/Shell/res/values-kn/strings.xml index 61ee3c3915db..6a9e07f91d08 100644 --- a/libs/WindowManager/Shell/res/values-kn/strings.xml +++ b/libs/WindowManager/Shell/res/values-kn/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"50% ಎಡಕ್ಕೆ"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"30% ಎಡಕ್ಕೆ"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"ಬಲ ಫುಲ್ ಸ್ಕ್ರೀನ್"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"ಮೇಲಿನ ಮತ್ತು ಕೆಳಗಿನ ಆ್ಯಪ್ಗಳನ್ನು ಅದಲು-ಬದಲು ಮಾಡಿ"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"ಎಡ ಮತ್ತು ಬಲದ ಆ್ಯಪ್ಗಳನ್ನು ಅದಲು-ಬದಲು ಮಾಡಿ"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"ಮೇಲಿನ ಫುಲ್ ಸ್ಕ್ರೀನ್"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"70% ಮೇಲಕ್ಕೆ"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"50% ಮೇಲಕ್ಕೆ"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"ಮರುಪ್ರಾರಂಭಿಸಿ"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"ಮತ್ತೊಮ್ಮೆ ತೋರಿಸಬೇಡಿ"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"ಈ ಆ್ಯಪ್ ಅನ್ನು ಸರಿಸಲು\nಡಬಲ್-ಟ್ಯಾಪ್ ಮಾಡಿ"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"<xliff:g id="APP_NAME">%1$s</xliff:g> ಅನ್ನು ಮ್ಯಾಕ್ಸಿಮೈಸ್ ಮಾಡಿ"</string> + <string name="restore_button_text" msgid="5377571986086775288">"<xliff:g id="APP_NAME">%1$s</xliff:g> ಅನ್ನು ಮರುಸ್ಥಾಪಿಸಿ"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"<xliff:g id="APP_NAME">%1$s</xliff:g> ಅನ್ನು ಮಿನಿಮೈಸ್ ಮಾಡಿ"</string> + <string name="close_button_text" msgid="4544839489310949894">"<xliff:g id="APP_NAME">%1$s</xliff:g> ಅನ್ನು ಮುಚ್ಚಿ"</string> <string name="back_button_text" msgid="1469718707134137085">"ಹಿಂದಕ್ಕೆ"</string> <string name="handle_text" msgid="4419667835599523257">"ಆ್ಯಪ್ ಹ್ಯಾಂಡಲ್"</string> <string name="app_icon_text" msgid="2823268023931811747">"ಆ್ಯಪ್ ಐಕಾನ್"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"ಮರುಗಾತ್ರಗೊಳಿಸಿ ವಿಂಡೋವನ್ನು ಎಡಕ್ಕೆ ಸರಿಸಿ"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"ಮರುಗಾತ್ರಗೊಳಿಸಿ ವಿಂಡೋವನ್ನು ಬಲಕ್ಕೆ ಸರಿಸಿ"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"ವಿಂಡೋ ಗಾತ್ರವನ್ನು ಗರಿಷ್ಠಗೊಳಿಸಿ ಅಥವಾ ಮರುಸ್ಥಾಪಿಸಿ"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"ಆ್ಯಪ್ ವಿಂಡೋದ ಗಾತ್ರವನ್ನು ಮ್ಯಾಕ್ಸಿಮೈಸ್ ಮಾಡಿ"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"ವಿಂಡೋ ಗಾತ್ರವನ್ನು ಮರುಸ್ಥಾಪಿಸಿ"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"ಆ್ಯಪ್ ವಿಂಡೋವನ್ನು ಮಿನಿಮೈಸ್ ಮಾಡಿ"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"ಆ್ಯಪ್ ವಿಂಡೋವನ್ನು ಮುಚ್ಚಿರಿ"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"ಡೀಫಾಲ್ಟ್ ಸೆಟ್ಟಿಂಗ್ಗಳಿಂದ ತೆರೆಯಿರಿ"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"ಈ ಆ್ಯಪ್ಗೆ ವೆಬ್ ಲಿಂಕ್ಗಳನ್ನು ಹೇಗೆ ತೆರೆಯಬೇಕು ಎಂಬುದನ್ನು ಆಯ್ಕೆಮಾಡಿ"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"ಆ್ಯಪ್ನಲ್ಲಿ"</string> diff --git a/libs/WindowManager/Shell/res/values-ko/strings.xml b/libs/WindowManager/Shell/res/values-ko/strings.xml index 2b36ba1eb3d7..e94d8893010f 100644 --- a/libs/WindowManager/Shell/res/values-ko/strings.xml +++ b/libs/WindowManager/Shell/res/values-ko/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"왼쪽 화면 50%"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"왼쪽 화면 30%"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"오른쪽 화면 전체화면"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"상단 앱과 하단 앱 바꾸기"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"왼쪽 앱과 오른쪽 앱 바꾸기"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"위쪽 화면 전체화면"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"위쪽 화면 70%"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"위쪽 화면 50%"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"다시 시작"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"다시 표시 안함"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"두 번 탭하여\n이 앱 이동"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"<xliff:g id="APP_NAME">%1$s</xliff:g> 최대화"</string> + <string name="restore_button_text" msgid="5377571986086775288">"<xliff:g id="APP_NAME">%1$s</xliff:g> 복원"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"<xliff:g id="APP_NAME">%1$s</xliff:g> 최소화"</string> + <string name="close_button_text" msgid="4544839489310949894">"<xliff:g id="APP_NAME">%1$s</xliff:g> 닫기"</string> <string name="back_button_text" msgid="1469718707134137085">"뒤로"</string> <string name="handle_text" msgid="4419667835599523257">"앱 핸들"</string> <string name="app_icon_text" msgid="2823268023931811747">"앱 아이콘"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"창 크기 왼쪽으로 조절"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"창 크기 오른쪽으로 조절"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"창 최대화 또는 크기 복원"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"앱 창 크기 최대화"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"창 크기 복원"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"앱 창 최소화"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"앱 창 닫기"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"기본값으로 열기 설정"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"이 앱에서 웹 링크를 여는 방법을 선택하세요"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"앱에서"</string> diff --git a/libs/WindowManager/Shell/res/values-ky/strings.xml b/libs/WindowManager/Shell/res/values-ky/strings.xml index bb3c2fdae471..73e743686b77 100644 --- a/libs/WindowManager/Shell/res/values-ky/strings.xml +++ b/libs/WindowManager/Shell/res/values-ky/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Сол жактагы экранды 50%"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Сол жактагы экранды 30%"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Оң жактагы экранды толук экран режимине өткөрүү"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Өйдө жактагы колдонмону ылдый жактагы менен алмаштыруу"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Сол жактагы колдонмону оң жактагы менен алмаштыруу"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Үстүнкү экранды толук экран режимине өткөрүү"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Үстүнкү экранды 70%"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Үстүнкү экранды 50%"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Өчүрүп күйгүзүү"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Экинчи көрүнбөсүн"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Бул колдонмону жылдыруу үчүн\nэки жолу таптаңыз"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"<xliff:g id="APP_NAME">%1$s</xliff:g> чоңойтуу"</string> + <string name="restore_button_text" msgid="5377571986086775288">"<xliff:g id="APP_NAME">%1$s</xliff:g> калыбына келтирүү"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"<xliff:g id="APP_NAME">%1$s</xliff:g> кичирейтүү"</string> + <string name="close_button_text" msgid="4544839489310949894">"<xliff:g id="APP_NAME">%1$s</xliff:g> жабуу"</string> <string name="back_button_text" msgid="1469718707134137085">"Артка"</string> <string name="handle_text" msgid="4419667835599523257">"Колдонмонун маркери"</string> <string name="app_icon_text" msgid="2823268023931811747">"Колдонмонун сүрөтчөсү"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Терезенин өлчөмүн солго өзгөртүү"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Терезенин өлчөмүн оңго өзгөртүү"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Терезенин өлчөмүн чоңойтуу же калыбына келтирүү"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Колдонмонун терезесинин өлчөмүн чоңойтуу"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Колдонмонун терезесинин өлчөмүн калыбына келтирүү"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Колдонмонун терезесин кичирейтүү"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Колдонмонун терезесин жабуу"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"Демейки шартта ачылуучу шилтемелердин параметрлери"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Колдонмодо шилтемелер кантип ачыларын тандаңыз"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Колдонмодо"</string> diff --git a/libs/WindowManager/Shell/res/values-lo/strings.xml b/libs/WindowManager/Shell/res/values-lo/strings.xml index 8850923d8cca..fd47465de5da 100644 --- a/libs/WindowManager/Shell/res/values-lo/strings.xml +++ b/libs/WindowManager/Shell/res/values-lo/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"ຊ້າຍ 50%"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"ຊ້າຍ 30%"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"ເຕັມໜ້າຈໍຂວາ"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"ສະຫຼັບແອັບທາງເທິງກັບທາງລຸ່ມ"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"ສະຫຼັບແອັບທາງຊ້າຍກັບທາງຂວາ"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"ເຕັມໜ້າຈໍເທິງສຸດ"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"ເທິງສຸດ 70%"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"ເທິງສຸດ 50%"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"ຣີສະຕາດ"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"ບໍ່ຕ້ອງສະແດງອີກ"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"ແຕະສອງເທື່ອເພື່ອ\nຍ້າຍແອັບນີ້"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"ຂະຫຍາຍ <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="restore_button_text" msgid="5377571986086775288">"ກູ້ຄືນ <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"ຫຍໍ້ <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="close_button_text" msgid="4544839489310949894">"ປິດ <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="back_button_text" msgid="1469718707134137085">"ກັບຄືນ"</string> <string name="handle_text" msgid="4419667835599523257">"ຊື່ຜູ້ໃຊ້ແອັບ"</string> <string name="app_icon_text" msgid="2823268023931811747">"ໄອຄອນແອັບ"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"ປັບຂະໜາດໜ້າຈໍໄປທາງຊ້າຍ"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"ປັບຂະໜາດໜ້າຈໍໄປທາງຂວາ"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"ຂະຫຍາຍ ຫຼື ຄືນຄ່າຂະໜາດໜ້າຈໍ"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"ຂະຫຍາຍຂະໜາດໜ້າຈໍແອັບໃຫ້ໃຫຍ່ສຸດ"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"ກູ້ຄືນຂະໜາດໜ້າຈໍ"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"ຫຍໍ້ໜ້າຈໍແອັບ"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"ປິດໜ້າຈໍແອັບ"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"ເປີດຕາມການຕັ້ງຄ່າເລີ່ມຕົ້ນ"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"ເລືອກວິທີເປີດລິ້ງເວັບສຳລັບແອັບນີ້"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"ໃນແອັບ"</string> diff --git a/libs/WindowManager/Shell/res/values-lt/strings.xml b/libs/WindowManager/Shell/res/values-lt/strings.xml index 8ed826aa11fc..902ef048b11e 100644 --- a/libs/WindowManager/Shell/res/values-lt/strings.xml +++ b/libs/WindowManager/Shell/res/values-lt/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Kairysis ekranas 50 %"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Kairysis ekranas 30 %"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Dešinysis ekranas viso ekrano režimu"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Sukeisti viršutiniąją programą su apatiniąja"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Sukeisti kairiąją programą su dešiniąja"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Viršutinis ekranas viso ekrano režimu"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Viršutinis ekranas 70 %"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Viršutinis ekranas 50 %"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Paleisti iš naujo"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Daugiau neberodyti"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Dukart palieskite, kad\nperkeltumėte šią programą"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"Padidinti „<xliff:g id="APP_NAME">%1$s</xliff:g>“"</string> + <string name="restore_button_text" msgid="5377571986086775288">"Atkurti „<xliff:g id="APP_NAME">%1$s</xliff:g>“"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"Sumažinti „<xliff:g id="APP_NAME">%1$s</xliff:g>“"</string> + <string name="close_button_text" msgid="4544839489310949894">"Uždaryti „<xliff:g id="APP_NAME">%1$s</xliff:g>“"</string> <string name="back_button_text" msgid="1469718707134137085">"Atgal"</string> <string name="handle_text" msgid="4419667835599523257">"Programos kreipinys"</string> <string name="app_icon_text" msgid="2823268023931811747">"Programos piktograma"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Pakeisti lango dydį kairėje"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Pakeisti lango dydį dešinėje"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Padidinti arba atkurti lango dydį"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Padidinti programos lango dydį"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Atkurti lango dydį"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Sumažinti programos langą"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Uždaryti programos langą"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"Atidaryti pagal numatytuosius nustatymus"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Pasirinkite, kaip atidaryti šios programos žiniatinklio nuorodas"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Programoje"</string> diff --git a/libs/WindowManager/Shell/res/values-lv/strings.xml b/libs/WindowManager/Shell/res/values-lv/strings.xml index 1227055b3222..b4095dfd6f1e 100644 --- a/libs/WindowManager/Shell/res/values-lv/strings.xml +++ b/libs/WindowManager/Shell/res/values-lv/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Pa kreisi 50%"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Pa kreisi 30%"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Labā daļa pa visu ekrānu"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Apmainīt vietām lietotni augšpusē ar lietotni apakšpusē"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Apmainīt vietām lietotni kreisajā pusē ar lietotni labajā pusē"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Augšdaļa pa visu ekrānu"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Augšdaļa 70%"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Augšdaļa 50%"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Restartēt"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Vairs nerādīt"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Veiciet dubultskārienu,\nlai pārvietotu šo lietotni"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"Maksimizēt lietotni <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="restore_button_text" msgid="5377571986086775288">"Atjaunot lietotni <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"Minimizēt lietotni <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="close_button_text" msgid="4544839489310949894">"Aizvērt lietotni <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="back_button_text" msgid="1469718707134137085">"Atpakaļ"</string> <string name="handle_text" msgid="4419667835599523257">"Lietotnes turis"</string> <string name="app_icon_text" msgid="2823268023931811747">"Lietotnes ikona"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Mainīt loga lielumu uz kreiso pusi"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Mainīt loga lielumu uz labo pusi"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Maksimizēt vai atjaunot loga lielumu"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Maksimizēt lietotnes loga lielumu"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Atjaunot loga lielumu"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Minimizēt lietotnes logu"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Aizvērt lietotnes logu"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"Atvērt pēc noklusējuma iestatījumiem"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Izvēlieties, kā atvērt šajā lietotnē norādītās saites"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Lietotnē"</string> diff --git a/libs/WindowManager/Shell/res/values-mk/strings.xml b/libs/WindowManager/Shell/res/values-mk/strings.xml index eb513748cdba..be97489d6159 100644 --- a/libs/WindowManager/Shell/res/values-mk/strings.xml +++ b/libs/WindowManager/Shell/res/values-mk/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Левиот 50%"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Левиот 30%"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Десниот на цел екран"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Заменете ги местата на горната и долната апликација"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Заменете ги местата на левата и десната апликација"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Горниот на цел екран"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Горниот 70%"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Горниот 50%"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Рестартирај"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Не прикажувај повторно"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Допрете двапати за да ја\nпоместите апликацијава"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"Максимизирај <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="restore_button_text" msgid="5377571986086775288">"Врати <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"Минимизирај <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="close_button_text" msgid="4544839489310949894">"Затвори <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="back_button_text" msgid="1469718707134137085">"Назад"</string> <string name="handle_text" msgid="4419667835599523257">"Прекар на апликацијата"</string> <string name="app_icon_text" msgid="2823268023931811747">"Икона на апликацијата"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Променете ја големината на прозорецот налево"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Променете ја големината на прозорецот надесно"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Максимизирајте или вратете ја големината на прозорецот"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Максимизирај ја големината на прозорецот на апликацијата"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Врати ја големината на прозорецот"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Минимизирај го прозорецот на апликацијата"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Затвори го прозорецот на апликацијата"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"Отвори според стандардните поставки"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Изберете како да се отвораат линковите за апликацијава"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Во апликацијата"</string> diff --git a/libs/WindowManager/Shell/res/values-mn/strings.xml b/libs/WindowManager/Shell/res/values-mn/strings.xml index fca2f7ce9177..d8efd8b2a103 100644 --- a/libs/WindowManager/Shell/res/values-mn/strings.xml +++ b/libs/WindowManager/Shell/res/values-mn/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Зүүн 50%"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Зүүн 30%"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Баруун талын бүтэн дэлгэц"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Дээд талын аппыг доод талынхаар солих"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Зүүн талын аппыг баруун талынхаар солих"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Дээд талын бүтэн дэлгэц"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Дээд 70%"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Дээд 50%"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Дахин эхлүүлэх"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Дахиж бүү харуул"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Энэ аппыг зөөхийн тулд\nхоёр товшино уу"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"<xliff:g id="APP_NAME">%1$s</xliff:g>-г томруулах"</string> + <string name="restore_button_text" msgid="5377571986086775288">"<xliff:g id="APP_NAME">%1$s</xliff:g>-г сэргээх"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"<xliff:g id="APP_NAME">%1$s</xliff:g>-г жижгэрүүлэх"</string> + <string name="close_button_text" msgid="4544839489310949894">"<xliff:g id="APP_NAME">%1$s</xliff:g>-г хаах"</string> <string name="back_button_text" msgid="1469718707134137085">"Буцах"</string> <string name="handle_text" msgid="4419667835599523257">"Аппын бариул"</string> <string name="app_icon_text" msgid="2823268023931811747">"Aппын дүрс тэмдэг"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Цонхны хэмжээг зүүн тал руу өөрчлөх"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Цонхны хэмжээг баруун тал руу өөрчлөх"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Цонхны хэмжээг томруулах эсвэл сэргээх"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Аппын цонхны хэмжээг томруулах"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Цонхны хэмжээг сэргээх"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Аппын цонхыг жижгэрүүлэх"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Аппын цонхыг хаах"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"Өгөгдмөл тохиргоогоор нээх"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Энэ аппад веб холбоосыг хэрхэн нээхийг сонгоно уу"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Аппад"</string> diff --git a/libs/WindowManager/Shell/res/values-mr/strings.xml b/libs/WindowManager/Shell/res/values-mr/strings.xml index 888118732b1a..d09a78426669 100644 --- a/libs/WindowManager/Shell/res/values-mr/strings.xml +++ b/libs/WindowManager/Shell/res/values-mr/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"डावी 50%"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"डावी 30%"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"उजवी फुल स्क्रीन"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"सर्वात वरचे अॅप हे तळाशी असलेल्या अॅपने स्वॅप करा"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"डावीकडील अॅप हे उजवीकडील अॅपने स्वॅप करा"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"शीर्ष फुल स्क्रीन"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"शीर्ष 70%"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"शीर्ष 50%"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"रीस्टार्ट करा"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"पुन्हा दाखवू नका"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"हे ॲप हलवण्यासाठी\nदोनदा टॅप करा"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"<xliff:g id="APP_NAME">%1$s</xliff:g> मोठे करा"</string> + <string name="restore_button_text" msgid="5377571986086775288">"<xliff:g id="APP_NAME">%1$s</xliff:g> रिस्टोअर करा"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"<xliff:g id="APP_NAME">%1$s</xliff:g> लहान करा"</string> + <string name="close_button_text" msgid="4544839489310949894">"<xliff:g id="APP_NAME">%1$s</xliff:g> बंद करा"</string> <string name="back_button_text" msgid="1469718707134137085">"मागे जा"</string> <string name="handle_text" msgid="4419667835599523257">"अॅपचे हँडल"</string> <string name="app_icon_text" msgid="2823268023931811747">"अॅप आयकन"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"अॅप विंडोचा डावीकडे आकार बदला"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"अॅप विंडोचा उजवीकडे आकार बदला"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"विंडोचा आकार मोठा करा किंवा रिस्टोअर करा"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"अॅप विंडोचा आकार मोठा करा"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"विंडोचा आकार रिस्टोअर करा"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"अॅप विंडो लहान करा"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"अॅप विंडो बंद करा"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"बाय डीफॉल्ट सेटिंग्ज उघडा"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"या अॅपसाठीच्या वेब लिंक कशा उघडाव्यात हे निवडा"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"ॲपमध्ये"</string> diff --git a/libs/WindowManager/Shell/res/values-my/strings.xml b/libs/WindowManager/Shell/res/values-my/strings.xml index 3451701a0456..2342564930b4 100644 --- a/libs/WindowManager/Shell/res/values-my/strings.xml +++ b/libs/WindowManager/Shell/res/values-my/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"ဘယ်ဘက် မျက်နှာပြင် ၅၀%"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"ဘယ်ဘက် မျက်နှာပြင် ၃၀%"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"ညာဘက် မျက်နှာပြင်အပြည့်"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"အပေါ်အက်ပ်ကို အောက်သို့ ပြောင်းရန်"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"ဘယ်ဘက်အက်ပ်ကို ညာဘက်သို့ ပြောင်းရန်"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"အပေါ်ဘက် မျက်နှာပြင်အပြည့်"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"အပေါ်ဘက် မျက်နှာပြင် ၇၀%"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"အပေါ်ဘက် မျက်နှာပြင် ၅၀%"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"ပြန်စရန်"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"နောက်ထပ်မပြပါနှင့်"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"ဤအက်ပ်ကို ရွှေ့ရန်\nနှစ်ချက်တို့ပါ"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"<xliff:g id="APP_NAME">%1$s</xliff:g> ချဲ့ရန်"</string> + <string name="restore_button_text" msgid="5377571986086775288">"<xliff:g id="APP_NAME">%1$s</xliff:g> ကို ပြန်ယူရန်"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"<xliff:g id="APP_NAME">%1$s</xliff:g> ချုံ့ရန်"</string> + <string name="close_button_text" msgid="4544839489310949894">"<xliff:g id="APP_NAME">%1$s</xliff:g> ပိတ်ရန်"</string> <string name="back_button_text" msgid="1469718707134137085">"နောက်သို့"</string> <string name="handle_text" msgid="4419667835599523257">"အက်ပ်သုံးသူအမည်"</string> <string name="app_icon_text" msgid="2823268023931811747">"အက်ပ်သင်္ကေတ"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"ဝင်းဒိုးကို ဘယ်ဘက်သို့ အရွယ်ပြင်ရန်"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"ဝင်းဒိုးကို ညာဘက်သို့ အရွယ်ပြင်ရန်"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"ဝင်းဒိုးအရွယ်အစားကို ချဲ့ရန် (သို့) ပြန်ပြောင်းရန်"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"အက်ပ်ဝင်းဒိုး အရွယ်အစားကို ချဲ့ရန်"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"ဝင်းဒိုးအရွယ်အစား ပြန်ပြောင်းရန်"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"အက်ပ်ဝင်းဒိုးကို ချုံ့ရန်"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"အက်ပ်ဝင်းဒိုးကို ပိတ်ရန်"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"မူရင်းဆက်တင်ဖြင့် ဖွင့်ရန်"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"ဤအက်ပ်အတွက် ဝဘ်လင့်ခ်များ မည်သို့ဖွင့်မည်ကို ရွေးပါ"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"အက်ပ်တွင်"</string> diff --git a/libs/WindowManager/Shell/res/values-nb/strings.xml b/libs/WindowManager/Shell/res/values-nb/strings.xml index 3a4100c5f154..fb61a105eaaa 100644 --- a/libs/WindowManager/Shell/res/values-nb/strings.xml +++ b/libs/WindowManager/Shell/res/values-nb/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Sett størrelsen på den venstre delen av skjermen til 50 %"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Sett størrelsen på den venstre delen av skjermen til 30 %"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Utvid den høyre delen av skjermen til hele skjermen"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Bytt om på den øvre og nedre appen"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Bytt om på venstre og høyre app"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Utvid den øverste delen av skjermen til hele skjermen"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Sett størrelsen på den øverste delen av skjermen til 70 %"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Sett størrelsen på den øverste delen av skjermen til 50 %"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Start på nytt"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ikke vis dette igjen"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Dobbelttrykk for\nå flytte denne appen"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"Maksimer <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="restore_button_text" msgid="5377571986086775288">"Gjenopprett <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"Minimer <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="close_button_text" msgid="4544839489310949894">"Lukk <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="back_button_text" msgid="1469718707134137085">"Tilbake"</string> <string name="handle_text" msgid="4419667835599523257">"Apphåndtak"</string> <string name="app_icon_text" msgid="2823268023931811747">"Appikon"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Endre størrelsen på vinduet til venstre"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Endre størrelsen på vinduet til høyre"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Maksimer eller gjenopprett størrelsen på vinduet"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Maksimer størrelsen på appvinduet"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Gjenopprett vindusstørrelsen"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Minimer appvinduet"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Lukk appvinduet"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"Innstillinger for åpning som standard"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Velg hvordan nettlinker skal åpnes for denne appen"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"I appen"</string> diff --git a/libs/WindowManager/Shell/res/values-ne/strings.xml b/libs/WindowManager/Shell/res/values-ne/strings.xml index cc31e384260c..fdfa227b8080 100644 --- a/libs/WindowManager/Shell/res/values-ne/strings.xml +++ b/libs/WindowManager/Shell/res/values-ne/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"बायाँ भाग ५०%"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"बायाँ भाग ३०%"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"दायाँ भाग फुल स्क्रिन"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"सिरान र पुछारको एप अदलबदल गर्नुहोस्"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"दायाँ र बायाँतिरको एप अदलबदल गर्नुहोस्"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"माथिल्लो भाग फुल स्क्रिन"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"माथिल्लो भाग ७०%"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"माथिल्लो भाग ५०%"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"रिस्टार्ट गर्नुहोस्"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"फेरि नदेखाउनुहोस्"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"यो एप सार्न डबल\nट्याप गर्नुहोस्"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"<xliff:g id="APP_NAME">%1$s</xliff:g> म्याक्सिमाइज गर्नुहोस्"</string> + <string name="restore_button_text" msgid="5377571986086775288">"<xliff:g id="APP_NAME">%1$s</xliff:g> रिस्टोर गर्नुहोस्"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"<xliff:g id="APP_NAME">%1$s</xliff:g> मिनिमाइज गर्नुहोस्"</string> + <string name="close_button_text" msgid="4544839489310949894">"<xliff:g id="APP_NAME">%1$s</xliff:g> बन्द गर्नुहोस्"</string> <string name="back_button_text" msgid="1469718707134137085">"पछाडि"</string> <string name="handle_text" msgid="4419667835599523257">"एपको ह्यान्डल"</string> <string name="app_icon_text" msgid="2823268023931811747">"एपको आइकन"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"विन्डोको आकार बदलेर बायाँतिर लैजानुहोस्"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"विन्डोको आकार बदलेर दायाँतिर लैजानुहोस्"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"विन्डोको आकार म्याक्सिमाइज गर्नुहोस् वा रिस्टोर गर्नुहोस्"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"एपको विन्डोको आकार म्याक्सिमाइज गर्नुहोस्"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"विन्डोको आकार रिस्टोर गर्नुहोस्"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"एपको विन्डो मिनिमाइज गर्नुहोस्"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"एपको विन्डो बन्द गर्नुहोस्"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"डिफल्ट सेटिङअनुसार खोल्नुहोस्"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"यो एपका वेब लिंकहरू खोल्ने तरिका छनौट गर्नुहोस्"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"एपमा"</string> diff --git a/libs/WindowManager/Shell/res/values-nl/strings.xml b/libs/WindowManager/Shell/res/values-nl/strings.xml index 4234ddf0e456..578877c65dd1 100644 --- a/libs/WindowManager/Shell/res/values-nl/strings.xml +++ b/libs/WindowManager/Shell/res/values-nl/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Linkerscherm 50%"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Linkerscherm 30%"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Rechterscherm op volledig scherm"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Apps bovenaan en onderaan omwisselen"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Apps links en rechts omwisselen"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Bovenste scherm op volledig scherm"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Bovenste scherm 70%"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Bovenste scherm 50%"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Opnieuw opstarten"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Niet opnieuw tonen"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Dubbeltik om\ndeze app te verplaatsen"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"<xliff:g id="APP_NAME">%1$s</xliff:g> maximaliseren"</string> + <string name="restore_button_text" msgid="5377571986086775288">"<xliff:g id="APP_NAME">%1$s</xliff:g> herstellen"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"<xliff:g id="APP_NAME">%1$s</xliff:g> minimaliseren"</string> + <string name="close_button_text" msgid="4544839489310949894">"<xliff:g id="APP_NAME">%1$s</xliff:g> sluiten"</string> <string name="back_button_text" msgid="1469718707134137085">"Terug"</string> <string name="handle_text" msgid="4419667835599523257">"App-handgreep"</string> <string name="app_icon_text" msgid="2823268023931811747">"App-icoon"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Formaat van venster naar links aanpassen"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Formaat van venster naar rechts aanpassen"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Formaat van venster maximaliseren of herstellen"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Formaat van app-venster maximaliseren"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Vensterformaat herstellen"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"App-venster minimaliseren"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"App-venster sluiten"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"Instellingen voor Standaard openen"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Kies hoe je weblinks voor deze app wilt openen"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"In de app"</string> diff --git a/libs/WindowManager/Shell/res/values-pl/strings.xml b/libs/WindowManager/Shell/res/values-pl/strings.xml index 59deb01e689f..50861bac617b 100644 --- a/libs/WindowManager/Shell/res/values-pl/strings.xml +++ b/libs/WindowManager/Shell/res/values-pl/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"50% lewej części ekranu"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"30% lewej części ekranu"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Prawa część ekranu na pełnym ekranie"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Zamień aplikację na górze z tą na dole"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Zamień aplikację po lewej z tą po prawej"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Górna część ekranu na pełnym ekranie"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"70% górnej części ekranu"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"50% górnej części ekranu"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Uruchom ponownie"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Nie pokazuj ponownie"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Aby przenieść aplikację,\nkliknij dwukrotnie"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"Maksymalizuj okno aplikacji <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="restore_button_text" msgid="5377571986086775288">"Przywróć rozmiar okna aplikacji <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"Minimalizuj okno aplikacji <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="close_button_text" msgid="4544839489310949894">"Zamknij <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="back_button_text" msgid="1469718707134137085">"Wstecz"</string> <string name="handle_text" msgid="4419667835599523257">"Uchwyt aplikacji"</string> <string name="app_icon_text" msgid="2823268023931811747">"Ikona aplikacji"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Zmień rozmiar okna do lewej"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Zmień rozmiar okna do prawej"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Zmaksymalizuj lub przywróć rozmiar okna"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Maksymalizuj rozmiar okna aplikacji"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Przywróć rozmiar okna"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Minimalizuj okno aplikacji"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Zamknij okno aplikacji"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"Ustawienia domyślnego otwierania"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Wybierz, gdzie chcesz otwierać linki z tej aplikacji"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"W aplikacji"</string> diff --git a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml index 593f830e0d6e..a8f7403a94db 100644 --- a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml +++ b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Esquerda a 50%"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Esquerda a 30%"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Lado direito em tela cheia"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Trocar o app de cima pelo de baixo"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Trocar o app da esquerda pelo da direita"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Parte superior em tela cheia"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Parte superior a 70%"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Parte superior a 50%"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Reiniciar"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Não mostrar novamente"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Toque duas vezes para\nmover o app"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"Maximizar <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="restore_button_text" msgid="5377571986086775288">"Restaurar <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"Minimizar <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="close_button_text" msgid="4544839489310949894">"Fechar <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="back_button_text" msgid="1469718707134137085">"Voltar"</string> <string name="handle_text" msgid="4419667835599523257">"Identificador do app"</string> <string name="app_icon_text" msgid="2823268023931811747">"Ícone do app"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Redimensionar janela para a esquerda"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Redimensionar janela para a direita"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Maximizar ou restaurar o tamanho da janela"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Maximizar o tamanho da janela do app"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Restaurar o tamanho da janela"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Minimizar janela do app"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Fechar janela do app"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"Configurações \"Abrir por padrão\""</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Escolha como abrir links da Web para este app"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"No app"</string> diff --git a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml index fcf59163be83..04d404287327 100644 --- a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml +++ b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"50% no ecrã esquerdo"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"30% no ecrã esquerdo"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Ecrã direito inteiro"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Trocar app superior pela inferior"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Trocar app da esquerda pela da direita"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Ecrã superior inteiro"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"70% no ecrã superior"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"50% no ecrã superior"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Reiniciar"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Não mostrar de novo"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Toque duas vezes\npara mover esta app"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"Maximizar <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="restore_button_text" msgid="5377571986086775288">"Restaurar <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"Minimizar <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="close_button_text" msgid="4544839489310949894">"Fechar <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="back_button_text" msgid="1469718707134137085">"Anterior"</string> <string name="handle_text" msgid="4419667835599523257">"Indicador da app"</string> <string name="app_icon_text" msgid="2823268023931811747">"Ícone da app"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Redimensionar janela para a esquerda"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Redimensionar janela para a direita"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Maximizar ou restaurar tamanho da janela"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Maximizar tamanho da janela da app"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Restaurar tamanho da janela"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Minimizar janela da app"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Fechar janela da app"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"Definições de Abrir por predefinição"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Escolha como abrir links da Web para esta app"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Na app"</string> diff --git a/libs/WindowManager/Shell/res/values-pt/strings.xml b/libs/WindowManager/Shell/res/values-pt/strings.xml index 593f830e0d6e..a8f7403a94db 100644 --- a/libs/WindowManager/Shell/res/values-pt/strings.xml +++ b/libs/WindowManager/Shell/res/values-pt/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Esquerda a 50%"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Esquerda a 30%"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Lado direito em tela cheia"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Trocar o app de cima pelo de baixo"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Trocar o app da esquerda pelo da direita"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Parte superior em tela cheia"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Parte superior a 70%"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Parte superior a 50%"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Reiniciar"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Não mostrar novamente"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Toque duas vezes para\nmover o app"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"Maximizar <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="restore_button_text" msgid="5377571986086775288">"Restaurar <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"Minimizar <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="close_button_text" msgid="4544839489310949894">"Fechar <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="back_button_text" msgid="1469718707134137085">"Voltar"</string> <string name="handle_text" msgid="4419667835599523257">"Identificador do app"</string> <string name="app_icon_text" msgid="2823268023931811747">"Ícone do app"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Redimensionar janela para a esquerda"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Redimensionar janela para a direita"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Maximizar ou restaurar o tamanho da janela"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Maximizar o tamanho da janela do app"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Restaurar o tamanho da janela"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Minimizar janela do app"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Fechar janela do app"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"Configurações \"Abrir por padrão\""</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Escolha como abrir links da Web para este app"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"No app"</string> diff --git a/libs/WindowManager/Shell/res/values-ro/strings.xml b/libs/WindowManager/Shell/res/values-ro/strings.xml index 81db82a8c526..d781d0893fa6 100644 --- a/libs/WindowManager/Shell/res/values-ro/strings.xml +++ b/libs/WindowManager/Shell/res/values-ro/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Partea stângă: 50%"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Partea stângă: 30%"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Partea dreaptă pe ecran complet"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Schimbă aplicația de sus cu cea de jos"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Schimbă aplicația din stânga cu cea din dreapta"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Partea de sus pe ecran complet"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Partea de sus: 70%"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Partea de sus: 50%"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Repornește"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Nu mai afișa"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Atinge de două ori\nca să muți aplicația"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"Maximizează <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="restore_button_text" msgid="5377571986086775288">"Restabilește <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"Minimizează <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="close_button_text" msgid="4544839489310949894">"Închide <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="back_button_text" msgid="1469718707134137085">"Înapoi"</string> <string name="handle_text" msgid="4419667835599523257">"Handle de aplicație"</string> <string name="app_icon_text" msgid="2823268023931811747">"Pictograma aplicației"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Redimensionează fereastra la stânga"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Redimensionează fereastra la dreapta"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Maximizează sau restabilește dimensiunea ferestrei"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Maximizează dimensiunea ferestrei aplicației"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Restabilește dimensiunea ferestrei"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Minimizează fereastra aplicației"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Închide fereastra aplicației"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"Setări de deschidere în mod prestabilit"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Alege modul de deschidere a linkurilor web pentru aplicație"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"În aplicație"</string> diff --git a/libs/WindowManager/Shell/res/values-ru/strings.xml b/libs/WindowManager/Shell/res/values-ru/strings.xml index aa6f484debee..1837c31cbeef 100644 --- a/libs/WindowManager/Shell/res/values-ru/strings.xml +++ b/libs/WindowManager/Shell/res/values-ru/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Левый на 50%"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Левый на 30%"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Правый во весь экран"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Поменять местами приложения сверху и снизу"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Поменять местами приложения слева и справа"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Верхний во весь экран"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Верхний на 70%"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Верхний на 50%"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Перезапустить"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Больше не показывать"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Дважды нажмите, чтобы\nпереместить приложение."</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"Развернуть окно приложения \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string> + <string name="restore_button_text" msgid="5377571986086775288">"Восстановить окно приложения \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string> + <string name="minimize_button_text" msgid="5213953162664451152">"Свернуть окно приложения \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string> + <string name="close_button_text" msgid="4544839489310949894">"Закрыть окно приложения \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string> <string name="back_button_text" msgid="1469718707134137085">"Назад"</string> <string name="handle_text" msgid="4419667835599523257">"Обозначение приложения"</string> <string name="app_icon_text" msgid="2823268023931811747">"Значок приложения"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Растянуть окно влево"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Растянуть окно вправо"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Развернуть окно или восстановить его размер"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Развернуть окно приложения"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Восстановить размер окна"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Свернуть окно приложения"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Закрыть окно приложения"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"Настройки, регулирующие, как по умолчанию открываются ссылки"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Выберите, где будут открываться ссылки из этого приложения"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"В приложении"</string> diff --git a/libs/WindowManager/Shell/res/values-si/strings.xml b/libs/WindowManager/Shell/res/values-si/strings.xml index efa978aa4505..d2fc08229793 100644 --- a/libs/WindowManager/Shell/res/values-si/strings.xml +++ b/libs/WindowManager/Shell/res/values-si/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"වම් 50%"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"වම් 30%"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"දකුණු පූර්ණ තිරය"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"ඉහළ යෙදුම පහළ සමග මාරු කරන්න"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"වම් යෙදුම දකුණ සමග මාරු කරන්න"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"ඉහළම පූර්ණ තිරය"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"ඉහළම 70%"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"ඉහළම 50%"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"යළි අරඹන්න"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"නැවත නොපෙන්වන්න"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"මෙම යෙදුම ගෙන යාමට\nදෙවරක් තට්ටු කරන්න"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"<xliff:g id="APP_NAME">%1$s</xliff:g> විහිදන්න"</string> + <string name="restore_button_text" msgid="5377571986086775288">"<xliff:g id="APP_NAME">%1$s</xliff:g> ප්රතිසාධනය කරන්න"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"<xliff:g id="APP_NAME">%1$s</xliff:g> කුඩා කරන්න"</string> + <string name="close_button_text" msgid="4544839489310949894">"<xliff:g id="APP_NAME">%1$s</xliff:g> වසන්න"</string> <string name="back_button_text" msgid="1469718707134137085">"ආපසු"</string> <string name="handle_text" msgid="4419667835599523257">"යෙදුම් හසුරුව"</string> <string name="app_icon_text" msgid="2823268023931811747">"යෙදුම් නිරූපකය"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"කවුළුව වමට ප්රතිප්රමාණ කරන්න"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"කවුළුව දකුණට ප්රතිප්රමාණ කරන්න"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"කවුළු ප්රමාණය උපරිම කරන්න හෝ ප්රතිසාධනය කරන්න"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"යෙදුම් කවුළු ප්රමාණය උපරිම කරන්න"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"කවුළු ප්රමාණය ප්රතිසාධනය කරන්න"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"යෙදුම් කවුළුව අවම කරන්න"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"යෙදුම් කවුළුව වසන්න"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"පෙරනිමි සැකසීම් මඟින් විවෘත කරන්න"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"මෙම යෙදුම සඳහා වෙබ් සබැඳි විවෘත කරන ආකාරය තෝරා ගන්න"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"යෙදුම තුළ"</string> diff --git a/libs/WindowManager/Shell/res/values-sk/strings.xml b/libs/WindowManager/Shell/res/values-sk/strings.xml index fac26b49819a..a9c81a2e59ad 100644 --- a/libs/WindowManager/Shell/res/values-sk/strings.xml +++ b/libs/WindowManager/Shell/res/values-sk/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Ľavá – 50 %"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Ľavá – 30 %"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Pravá– na celú obrazovku"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Vymeniť hornú aplikáciu za dolnú"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Vymeniť ľavú aplikáciu za pravú"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Horná – na celú obrazovku"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Horná – 70 %"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Horná – 50 %"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Reštartovať"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Už nezobrazovať"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Túto aplikáciu\npresuniete dvojitým klepnutím"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"Maximalizovať <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="restore_button_text" msgid="5377571986086775288">"Obnoviť <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"Minimalizovať <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="close_button_text" msgid="4544839489310949894">"Zavrieť <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="back_button_text" msgid="1469718707134137085">"Späť"</string> <string name="handle_text" msgid="4419667835599523257">"Rukoväť aplikácie"</string> <string name="app_icon_text" msgid="2823268023931811747">"Ikona aplikácie"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Zmeniť veľkosť okna vľavo"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Zmeniť veľkosť okna vpravo"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Maximalizovať alebo obnoviť veľkosť okna"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Maximalizovať veľkosť okna aplikácie"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Obnoviť veľkosť okna"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Minimalizovať okno aplikácie"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Zavrieť okno aplikácie"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"Otvárať podľa predvolených nastavení"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Vyberte, ako sa majú v tejto aplikácii otvárať webové odkazy"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"V aplikácii"</string> diff --git a/libs/WindowManager/Shell/res/values-sl/strings.xml b/libs/WindowManager/Shell/res/values-sl/strings.xml index f8dacc4fde23..8dcca185e374 100644 --- a/libs/WindowManager/Shell/res/values-sl/strings.xml +++ b/libs/WindowManager/Shell/res/values-sl/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Levi 50 %"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Levi 30 %"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Desni v celozaslonski način"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Zamenjava zgornje aplikacije s spodnjo"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Zamenjava leve aplikacije z desno"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Zgornji v celozaslonski način"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Zgornji 70 %"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Zgornji 50 %"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Znova zaženi"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ne prikaži več"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Dvakrat se dotaknite\nza premik te aplikacije"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"Povečanje aplikacije <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="restore_button_text" msgid="5377571986086775288">"Obnovitev aplikacije <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"Pomanjšava aplikacije <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="close_button_text" msgid="4544839489310949894">"Zapiranje aplikacije <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="back_button_text" msgid="1469718707134137085">"Nazaj"</string> <string name="handle_text" msgid="4419667835599523257">"Identifikator aplikacije"</string> <string name="app_icon_text" msgid="2823268023931811747">"Ikona aplikacije"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Sprememba velikosti okna na levi"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Sprememba velikosti okna na desni"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Povečava ali obnovitev velikosti okna"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Povečanje velikosti okna aplikacije"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Obnovitev velikosti okna"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Pomanjšava okna aplikacije"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Zapiranje okna aplikacije"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"Nastavitve privzetega odpiranja"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Izberite način odpiranja spletnih povezav za to aplikacijo"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"V aplikaciji"</string> diff --git a/libs/WindowManager/Shell/res/values-sq/strings.xml b/libs/WindowManager/Shell/res/values-sq/strings.xml index 3e88ed15aff1..6f8bdef18dfe 100644 --- a/libs/WindowManager/Shell/res/values-sq/strings.xml +++ b/libs/WindowManager/Shell/res/values-sq/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Majtas 50%"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Majtas 30%"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Ekrani i plotë djathtas"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Ndërro aplikacionin lart me atë poshtë"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Ndërro aplikacionin majtas me atë djathtas"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Ekrani i plotë lart"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Lart 70%"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Lart 50%"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Rinis"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Mos e shfaq përsëri"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Trokit dy herë për të\nlëvizur këtë aplikacion"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"Maksimizo \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string> + <string name="restore_button_text" msgid="5377571986086775288">"Restauro \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string> + <string name="minimize_button_text" msgid="5213953162664451152">"Minimizo \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string> + <string name="close_button_text" msgid="4544839489310949894">"Mbyll \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string> <string name="back_button_text" msgid="1469718707134137085">"Pas"</string> <string name="handle_text" msgid="4419667835599523257">"Emërtimi i aplikacionit"</string> <string name="app_icon_text" msgid="2823268023931811747">"Ikona e aplikacionit"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Ndrysho përmasat e dritares në të majtë"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Ndrysho përmasat e dritares në të djathtë"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Maksimizo ose restauro madhësinë e dritares"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Maksimizo madhësinë e dritares së aplikacionit"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Restauro madhësinë e dritares"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Minimizo dritaren e aplikacionit"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Mbyll dritaren e aplikacionit"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"Hap sipas cilësimeve të parazgjedhura"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Zgjidh si do t\'i hapësh lidhjet e uebit për këtë aplikacion"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Në aplikacion"</string> diff --git a/libs/WindowManager/Shell/res/values-sv/strings.xml b/libs/WindowManager/Shell/res/values-sv/strings.xml index 0105e158ac08..d8893dfab5f1 100644 --- a/libs/WindowManager/Shell/res/values-sv/strings.xml +++ b/libs/WindowManager/Shell/res/values-sv/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Vänster 50 %"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Vänster 30 %"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Helskärm på höger skärm"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Byt plats på den översta och understa appen"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Byt plats på den vänstra och högra appen"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Helskärm på övre skärm"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Övre 70 %"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Övre 50 %"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Starta om"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Visa inte igen"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Tryck snabbt två gånger\nför att flytta denna app"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"Utöka <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="restore_button_text" msgid="5377571986086775288">"Återställ <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"Minimera <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="close_button_text" msgid="4544839489310949894">"Stäng <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="back_button_text" msgid="1469718707134137085">"Tillbaka"</string> <string name="handle_text" msgid="4419667835599523257">"Apphandtag"</string> <string name="app_icon_text" msgid="2823268023931811747">"Appikon"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Ändra storlek på fönstret åt vänster"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Ändra storlek på fönstret åt höger"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Maximera eller återställ fönsterstorleken"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Maximera appfönstrets storlek"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Återställ fönsterstorlek"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Minimera appfönstret"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Stäng appfönstret"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"Inställningar för Öppna som standard"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Välj hur webblänkar ska öppnas för den här appen"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"I appen"</string> diff --git a/libs/WindowManager/Shell/res/values-sw/strings.xml b/libs/WindowManager/Shell/res/values-sw/strings.xml index b4415cb58b87..3ee43f3a69a8 100644 --- a/libs/WindowManager/Shell/res/values-sw/strings.xml +++ b/libs/WindowManager/Shell/res/values-sw/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Kushoto 50%"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Kushoto 30%"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Skrini nzima ya kulia"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Badilisha nafasi ya programu ya juu na ya chini"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Badilisha nafasi ya programu ya kulia na ya kushoto"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Skrini nzima ya juu"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Juu 70%"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Juu 50%"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Zima kisha uwashe"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Usionyeshe tena"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Gusa mara mbili ili\nusogeze programu hii"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"Panua <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="restore_button_text" msgid="5377571986086775288">"Rejesha <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"Punguza <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="close_button_text" msgid="4544839489310949894">"Funga <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="back_button_text" msgid="1469718707134137085">"Rudi nyuma"</string> <string name="handle_text" msgid="4419667835599523257">"Utambulisho wa programu"</string> <string name="app_icon_text" msgid="2823268023931811747">"Aikoni ya Programu"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Badilisha ukubwa wa dirisha kushoto"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Badilisha ukubwa wa dirisha kulia"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Panua au urejeshe ukubwa wa dirisha"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Panua ukubwa wa dirisha la programu"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Rejesha ukubwa wa dirisha"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Punguza dirisha la programu"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Funga dirisha la programu"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"Fungua kwa mipangilio chaguomsingi"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Chagua jinsi ya kufungua viungo vya wavuti vya programu hii"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Kwenye programu"</string> diff --git a/libs/WindowManager/Shell/res/values-ta/strings.xml b/libs/WindowManager/Shell/res/values-ta/strings.xml index ccc9f9423e8a..a0e7c6acfb05 100644 --- a/libs/WindowManager/Shell/res/values-ta/strings.xml +++ b/libs/WindowManager/Shell/res/values-ta/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"இடது புறம் 50%"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"இடது புறம் 30%"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"வலது புறம் முழுத் திரை"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"மேலுள்ள ஆப்ஸைக் கீழுள்ள ஆப்ஸ் கொண்டு மாற்றும்"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"இடதுபுற ஆப்ஸை வலதுபுற ஆப்ஸ் கொண்டு மாற்றும்"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"மேற்புறம் முழுத் திரை"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"மேலே 70%"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"மேலே 50%"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"மீண்டும் தொடங்கு"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"மீண்டும் காட்டாதே"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"இந்த ஆப்ஸை நகர்த்த\nஇருமுறை தட்டவும்"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"<xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸைப் பெரிதாக்கும்"</string> + <string name="restore_button_text" msgid="5377571986086775288">"<xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸை மீட்டெடுக்கும்"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"<xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸைச் சிறிதாக்கும்"</string> + <string name="close_button_text" msgid="4544839489310949894">"<xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸை மூடும்"</string> <string name="back_button_text" msgid="1469718707134137085">"பின்செல்லும்"</string> <string name="handle_text" msgid="4419667835599523257">"ஆப்ஸ் ஹேண்டில்"</string> <string name="app_icon_text" msgid="2823268023931811747">"ஆப்ஸ் ஐகான்"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"சாளரத்தை இடதுபுறமாக அளவு மாற்றும்"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"சாளரத்தை வலதுபுறமாக அளவு மாற்றும்"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"சாளரத்தின் அளவைப் பெரிதாக்கும்/மீட்டெடுக்கும்"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"ஆப்ஸ் சாளரத்தின் அளவைப் பெரிதாக்கும்"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"சாளரத்தின் அளவை மீட்டெடுக்கும்"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"ஆப்ஸ் சாளரத்தைச் சிறிதாக்கும்"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"ஆப்ஸ் சாளரத்தை மூடும்"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"இயல்பாக அமைப்புகளைத் திறக்கும்"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"இந்த ஆப்ஸில் வலை இணைப்புகளைத் திறக்கும் வழிமுறையைத் தேர்வுசெய்யுங்கள்"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"ஆப்ஸில்"</string> diff --git a/libs/WindowManager/Shell/res/values-te/strings.xml b/libs/WindowManager/Shell/res/values-te/strings.xml index 96ce40abb477..e0930132e6f3 100644 --- a/libs/WindowManager/Shell/res/values-te/strings.xml +++ b/libs/WindowManager/Shell/res/values-te/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"ఎడమవైపు 50%"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"ఎడమవైపు 30%"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"కుడివైపు ఫుల్-స్క్రీన్"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"పైన ఉన్న యాప్ను కింద ఉన్న యాప్తో మార్చండి"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"ఎడమ వైపు ఉన్న యాప్ను కుడి వైపు ఉన్న యాప్తో మార్చండి"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"ఎగువ ఫుల్-స్క్రీన్"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"ఎగువ 70%"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"ఎగువ 50%"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"రీస్టార్ట్ చేయండి"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"మళ్లీ చూపవద్దు"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"ఈ యాప్ను తరలించడానికి\nడబుల్-ట్యాప్ చేయండి"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"<xliff:g id="APP_NAME">%1$s</xliff:g>ను పెద్దదిగా చేయండి"</string> + <string name="restore_button_text" msgid="5377571986086775288">"<xliff:g id="APP_NAME">%1$s</xliff:g>ను రీస్టోర్ చేయండి"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"<xliff:g id="APP_NAME">%1$s</xliff:g>ను చిన్నదిగా చేయండి"</string> + <string name="close_button_text" msgid="4544839489310949894">"<xliff:g id="APP_NAME">%1$s</xliff:g>ను మూసివేయండి"</string> <string name="back_button_text" msgid="1469718707134137085">"వెనుకకు"</string> <string name="handle_text" msgid="4419667835599523257">"యాప్ హ్యాండిల్"</string> <string name="app_icon_text" msgid="2823268023931811747">"యాప్ చిహ్నం"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"విండో ఎడమ వైపునకు సైజ్ను మార్చండి"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"విండో కుడి వైపునకు సైజ్ను మార్చండి"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"విండో సైజ్ను మ్యాగ్జిమైజ్ చేయండి లేదా రీస్టోర్ చేయండి"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"యాప్ విండో సైజ్ను పెద్దదిగా చేయండి"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"విండో సైజ్ను రీస్టోర్ చేయండి"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"యాప్ విండోను చిన్నదిగా చేయండి"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"యాప్ విండోను మూసివేయండి"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"ఆటోమేటిక్ సెట్టింగ్ల ద్వారా తెరవండి"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"ఈ యాప్నకు సంబంధించిన వెబ్ లింక్లను ఎలా తెరవాలో ఎంచుకోండి"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"యాప్లో"</string> diff --git a/libs/WindowManager/Shell/res/values-tl/strings.xml b/libs/WindowManager/Shell/res/values-tl/strings.xml index ad38be82a76a..f13de5b7846b 100644 --- a/libs/WindowManager/Shell/res/values-tl/strings.xml +++ b/libs/WindowManager/Shell/res/values-tl/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Gawing 50% ang nasa kaliwa"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Gawing 30% ang nasa kaliwa"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"I-full screen ang nasa kanan"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Pagpalitin ang app sa itaas at ibaba"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Pagpalitin ang app sa kaliwa at kanan"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"I-full screen ang nasa itaas"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Gawing 70% ang nasa itaas"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Gawing 50% ang nasa itaas"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"I-restart"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Huwag nang ipakita ulit"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"I-double tap para\nilipat ang app na ito"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"I-maximize ang <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="restore_button_text" msgid="5377571986086775288">"I-restore ang <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"I-minimize ang <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="close_button_text" msgid="4544839489310949894">"Isara ang <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="back_button_text" msgid="1469718707134137085">"Bumalik"</string> <string name="handle_text" msgid="4419667835599523257">"Handle ng app"</string> <string name="app_icon_text" msgid="2823268023931811747">"Icon ng App"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"I-resize pakaliwa ang window"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"I-resize pakanan ang window"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"I-maximize o i-restore ang laki ng window"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"I-maximize ang laki ng window ng app"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"I-restore ang laki ng window"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"I-minimize ang window ng app"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Isara ang window ng app"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"Buksan sa pamamagitan ng mga default na setting"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Piliin kung paano magbukas ng web link para sa app na ito"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Sa app"</string> diff --git a/libs/WindowManager/Shell/res/values-tr/strings.xml b/libs/WindowManager/Shell/res/values-tr/strings.xml index 2bc0a960bbb5..61fae05cc71c 100644 --- a/libs/WindowManager/Shell/res/values-tr/strings.xml +++ b/libs/WindowManager/Shell/res/values-tr/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Solda %50"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Solda %30"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Sağda tam ekran"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Üstteki uygulamayı alttaki uygulamayla değiştir"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Soldaki uygulamayı sağdakiyle değiştir"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Üstte tam ekran"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Üstte %70"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Üstte %50"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Yeniden başlat"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Bir daha gösterme"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Bu uygulamayı taşımak için\niki kez dokunun"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"<xliff:g id="APP_NAME">%1$s</xliff:g> uygulamasını büyüt"</string> + <string name="restore_button_text" msgid="5377571986086775288">"<xliff:g id="APP_NAME">%1$s</xliff:g> uygulamasını geri yükle"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"<xliff:g id="APP_NAME">%1$s</xliff:g> uygulamasını küçült"</string> + <string name="close_button_text" msgid="4544839489310949894">"<xliff:g id="APP_NAME">%1$s</xliff:g> uygulamasını kapat"</string> <string name="back_button_text" msgid="1469718707134137085">"Geri"</string> <string name="handle_text" msgid="4419667835599523257">"Uygulama tanıtıcısı"</string> <string name="app_icon_text" msgid="2823268023931811747">"Uygulama Simgesi"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Pencereyi sola yeniden boyutlandır"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Pencereyi sağa yeniden boyutlandır"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Pencereyi ekranı kaplayacak şekilde büyüt veya önceki boyutuna döndür"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Uygulama penceresini ekranı kaplayacak şekilde büyüt"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Pencere boyutunu geri yükle"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Uygulama penceresini küçült"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Uygulama penceresini kapat"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"Varsayılan olarak açma ayarları"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Bu uygulama için web bağlantılarının nasıl açılacağını seçin"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Uygulamada"</string> diff --git a/libs/WindowManager/Shell/res/values-uk/strings.xml b/libs/WindowManager/Shell/res/values-uk/strings.xml index c1aa82e472b1..ada82dfdc126 100644 --- a/libs/WindowManager/Shell/res/values-uk/strings.xml +++ b/libs/WindowManager/Shell/res/values-uk/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Ліве вікно на 50%"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Ліве вікно на 30%"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Праве вікно на весь екран"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Поміняти місцями додатки зверху й знизу"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Поміняти місцями додатки ліворуч і праворуч"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Верхнє вікно на весь екран"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Верхнє вікно на 70%"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Верхнє вікно на 50%"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Перезапустити"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Більше не показувати"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Двічі торкніться, щоб\nперемістити цей додаток"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"Розгорнути додаток <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="restore_button_text" msgid="5377571986086775288">"Відновити додаток <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"Згорнути додаток <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="close_button_text" msgid="4544839489310949894">"Закрити додаток <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="back_button_text" msgid="1469718707134137085">"Назад"</string> <string name="handle_text" msgid="4419667835599523257">"Дескриптор додатка"</string> <string name="app_icon_text" msgid="2823268023931811747">"Значок додатка"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Змінити розмір вікна ліворуч"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Змінити розмір вікна праворуч"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Розгорнути вікно або відновити його розмір"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Розгорнути вікно додатка"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Відновити розмір вікна"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Згорнути вікно додатка"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Закрити вікно додатка"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"Налаштування \"Відкривати за умовчанням\""</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Виберіть, як відкривати вебпосилання в цьому додатку"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"У додатку"</string> diff --git a/libs/WindowManager/Shell/res/values-ur/strings.xml b/libs/WindowManager/Shell/res/values-ur/strings.xml index 1afb48d7952c..ded104cfd718 100644 --- a/libs/WindowManager/Shell/res/values-ur/strings.xml +++ b/libs/WindowManager/Shell/res/values-ur/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"بائیں %50"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"بائیں %30"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"دائیں فل اسکرین"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"اوپری ایپ کو نیچے کے ساتھ سویپ کریں"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"بائیں ایپ کو دائیں کے ساتھ سویپ کریں"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"بالائی فل اسکرین"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"اوپر %70"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"اوپر %50"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"ری اسٹارٹ کریں"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"دوبارہ نہ دکھائیں"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"اس ایپ کو منتقل کرنے کیلئے\nدو بار تھپتھپائیں"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"<xliff:g id="APP_NAME">%1$s</xliff:g> بڑا کریں"</string> + <string name="restore_button_text" msgid="5377571986086775288">"<xliff:g id="APP_NAME">%1$s</xliff:g> کو بحال کریں"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"<xliff:g id="APP_NAME">%1$s</xliff:g> چھوٹا کریں"</string> + <string name="close_button_text" msgid="4544839489310949894">"<xliff:g id="APP_NAME">%1$s</xliff:g> بند کریں"</string> <string name="back_button_text" msgid="1469718707134137085">"پیچھے"</string> <string name="handle_text" msgid="4419667835599523257">"ایپ ہینڈل"</string> <string name="app_icon_text" msgid="2823268023931811747">"ایپ کا آئیکن"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"دائیں طرف ونڈو کا سائز تبدیل کریں"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"ونڈو کا سائز بائیں طرف تبدیل کریں"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"ونڈو کا سائز زیادہ سے زیادہ یا بحال کریں"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"ایپ ونڈو سائز بڑا کریں"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"ونڈو سائز بحال کریں"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"ایپ ونڈو چھوٹا کریں"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"ایپ ونڈو بند کریں"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"بطور ڈیفالٹ ترتیبات کھولیں"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"اس ایپ کے لیے ویب لنکس کھولنے کا طریقہ منتخب کریں"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"ایپ میں"</string> diff --git a/libs/WindowManager/Shell/res/values-uz/strings.xml b/libs/WindowManager/Shell/res/values-uz/strings.xml index 04fd4290a89f..8a9dad029276 100644 --- a/libs/WindowManager/Shell/res/values-uz/strings.xml +++ b/libs/WindowManager/Shell/res/values-uz/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Chapda 50%"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Chapda 30%"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"O‘ngda to‘liq ekran"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Tepadagi ilovani pastkisi bilan almashtirish"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Chap ilovani oʻngdagisi bilan almashtirish"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Tepada to‘liq ekran"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Tepada 70%"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Tepada 50%"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Qaytadan"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Boshqa chiqmasin"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Bu ilovani siljitish uchun\nikki marta bosing"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"Yoyish: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="restore_button_text" msgid="5377571986086775288">"Tiklash: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"Kichraytirish: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="close_button_text" msgid="4544839489310949894">"Yopish: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="back_button_text" msgid="1469718707134137085">"Orqaga"</string> <string name="handle_text" msgid="4419667835599523257">"Ilova identifikatori"</string> <string name="app_icon_text" msgid="2823268023931811747">"Ilova belgisi"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Oyna oʻlchamini chapga oʻzgartirish"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Oyna oʻlchamini oʻngga oʻzgartirish"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Oyna oʻlchamini kengaytirish yoki asliga qaytarish"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Ilova oynasini kattartirish"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Oyna hajmini tiklash"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Ilova oynasini kichraytirish"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Ilova oynasini yopish"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"Birlamchi sozlamalar asosida ochish"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Bu ilovalardagi veb havolalar qanday ochilishini tanlang"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Ilovada"</string> diff --git a/libs/WindowManager/Shell/res/values-vi/strings.xml b/libs/WindowManager/Shell/res/values-vi/strings.xml index 169c2b787fa7..d6c8bc6173fb 100644 --- a/libs/WindowManager/Shell/res/values-vi/strings.xml +++ b/libs/WindowManager/Shell/res/values-vi/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Trái 50%"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Trái 30%"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Toàn màn hình bên phải"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Hoán đổi ứng dụng ở trên cùng với ứng dụng ở dưới cùng"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Hoán đổi ứng dụng bên trái với ứng dụng bên phải"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Toàn màn hình phía trên"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Trên 70%"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Trên 50%"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Khởi động lại"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Không hiện lại"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Nhấn đúp để\ndi chuyển ứng dụng này"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"Phóng to <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="restore_button_text" msgid="5377571986086775288">"Khôi phục <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"Thu nhỏ <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="close_button_text" msgid="4544839489310949894">"Đóng <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="back_button_text" msgid="1469718707134137085">"Quay lại"</string> <string name="handle_text" msgid="4419667835599523257">"Ô điều khiển ứng dụng"</string> <string name="app_icon_text" msgid="2823268023931811747">"Biểu tượng ứng dụng"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Đổi kích thước và chuyển cửa sổ sang trái"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Đổi kích thước và chuyển cửa sổ sang phải"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Phóng to hoặc khôi phục kích thước cửa sổ"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Phóng to kích thước cửa sổ ứng dụng"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Khôi phục kích thước cửa sổ"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Thu nhỏ cửa sổ ứng dụng"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Đóng cửa sổ ứng dụng"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"Mở các chế độ cài đặt theo mặc định"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Chọn cách mở đường liên kết trang web cho ứng dụng này"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Trong ứng dụng"</string> diff --git a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml index 942734ad0849..1009afc62f55 100644 --- a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml +++ b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"左侧 50%"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"左侧 30%"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"右侧全屏"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"将顶部应用与底部应用互换"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"将左侧应用与右侧应用互换"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"顶部全屏"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"顶部 70%"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"顶部 50%"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"重启"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"不再显示"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"点按两次\n即可移动此应用"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"将“<xliff:g id="APP_NAME">%1$s</xliff:g>”窗口最大化"</string> + <string name="restore_button_text" msgid="5377571986086775288">"恢复“<xliff:g id="APP_NAME">%1$s</xliff:g>”窗口大小"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"将“<xliff:g id="APP_NAME">%1$s</xliff:g>”窗口最小化"</string> + <string name="close_button_text" msgid="4544839489310949894">"关闭“<xliff:g id="APP_NAME">%1$s</xliff:g>”"</string> <string name="back_button_text" msgid="1469718707134137085">"返回"</string> <string name="handle_text" msgid="4419667835599523257">"应用手柄"</string> <string name="app_icon_text" msgid="2823268023931811747">"应用图标"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"调整窗口大小并贴靠左侧"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"调整窗口大小并贴靠右侧"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"将窗口最大化或恢复大小"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"将应用窗口最大化"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"恢复窗口大小"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"将应用窗口最小化"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"关闭应用窗口"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"默认打开设置"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"选择如何打开此应用中的网页链接"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"在此应用内"</string> diff --git a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml index f89792235c78..edd67c17fcf4 100644 --- a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml +++ b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"左邊 50%"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"左邊 30%"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"右邊全螢幕"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"調換上下應用程式"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"調換左右應用程式"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"頂部全螢幕"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"頂部 70%"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"頂部 50%"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"重新啟動"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"不要再顯示"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"輕按兩下\n即可移動此應用程式"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"將 <xliff:g id="APP_NAME">%1$s</xliff:g> 放到最大"</string> + <string name="restore_button_text" msgid="5377571986086775288">"還原 <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"將 <xliff:g id="APP_NAME">%1$s</xliff:g> 縮到最細"</string> + <string name="close_button_text" msgid="4544839489310949894">"閂 <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="back_button_text" msgid="1469718707134137085">"返去"</string> <string name="handle_text" msgid="4419667835599523257">"應用程式控點"</string> <string name="app_icon_text" msgid="2823268023931811747">"應用程式圖示"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"將視窗移去左邊調整大小"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"將視窗移去右邊調整大小"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"將視窗放到最大或者還原視窗大小"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"將應用程式視窗放到最大"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"還原視窗大細"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"將應用程式視窗縮到最細"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"關閉應用程式視窗"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"採用預設設定打開"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"選擇此應用程式開啟網絡連結的方式"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"在應用程式內"</string> diff --git a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml index 3c6abec3c08c..32d87f3f8ce6 100644 --- a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml +++ b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"以 50% 的螢幕空間顯示左側畫面"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"以 30% 的螢幕空間顯示左側畫面"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"以全螢幕顯示右側畫面"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"將頂端與底部的應用程式對調"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"將左側與右側的應用程式對調"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"以全螢幕顯示頂端畫面"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"以 70% 的螢幕空間顯示頂端畫面"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"以 50% 的螢幕空間顯示頂端畫面"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"重新啟動"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"不要再顯示"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"輕觸兩下即可\n移動這個應用程式"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"最大化「<xliff:g id="APP_NAME">%1$s</xliff:g>」視窗"</string> + <string name="restore_button_text" msgid="5377571986086775288">"還原「<xliff:g id="APP_NAME">%1$s</xliff:g>」視窗"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"最小化「<xliff:g id="APP_NAME">%1$s</xliff:g>」視窗"</string> + <string name="close_button_text" msgid="4544839489310949894">"關閉「<xliff:g id="APP_NAME">%1$s</xliff:g>」"</string> <string name="back_button_text" msgid="1469718707134137085">"返回"</string> <string name="handle_text" msgid="4419667835599523257">"應用程式控制代碼"</string> <string name="app_icon_text" msgid="2823268023931811747">"應用程式圖示"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"調整應用程式視窗大小並向左貼齊"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"調整應用程式視窗大小並向右貼齊"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"將視窗最大化或還原大小"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"最大化應用程式視窗"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"還原視窗大小"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"最小化應用程式視窗"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"關閉應用程式視窗"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"開啟連結的預設設定"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"選擇如何開啟這個應用程式的網頁連結"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"使用應用程式"</string> diff --git a/libs/WindowManager/Shell/res/values-zu/strings.xml b/libs/WindowManager/Shell/res/values-zu/strings.xml index b304299a6c18..94f0d3476c5e 100644 --- a/libs/WindowManager/Shell/res/values-zu/strings.xml +++ b/libs/WindowManager/Shell/res/values-zu/strings.xml @@ -43,10 +43,8 @@ <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Kwesokunxele ngo-50%"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"Kwesokunxele ngo-30%"</string> <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"Isikrini esigcwele esingakwesokudla"</string> - <!-- no translation found for accessibility_action_divider_swap_vertical (3644891227133372072) --> - <skip /> - <!-- no translation found for accessibility_action_divider_swap_horizontal (2722197605446631628) --> - <skip /> + <string name="accessibility_action_divider_swap_vertical" msgid="3644891227133372072">"Shintshanisa i-app ephezulu ngengaphansi"</string> + <string name="accessibility_action_divider_swap_horizontal" msgid="2722197605446631628">"Shintshanisa i-app engakwesokunxele naleyo engakwesokudla"</string> <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"Isikrini esigcwele esiphezulu"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"Okuphezulu okungu-70%"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Okuphezulu okungu-50%"</string> @@ -115,14 +113,10 @@ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Qala kabusha"</string> <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ungabonisi futhi"</string> <string name="letterbox_reachability_reposition_text" msgid="3522042240665748268">"Thepha kabili ukuze\nuhambise le-app"</string> - <!-- no translation found for maximize_button_text (8106849394538234709) --> - <skip /> - <!-- no translation found for restore_button_text (5377571986086775288) --> - <skip /> - <!-- no translation found for minimize_button_text (5213953162664451152) --> - <skip /> - <!-- no translation found for close_button_text (4544839489310949894) --> - <skip /> + <string name="maximize_button_text" msgid="8106849394538234709">"Khulisa i-<xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="restore_button_text" msgid="5377571986086775288">"Buyisela i-<xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="minimize_button_text" msgid="5213953162664451152">"Nciphisa i-<xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="close_button_text" msgid="4544839489310949894">"Vala i-<xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="back_button_text" msgid="1469718707134137085">"Emuva"</string> <string name="handle_text" msgid="4419667835599523257">"Inkomba ye-App"</string> <string name="app_icon_text" msgid="2823268023931811747">"Isithonjana Se-app"</string> @@ -158,14 +152,10 @@ <string name="maximize_menu_talkback_action_snap_left_text" msgid="500309467459084564">"Shintsha usayizi wewindi ngakwesokunxele"</string> <string name="maximize_menu_talkback_action_snap_right_text" msgid="7010831426654467163">"Shintsha usayizi wewindi ngakwesokudla"</string> <string name="maximize_menu_talkback_action_maximize_restore_text" msgid="4942610897847934859">"Khulisa noma buyisela usayizi wewindi"</string> - <!-- no translation found for app_header_talkback_action_maximize_button_text (8776156791095878638) --> - <skip /> - <!-- no translation found for app_header_talkback_action_restore_button_text (2153022340772980863) --> - <skip /> - <!-- no translation found for app_header_talkback_action_minimize_button_text (7491054416186901764) --> - <skip /> - <!-- no translation found for app_header_talkback_action_close_button_text (5159612596378268926) --> - <skip /> + <string name="app_header_talkback_action_maximize_button_text" msgid="8776156791095878638">"Khulisa usayizi wewindi le-app"</string> + <string name="app_header_talkback_action_restore_button_text" msgid="2153022340772980863">"Buyisela usayizi wewindi"</string> + <string name="app_header_talkback_action_minimize_button_text" msgid="7491054416186901764">"Nciphisa iwindi le-app"</string> + <string name="app_header_talkback_action_close_button_text" msgid="5159612596378268926">"Vala iwindi le-app"</string> <string name="open_by_default_settings_text" msgid="2526548548598185500">"Vula amasethingi ngokuzenzakalela"</string> <string name="open_by_default_dialog_subheader_text" msgid="1729599730664063881">"Khetha indlela yokuvula amalinki ewebhu ale app"</string> <string name="open_by_default_dialog_in_app_text" msgid="6978022419634199806">"Ku-app"</string> diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml index 2179128df300..5ef83826840b 100644 --- a/libs/WindowManager/Shell/res/values/strings.xml +++ b/libs/WindowManager/Shell/res/values/strings.xml @@ -226,7 +226,7 @@ <string name="windowing_app_handle_education_tooltip">The app menu can be found here</string> <!-- App handle education tooltip text for tooltip pointing to windowing image button --> - <string name="windowing_desktop_mode_image_button_education_tooltip">Enter desktop view to open multiple apps together</string> + <string name="windowing_desktop_mode_image_button_education_tooltip">Enter desktop windowing to open multiple apps together</string> <!-- App handle education tooltip text for tooltip pointing to app chip --> <string name="windowing_desktop_mode_exit_education_tooltip">Return to full screen anytime from the app menu</string> @@ -293,7 +293,7 @@ <!-- Accessibility text for the handle fullscreen button [CHAR LIMIT=NONE] --> <string name="fullscreen_text">Fullscreen</string> <!-- Accessibility text for the handle desktop button [CHAR LIMIT=NONE] --> - <string name="desktop_text">Desktop View</string> + <string name="desktop_text">Desktop windowing</string> <!-- Accessibility text for the handle split screen button [CHAR LIMIT=NONE] --> <string name="split_screen_text">Split Screen</string> <!-- Accessibility text for the handle more options button [CHAR LIMIT=NONE] --> @@ -319,7 +319,7 @@ <!-- Accessibility text for the handle menu close menu button [CHAR LIMIT=NONE] --> <string name="collapse_menu_text">Close Menu</string> <!-- Accessibility text for the App Header's App Chip [CHAR LIMIT=NONE] --> - <string name="desktop_mode_app_header_chip_text"><xliff:g id="app_name" example="Chrome">%1$s</xliff:g> (Desktop View)</string> + <string name="desktop_mode_app_header_chip_text"><xliff:g id="app_name" example="Chrome">%1$s</xliff:g> (Desktop windowing)</string> <!-- Maximize menu maximize button string. --> <string name="desktop_mode_maximize_menu_maximize_text">Maximize Screen</string> <!-- Maximize menu snap buttons string. --> @@ -348,7 +348,7 @@ <!-- Accessibility action replacement for caption handle app chip buttons [CHAR LIMIT=NONE] --> <string name="app_handle_chip_accessibility_announce">Open Menu</string> <!-- Accessibility action replacement for caption handle menu buttons [CHAR LIMIT=NONE] --> - <string name="app_handle_menu_accessibility_announce">Enter <xliff:g id="windowing_mode" example="Desktop View">%1$s</xliff:g></string> + <string name="app_handle_menu_accessibility_announce">Enter <xliff:g id="windowing_mode" example="Desktop windowing">%1$s</xliff:g></string> <!-- Accessibility action replacement for maximize menu enter snap left button [CHAR LIMIT=NONE] --> <string name="maximize_menu_talkback_action_snap_left_text">Resize window to left</string> <!-- Accessibility action replacement for maximize menu enter snap right button [CHAR LIMIT=NONE] --> diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/ShellSharedConstants.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/ShellSharedConstants.java index 01d2201a5a0c..8bcbd2a3fc9f 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/ShellSharedConstants.java +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/ShellSharedConstants.java @@ -22,4 +22,11 @@ package com.android.wm.shell.shared; public class ShellSharedConstants { public static final String KEY_EXTRA_SHELL_CAN_HAND_OFF_ANIMATION = "extra_shell_can_hand_off_animation"; + + /** + * Defines the max screen width or height in dp for a device to be considered a small tablet. + * + * @see android.view.WindowManager#LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP + */ + public static final int SMALL_TABLET_MAX_EDGE_DP = 960; } diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TypefaceUtils.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TypefaceUtils.kt new file mode 100644 index 000000000000..9bf56b075112 --- /dev/null +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TypefaceUtils.kt @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.shared + +import android.graphics.Typeface +import android.widget.TextView +import com.android.wm.shell.Flags + +/** + * Utility class to apply a specified typeface to a [TextView]. + * + * This class provides a method, [setTypeface], + * to easily set a pre-defined font family and style to a given [TextView]. + */ +class TypefaceUtils { + + enum class FontFamily(val value: String) { + GSF_DISPLAY_LARGE("variable-display-large"), + GSF_DISPLAY_MEDIUM("variable-display-medium"), + GSF_DISPLAY_SMALL("variable-display-small"), + GSF_HEADLINE_LARGE("variable-headline-large"), + GSF_HEADLINE_MEDIUM("variable-headline-medium"), + GSF_HEADLINE_SMALL("variable-headline-small"), + GSF_TITLE_LARGE("variable-title-large"), + GSF_TITLE_MEDIUM("variable-title-medium"), + GSF_TITLE_SMALL("variable-title-small"), + GSF_LABEL_LARGE("variable-label-large"), + GSF_LABEL_MEDIUM("variable-label-medium"), + GSF_LABEL_SMALL("variable-label-small"), + GSF_BODY_LARGE("variable-body-large"), + GSF_BODY_MEDIUM("variable-body-medium"), + GSF_BODY_SMALL("variable-body-small"), + GSF_DISPLAY_LARGE_EMPHASIZED("variable-display-large-emphasized"), + GSF_DISPLAY_MEDIUM_EMPHASIZED("variable-display-medium-emphasized"), + GSF_DISPLAY_SMALL_EMPHASIZED("variable-display-small-emphasized"), + GSF_HEADLINE_LARGE_EMPHASIZED("variable-headline-large-emphasized"), + GSF_HEADLINE_MEDIUM_EMPHASIZED("variable-headline-medium-emphasized"), + GSF_HEADLINE_SMALL_EMPHASIZED("variable-headline-small-emphasized"), + GSF_TITLE_LARGE_EMPHASIZED("variable-title-large-emphasized"), + GSF_TITLE_MEDIUM_EMPHASIZED("variable-title-medium-emphasized"), + GSF_TITLE_SMALL_EMPHASIZED("variable-title-small-emphasized"), + GSF_LABEL_LARGE_EMPHASIZED("variable-label-large-emphasized"), + GSF_LABEL_MEDIUM_EMPHASIZED("variable-label-medium-emphasized"), + GSF_LABEL_SMALL_EMPHASIZED("variable-label-small-emphasized"), + GSF_BODY_LARGE_EMPHASIZED("variable-body-large-emphasized"), + GSF_BODY_MEDIUM_EMPHASIZED("variable-body-medium-emphasized"), + GSF_BODY_SMALL_EMPHASIZED("variable-body-small-emphasized"), + } + + companion object { + /** + * Sets the typeface of the provided [textView] to the specified [fontFamily] and [fontStyle]. + * + * The typeface is only applied to the [TextView] when [Flags.enableGsf] is `true`. + * If [Flags.enableGsf] is `false`, this method has no effect. + * + * @param textView The [TextView] to which the typeface should be applied. If `null`, this method does nothing. + * @param fontFamily The desired [FontFamily] for the [TextView]. + * @param fontStyle The desired font style (e.g., [Typeface.NORMAL], [Typeface.BOLD], [Typeface.ITALIC]). Defaults to [Typeface.NORMAL]. + */ + @JvmStatic + @JvmOverloads + fun setTypeface( + textView: TextView?, + fontFamily: FontFamily, + fontStyle: Int = Typeface.NORMAL, + ) { + if (!Flags.enableGsf()) return + textView?.typeface = Typeface.create(fontFamily.name, fontStyle) + } + } +} diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DeviceConfig.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DeviceConfig.kt index 1b7c9c282304..ad2671b8135d 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DeviceConfig.kt +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DeviceConfig.kt @@ -26,6 +26,8 @@ import android.view.WindowInsets import android.view.WindowManager import kotlin.math.max +import com.android.wm.shell.shared.ShellSharedConstants.SMALL_TABLET_MAX_EDGE_DP + /** Contains device configuration used for positioning bubbles on the screen. */ data class DeviceConfig( val isLargeScreen: Boolean, @@ -38,7 +40,6 @@ data class DeviceConfig( companion object { private const val LARGE_SCREEN_MIN_EDGE_DP = 600 - private const val SMALL_TABLET_MAX_EDGE_DP = 960 @JvmStatic fun create(context: Context, windowManager: WindowManager): DeviceConfig { diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DragZoneFactory.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DragZoneFactory.kt index 362a5c5c3750..afeaf70c9d62 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DragZoneFactory.kt +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DragZoneFactory.kt @@ -303,6 +303,7 @@ class DragZoneFactory( val isVerticalSplit = deviceConfig.isSmallTablet == deviceConfig.isLandscape return if (isVerticalSplit) { when (splitScreenModeChecker.getSplitScreenMode()) { + SplitScreenMode.UNSUPPORTED -> emptyList() SplitScreenMode.SPLIT_50_50, SplitScreenMode.NONE -> listOf( @@ -360,6 +361,7 @@ class DragZoneFactory( } } else { when (splitScreenModeChecker.getSplitScreenMode()) { + SplitScreenMode.UNSUPPORTED -> emptyList() SplitScreenMode.SPLIT_50_50, SplitScreenMode.NONE -> listOf( @@ -453,6 +455,7 @@ class DragZoneFactory( // vertical split drag zones are aligned with the full screen drag zone width val splitZoneLeft = windowBounds.right / 2 - fullScreenDragZoneWidth / 2 when (splitScreenModeChecker.getSplitScreenMode()) { + SplitScreenMode.UNSUPPORTED -> emptyList() SplitScreenMode.SPLIT_50_50, SplitScreenMode.NONE -> listOf( @@ -560,7 +563,8 @@ class DragZoneFactory( NONE, SPLIT_50_50, SPLIT_10_90, - SPLIT_90_10 + SPLIT_90_10, + UNSUPPORTED } fun getSplitScreenMode(): SplitScreenMode diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetView.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetView.kt index 2bb6cf4ec3aa..73277310ffe4 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetView.kt +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetView.kt @@ -20,6 +20,7 @@ import android.content.Context import android.graphics.Canvas import android.graphics.Paint import android.graphics.RectF +import android.util.TypedValue import android.view.View import com.android.wm.shell.shared.R @@ -37,14 +38,21 @@ class DropTargetView(context: Context) : View(context) { private val strokePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { color = context.getColor(com.android.internal.R.color.materialColorPrimaryContainer) style = Paint.Style.STROKE - strokeWidth = context.resources.getDimensionPixelSize(R.dimen.drop_target_stroke).toFloat() + strokeWidth = 1.dpToPx() } - private val cornerRadius = context.resources.getDimensionPixelSize( - R.dimen.drop_target_radius).toFloat() + private val cornerRadius = 28.dpToPx() private val rect = RectF(0f, 0f, 0f, 0f) + // TODO b/396539130: Use shared xml resources once we can access them in launcher + private fun Int.dpToPx() = + TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, + this.toFloat(), + context.resources.displayMetrics + ) + override fun onDraw(canvas: Canvas) { canvas.save() canvas.drawRoundRect(rect, cornerRadius, cornerRadius, rectPaint) diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java index 2e33253b5e09..ed5e0c608675 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java @@ -17,7 +17,9 @@ package com.android.wm.shell.shared.desktopmode; import static android.hardware.display.DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED; +import static android.window.DesktopExperienceFlags.ENABLE_PROJECTED_DISPLAY_DESKTOP_MODE; +import static com.android.server.display.feature.flags.Flags.enableDisplayContentModeManagement; import static com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper.enableBubbleToFullscreen; import android.annotation.NonNull; @@ -224,7 +226,7 @@ public class DesktopModeStatus { /** * Return {@code true} if the current device can host desktop sessions on its internal display. */ - public static boolean canInternalDisplayHostDesktops(@NonNull Context context) { + private static boolean canInternalDisplayHostDesktops(@NonNull Context context) { return context.getResources().getBoolean(R.bool.config_canInternalDisplayHostDesktops); } @@ -269,6 +271,29 @@ public class DesktopModeStatus { } /** + * Check to see if a display should have desktop mode enabled or not. Internal + * and external displays have separate logic. + */ + public static boolean isDesktopModeSupportedOnDisplay(Context context, Display display) { + if (!canEnterDesktopMode(context)) { + return false; + } + if (display.getType() == Display.TYPE_INTERNAL) { + return canInternalDisplayHostDesktops(context); + } + + // TODO (b/395014779): Change this to use WM API + if ((display.getType() == Display.TYPE_EXTERNAL + || display.getType() == Display.TYPE_OVERLAY) + && enableDisplayContentModeManagement()) { + final WindowManager wm = context.getSystemService(WindowManager.class); + return wm != null && wm.shouldShowSystemDecors(display.getDisplayId()); + } + + return false; + } + + /** * Returns whether the multiple desktops feature is enabled for this device (both backend and * frontend implementations). */ @@ -341,8 +366,11 @@ public class DesktopModeStatus { if (!enforceDeviceRestrictions()) { return true; } - final boolean desktopModeSupported = isDesktopModeSupported(context) - && canInternalDisplayHostDesktops(context); + // If projected display is enabled, #canInternalDisplayHostDesktops is no longer a + // requirement. + final boolean desktopModeSupported = ENABLE_PROJECTED_DISPLAY_DESKTOP_MODE.isTrue() + ? isDesktopModeSupported(context) : (isDesktopModeSupported(context) + && canInternalDisplayHostDesktops(context)); final boolean desktopModeSupportedByDevOptions = Flags.enableDesktopModeThroughDevOption() && isDesktopModeDevOptionSupported(context); diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/multiinstance/ManageWindowsViewContainer.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/multiinstance/ManageWindowsViewContainer.kt index 0954b52ff151..ac54ac7a9d99 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/multiinstance/ManageWindowsViewContainer.kt +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/multiinstance/ManageWindowsViewContainer.kt @@ -48,12 +48,14 @@ abstract class ManageWindowsViewContainer( lateinit var menuView: ManageWindowsView /** Creates the base menu view and fills it with icon views. */ - fun createMenu(snapshotList: List<Pair<Int, TaskSnapshot>>, + fun createMenu(snapshotList: List<Pair<Int, TaskSnapshot?>>, onIconClickListener: ((Int) -> Unit), onOutsideClickListener: (() -> Unit)): ManageWindowsView { - val bitmapList = snapshotList.map { (index, snapshot) -> - index to Bitmap.wrapHardwareBuffer(snapshot.hardwareBuffer, snapshot.colorSpace) - } + val bitmapList = snapshotList + .filter { it.second != null } + .map { (index, snapshot) -> + index to Bitmap.wrapHardwareBuffer(snapshot!!.hardwareBuffer, snapshot.colorSpace) + } return createAndShowMenuView( bitmapList, onIconClickListener, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java index 80b65d025399..53dede6bd227 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java @@ -28,7 +28,7 @@ import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP; import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER; import static com.android.internal.jank.InteractionJankMonitor.CUJ_PREDICTIVE_BACK_HOME; -import static com.android.systemui.Flags.predictiveBackDelayTransition; +import static com.android.systemui.Flags.predictiveBackDelayWmTransition; import static com.android.window.flags.Flags.unifyBackNavigationTransition; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW; @@ -433,7 +433,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont public void onThresholdCrossed() { mThresholdCrossed = true; BackTouchTracker activeTracker = getActiveTracker(); - if (predictiveBackDelayTransition() && activeTracker != null && mActiveCallback == null + if (predictiveBackDelayWmTransition() && activeTracker != null && mActiveCallback == null && mBackGestureStarted) { startBackNavigation(activeTracker); } @@ -494,12 +494,12 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont if (swipeEdge == EDGE_NONE) { // start animation immediately for non-gestural sources (without ACTION_MOVE // events) - if (!predictiveBackDelayTransition()) { + if (!predictiveBackDelayWmTransition()) { mThresholdCrossed = true; } mPointersPilfered = true; onGestureStarted(touchX, touchY, swipeEdge); - if (predictiveBackDelayTransition()) { + if (predictiveBackDelayWmTransition()) { onThresholdCrossed(); } mShouldStartOnNextMoveEvent = false; @@ -555,7 +555,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont mPostCommitAnimationInProgress = false; mShellExecutor.removeCallbacks(mAnimationTimeoutRunnable); startSystemAnimation(); - } else if (!predictiveBackDelayTransition()) { + } else if (!predictiveBackDelayWmTransition()) { startBackNavigation(touchTracker); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java index d9489287ff42..4f30a052de80 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java @@ -375,7 +375,7 @@ public class Bubble implements BubbleViewProvider { user, icon, BubbleType.TYPE_APP, - getAppBubbleKeyForApp(intent.getPackage(), user), + getAppBubbleKeyForApp(ComponentUtils.getPackageName(intent), user), mainExecutor, bgExecutor); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index 50e2f4d52bf2..3e95a0b1100f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -1604,7 +1604,7 @@ public class BubbleController implements ConfigurationChangeListener, @Nullable BubbleTransitions.DragData dragData) { if (!BubbleAnythingFlagHelper.enableBubbleToFullscreen()) return; Bubble b = mBubbleData.getOrCreateBubble(taskInfo); // Removes from overflow - ProtoLog.v(WM_SHELL_BUBBLES, "expandStackAndSelectBubble - intent=%s", taskInfo.taskId); + ProtoLog.v(WM_SHELL_BUBBLES, "expandStackAndSelectBubble - taskId=%s", taskInfo.taskId); BubbleBarLocation location = null; if (dragData != null) { location = diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java index 2c2451cab999..8ac9230c36c3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java @@ -238,7 +238,6 @@ public class BubbleExpandedView extends LinearLayout { mContext.createContextAsUser( mBubble.getUser(), Context.CONTEXT_RESTRICTED); Intent fillInIntent = new Intent(); - fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); PendingIntent pi = PendingIntent.getActivity( context, /* requestCode= */ 0, @@ -467,6 +466,11 @@ public class BubbleExpandedView extends LinearLayout { new BubbleTaskViewListener.Callback() { @Override public void onTaskCreated() { + // The taskId is saved to use for removeTask, + // preventing appearance in recent tasks. + mTaskId = ((BubbleTaskViewListener) mCurrentTaskViewListener) + .getTaskId(); + setContentVisibility(true); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewListener.java index 63d713495177..9c20e3af9ab4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewListener.java @@ -130,7 +130,6 @@ public class BubbleTaskViewListener implements TaskView.Listener { mContext.createContextAsUser( mBubble.getUser(), Context.CONTEXT_RESTRICTED); Intent fillInIntent = new Intent(); - fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); // First try get pending intent from the bubble PendingIntent pi = mBubble.getPendingIntent(); if (pi == null) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java index e7e7be9cdf6b..51a5b12edb84 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java @@ -159,19 +159,22 @@ public class BubbleTransitions { private final WindowContainerTransaction mPendingWct; private final boolean mReleasedOnLeft; private final float mTaskScale; + private final float mCornerRadius; private final PointF mDragPosition; /** * @param releasedOnLeft true if the bubble was released in the left drop target * @param taskScale the scale of the task when it was dragged to bubble + * @param cornerRadius the corner radius of the task when it was dragged to bubble * @param dragPosition the position of the task when it was dragged to bubble * @param wct pending operations to be applied when finishing the drag */ - public DragData(boolean releasedOnLeft, float taskScale, @Nullable PointF dragPosition, - @Nullable WindowContainerTransaction wct) { + public DragData(boolean releasedOnLeft, float taskScale, float cornerRadius, + @Nullable PointF dragPosition, @Nullable WindowContainerTransaction wct) { mPendingWct = wct; mReleasedOnLeft = releasedOnLeft; mTaskScale = taskScale; + mCornerRadius = cornerRadius; mDragPosition = dragPosition != null ? dragPosition : new PointF(0, 0); } @@ -198,6 +201,13 @@ public class BubbleTransitions { } /** + * @return the corner radius of the task when it was dragged to bubble + */ + public float getCornerRadius() { + return mCornerRadius; + } + + /** * @return position of the task when it was dragged to bubble */ public PointF getDragPosition() { @@ -362,6 +372,7 @@ public class BubbleTransitions { (int) mDragData.getDragPosition().y); startTransaction.setScale(mSnapshot, mDragData.getTaskScale(), mDragData.getTaskScale()); + startTransaction.setCornerRadius(mSnapshot, mDragData.getCornerRadius()); } // Now update state (and talk to launcher) in parallel with snapshot stuff @@ -377,12 +388,6 @@ public class BubbleTransitions { startTransaction.setPosition(mSnapshot, left, top); startTransaction.setLayer(mSnapshot, Integer.MAX_VALUE); - BubbleBarExpandedView bbev = mBubble.getBubbleBarExpandedView(); - if (bbev != null) { - // Corners get reset during the animation. Add them back - startTransaction.setCornerRadius(mSnapshot, bbev.getRestingCornerRadius()); - } - startTransaction.apply(); mTaskViewTransitions.onExternalDone(transition); @@ -634,7 +639,7 @@ public class BubbleTransitions { @Override public void continueCollapse() { mBubble.cleanupTaskView(); - if (mTaskLeash == null) return; + if (mTaskLeash == null || !mTaskLeash.isValid()) return; SurfaceControl.Transaction t = new SurfaceControl.Transaction(); t.reparent(mTaskLeash, mRootLeash); t.apply(); 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 6c840f020f90..bdb21f246359 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java @@ -447,9 +447,9 @@ public class BubbleBarLayerView extends FrameLayout bubble.cleanupViews(!inTransition); endAction.run(); }; - if (mBubbleData.getBubbles().isEmpty()) { - // we're removing the last bubble. collapse the expanded view and cleanup bubble views - // at the end. + if (mBubbleData.getBubbles().isEmpty() || inTransition) { + // If we are removing the last bubble or removing the current bubble via transition, + // collapse the expanded view and clean up bubbles at the end. collapse(cleanUp); } else { cleanUp.run(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ComponentUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ComponentUtils.kt index 0d0bc9be72b3..9fefb515a539 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ComponentUtils.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ComponentUtils.kt @@ -25,7 +25,8 @@ import com.android.wm.shell.ShellTaskOrganizer object ComponentUtils { /** Retrieves the package name from an [Intent]. */ @JvmStatic - fun getPackageName(intent: Intent?): String? = intent?.component?.packageName + fun getPackageName(intent: Intent?): String? = + intent?.component?.packageName ?: intent?.`package` /** Retrieves the package name from a [PendingIntent]. */ @JvmStatic diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/HomeIntentProvider.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/HomeIntentProvider.kt new file mode 100644 index 000000000000..8751b65dce94 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/HomeIntentProvider.kt @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.common + +import android.app.ActivityManager +import android.app.ActivityOptions +import android.app.PendingIntent +import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN +import android.content.Context +import android.content.Intent +import android.os.UserHandle +import android.view.Display.DEFAULT_DISPLAY +import android.window.WindowContainerTransaction +import com.android.window.flags.Flags + +/** Creates home intent **/ +class HomeIntentProvider( + private val context: Context, +) { + fun addLaunchHomePendingIntent( + wct: WindowContainerTransaction, displayId: Int, userId: Int? = null + ) { + val userHandle = + if (userId != null) UserHandle.of(userId) else UserHandle.of(ActivityManager.getCurrentUser()) + + val launchHomeIntent = Intent(Intent.ACTION_MAIN).apply { + if (displayId != DEFAULT_DISPLAY) { + addCategory(Intent.CATEGORY_SECONDARY_HOME) + } else { + addCategory(Intent.CATEGORY_HOME) + } + } + val options = ActivityOptions.makeBasic().apply { + launchWindowingMode = WINDOWING_MODE_FULLSCREEN + pendingIntentBackgroundActivityStartMode = + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS + if (Flags.enablePerDisplayDesktopWallpaperActivity()) { + launchDisplayId = displayId + } + } + val pendingIntent = PendingIntent.getActivityAsUser( + context, + /* requestCode= */ 0, + launchHomeIntent, + PendingIntent.FLAG_IMMUTABLE, + /* options= */ null, + userHandle, + ) + wct.sendPendingIntent(pendingIntent, launchHomeIntent, options.toBundle()) + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java index 214e6ad455a1..aeef211ae3f3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java @@ -143,6 +143,9 @@ public class PipBoundsState { */ public final Rect mCachedLauncherShelfHeightKeepClearArea = new Rect(); + private final List<OnPipComponentChangedListener> mOnPipComponentChangedListeners = + new ArrayList<>(); + // the size of the current bounds relative to the max size spec private float mBoundsScale; @@ -156,9 +159,7 @@ public class PipBoundsState { // Update the relative proportion of the bounds compared to max possible size. Max size // spec takes the aspect ratio of the bounds into account, so both width and height // scale by the same factor. - addPipExclusionBoundsChangeCallback((bounds) -> { - updateBoundsScale(); - }); + addPipExclusionBoundsChangeCallback((bounds) -> updateBoundsScale()); } /** Reloads the resources. */ @@ -341,11 +342,14 @@ public class PipBoundsState { /** Set the last {@link ComponentName} to enter PIP mode. */ public void setLastPipComponentName(@Nullable ComponentName lastPipComponentName) { final boolean changed = !Objects.equals(mLastPipComponentName, lastPipComponentName); + if (!changed) return; + clearReentryState(); + setHasUserResizedPip(false); + setHasUserMovedPip(false); + final ComponentName oldComponentName = mLastPipComponentName; mLastPipComponentName = lastPipComponentName; - if (changed) { - clearReentryState(); - setHasUserResizedPip(false); - setHasUserMovedPip(false); + for (OnPipComponentChangedListener listener : mOnPipComponentChangedListeners) { + listener.onPipComponentChanged(oldComponentName, mLastPipComponentName); } } @@ -616,6 +620,21 @@ public class PipBoundsState { } } + /** Adds callback to listen on component change. */ + public void addOnPipComponentChangedListener(@NonNull OnPipComponentChangedListener listener) { + if (!mOnPipComponentChangedListeners.contains(listener)) { + mOnPipComponentChangedListeners.add(listener); + } + } + + /** Removes callback to listen on component change. */ + public void removeOnPipComponentChangedListener( + @NonNull OnPipComponentChangedListener listener) { + if (mOnPipComponentChangedListeners.contains(listener)) { + mOnPipComponentChangedListeners.remove(listener); + } + } + public LauncherState getLauncherState() { return mLauncherState; } @@ -695,7 +714,7 @@ public class PipBoundsState { * Represents the state of pip to potentially restore upon reentry. */ @VisibleForTesting - public static final class PipReentryState { + static final class PipReentryState { private static final String TAG = PipReentryState.class.getSimpleName(); private final float mSnapFraction; @@ -722,6 +741,22 @@ public class PipBoundsState { } } + /** + * Listener interface for PiP component change, i.e. the app in pip mode changes + * TODO: Move this out of PipBoundsState once pip1 is deprecated. + */ + public interface OnPipComponentChangedListener { + /** + * Callback when the component in pip mode changes. + * @param oldPipComponent previous component in pip mode, + * {@code null} if this is the very first time PiP appears. + * @param newPipComponent new component that enters pip mode. + */ + void onPipComponentChanged( + @Nullable ComponentName oldPipComponent, + @NonNull ComponentName newPipComponent); + } + /** Dumps internal state. */ public void dump(PrintWriter pw, String prefix) { final String innerPrefix = prefix + " "; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java index e20a3d839def..fec1f56c76bb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java @@ -1466,11 +1466,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange // Freeze the configuration size with offset to prevent app get a configuration // changed or relaunch. This is required to make sure client apps will calculate // insets properly after layout shifted. - if (mTargetYOffset == 0) { - mSplitLayoutHandler.setLayoutOffsetTarget(0, 0, SplitLayout.this); - } else { - mSplitLayoutHandler.setLayoutOffsetTarget(0, mTargetYOffset, SplitLayout.this); - } + mSplitLayoutHandler.setLayoutOffsetTarget(0, mTargetYOffset, SplitLayout.this); } // Make {@link DividerView} non-interactive while IME showing in split mode. Listen to diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java index 9b11e4ab16fa..4413c8715c0d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java @@ -18,6 +18,8 @@ package com.android.wm.shell.compatui; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static com.android.wm.shell.compatui.impl.CompatUIRequestsKt.DISPLAY_COMPAT_SHOW_RESTART_DIALOG; + import android.annotation.NonNull; import android.annotation.Nullable; import android.app.TaskInfo; @@ -53,7 +55,9 @@ import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.compatui.api.CompatUIEvent; import com.android.wm.shell.compatui.api.CompatUIHandler; import com.android.wm.shell.compatui.api.CompatUIInfo; +import com.android.wm.shell.compatui.api.CompatUIRequest; import com.android.wm.shell.compatui.impl.CompatUIEvents.SizeCompatRestartButtonClicked; +import com.android.wm.shell.compatui.impl.CompatUIRequests; import com.android.wm.shell.desktopmode.DesktopUserRepositories; import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; import com.android.wm.shell.sysui.KeyguardChangeListener; @@ -245,6 +249,21 @@ public class CompatUIController implements OnDisplaysChangedListener, mCallback = callback; } + @Override + public void sendCompatUIRequest(CompatUIRequest compatUIRequest) { + switch(compatUIRequest.getRequestId()) { + case DISPLAY_COMPAT_SHOW_RESTART_DIALOG: + handleDisplayCompatShowRestartDialog(compatUIRequest.asType()); + break; + default: + } + } + + private void handleDisplayCompatShowRestartDialog( + CompatUIRequests.DisplayCompatShowRestartDialog request) { + onRestartButtonClicked(new Pair<>(request.getTaskInfo(), request.getTaskListener())); + } + /** * Called when the Task info changed. Creates and updates the compat UI if there is an * activity in size compat, or removes the UI if there is no size compat activity. @@ -254,13 +273,17 @@ public class CompatUIController implements OnDisplaysChangedListener, public void onCompatInfoChanged(@NonNull CompatUIInfo compatUIInfo) { final TaskInfo taskInfo = compatUIInfo.getTaskInfo(); final ShellTaskOrganizer.TaskListener taskListener = compatUIInfo.getListener(); - if (taskInfo != null && !taskInfo.appCompatTaskInfo.isTopActivityInSizeCompat()) { + final boolean isInDisplayCompatMode = + taskInfo.appCompatTaskInfo.isRestartMenuEnabledForDisplayMove(); + if (taskInfo != null && !taskInfo.appCompatTaskInfo.isTopActivityInSizeCompat() + && !isInDisplayCompatMode) { mSetOfTaskIdsShowingRestartDialog.remove(taskInfo.taskId); } mIsInDesktopMode = isInDesktopMode(taskInfo); // We close all the Compat UI educations in case TaskInfo has no configuration or // TaskListener or in desktop mode. - if (taskInfo.configuration == null || taskListener == null || mIsInDesktopMode) { + if (taskInfo.configuration == null || taskListener == null + || (mIsInDesktopMode && !isInDisplayCompatMode)) { // Null token means the current foreground activity is not in compatibility mode. removeLayouts(taskInfo.taskId); return; @@ -552,8 +575,11 @@ public class CompatUIController implements OnDisplaysChangedListener, @Nullable ShellTaskOrganizer.TaskListener taskListener) { RestartDialogWindowManager layout = mTaskIdToRestartDialogWindowManagerMap.get(taskInfo.taskId); + final boolean isInNonDisplayCompatDesktopMode = mIsInDesktopMode + && !taskInfo.appCompatTaskInfo.isRestartMenuEnabledForDisplayMove(); if (layout != null) { - if (layout.needsToBeRecreated(taskInfo, taskListener) || mIsInDesktopMode) { + if (layout.needsToBeRecreated(taskInfo, taskListener) + || isInNonDisplayCompatDesktopMode) { mTaskIdToRestartDialogWindowManagerMap.remove(taskInfo.taskId); layout.release(); } else { @@ -568,8 +594,9 @@ public class CompatUIController implements OnDisplaysChangedListener, return; } } - if (mIsInDesktopMode) { - // Return if in desktop mode. + if (isInNonDisplayCompatDesktopMode) { + // No restart dialog can be shown in desktop mode unless the task is in display compat + // mode. return; } // Create a new UI layout. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIHandler.kt index 817e554b550e..f71f8099f29f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIHandler.kt @@ -28,6 +28,11 @@ interface CompatUIHandler { fun onCompatInfoChanged(compatUIInfo: CompatUIInfo) /** + * Invoked when another component in Shell requests a CompatUI state change. + */ + fun sendCompatUIRequest(compatUIRequest: CompatUIRequest) + + /** * Optional reference to the object responsible to send {@link CompatUIEvent} */ fun setCallback(compatUIEventSender: Consumer<CompatUIEvent>?) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIRequest.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIRequest.kt new file mode 100644 index 000000000000..069fd9b062a6 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIRequest.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.compatui.api + +/** + * Abstraction for all the possible Compat UI Component requests. + */ +interface CompatUIRequest { + /** + * Unique request identifier + */ + val requestId: Int + + @Suppress("UNCHECKED_CAST") + fun <T : CompatUIRequest> asType(): T? = this as? T + + fun <T : CompatUIRequest> asType(clazz: Class<T>): T? { + return if (clazz.isInstance(this)) clazz.cast(this) else null + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/CompatUIRequests.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/CompatUIRequests.kt new file mode 100644 index 000000000000..da4fc99491dc --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/CompatUIRequests.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.compatui.impl + +import android.app.TaskInfo +import com.android.wm.shell.ShellTaskOrganizer +import com.android.wm.shell.compatui.api.CompatUIRequest + +internal const val DISPLAY_COMPAT_SHOW_RESTART_DIALOG = 0 + +/** + * All the {@link CompatUIRequest} the Compat UI Framework can handle + */ +sealed class CompatUIRequests(override val requestId: Int) : CompatUIRequest { + /** Sent when the restart handle menu is clicked, and a restart dialog is requested. */ + data class DisplayCompatShowRestartDialog(val taskInfo: TaskInfo, + val taskListener: ShellTaskOrganizer.TaskListener) : + CompatUIRequests(DISPLAY_COMPAT_SHOW_RESTART_DIALOG) +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultCompatUIHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultCompatUIHandler.kt index 02db85a4f99d..7dcb16c10097 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultCompatUIHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultCompatUIHandler.kt @@ -23,6 +23,7 @@ import com.android.wm.shell.compatui.api.CompatUIEvent import com.android.wm.shell.compatui.api.CompatUIHandler import com.android.wm.shell.compatui.api.CompatUIInfo import com.android.wm.shell.compatui.api.CompatUIRepository +import com.android.wm.shell.compatui.api.CompatUIRequest import com.android.wm.shell.compatui.api.CompatUIState import java.util.function.Consumer @@ -102,4 +103,6 @@ class DefaultCompatUIHandler( override fun setCallback(compatUIEventSender: Consumer<CompatUIEvent>?) { this.compatUIEventSender = compatUIEventSender } + + override fun sendCompatUIRequest(compatUIRequest: CompatUIRequest) {} } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/crashhandling/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/crashhandling/OWNERS new file mode 100644 index 000000000000..007528e2e054 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/crashhandling/OWNERS @@ -0,0 +1,2 @@ +# WM shell sub-module crash handling owners +uysalorhan@google.com
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/crashhandling/ShellCrashHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/crashhandling/ShellCrashHandler.kt new file mode 100644 index 000000000000..2e34d043cbe1 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/crashhandling/ShellCrashHandler.kt @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.crashhandling + +import android.app.WindowConfiguration +import android.content.Context +import android.view.Display.DEFAULT_DISPLAY +import android.window.DesktopExperienceFlags +import android.window.WindowContainerTransaction +import com.android.wm.shell.ShellTaskOrganizer +import com.android.wm.shell.common.HomeIntentProvider +import com.android.wm.shell.shared.desktopmode.DesktopModeStatus +import com.android.wm.shell.sysui.ShellInit + +/** [ShellCrashHandler] for shell to use when it's being initialized. Currently it only restores + * the home task to top. + **/ +class ShellCrashHandler( + private val context: Context, + private val shellTaskOrganizer: ShellTaskOrganizer, + private val homeIntentProvider: HomeIntentProvider, + shellInit: ShellInit, +) { + init { + shellInit.addInitCallback(::onInit, this) + } + + private fun onInit() { + handleCrashIfNeeded() + } + + private fun handleCrashIfNeeded() { + // For now only handle crashes when desktop mode is enabled on the device. + if (DesktopModeStatus.canEnterDesktopMode(context) && + !DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) { + var freeformTaskExists = false + // If there are running tasks at init, WMShell has crashed but WMCore is still alive. + for (task in shellTaskOrganizer.getRunningTasks()) { + if (task.windowingMode == WindowConfiguration.WINDOWING_MODE_FREEFORM) { + freeformTaskExists = true + } + + if (freeformTaskExists) { + shellTaskOrganizer.applyTransaction( + addLaunchHomePendingIntent(WindowContainerTransaction(), DEFAULT_DISPLAY) + ) + break + } + } + } + } + + private fun addLaunchHomePendingIntent( + wct: WindowContainerTransaction, displayId: Int + ): WindowContainerTransaction { + // TODO: b/400462917 - Check that crashes are also handled correctly on HSUM devices. We + // might need to pass the [userId] here to launch the correct home. + homeIntentProvider.addLaunchHomePendingIntent(wct, displayId) + return wct + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index 43a19ef034c9..bc2ed3f35b45 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 @@ -69,6 +69,7 @@ import com.android.wm.shell.common.DisplayImeController; import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.FloatingContentCoordinator; +import com.android.wm.shell.common.HomeIntentProvider; import com.android.wm.shell.common.LaunchAdjacentController; import com.android.wm.shell.common.MultiDisplayDragMoveIndicatorController; import com.android.wm.shell.common.MultiDisplayDragMoveIndicatorSurface; @@ -80,6 +81,7 @@ import com.android.wm.shell.common.UserProfileContexts; import com.android.wm.shell.common.split.SplitState; import com.android.wm.shell.compatui.letterbox.LetterboxCommandHandler; import com.android.wm.shell.compatui.letterbox.LetterboxTransitionObserver; +import com.android.wm.shell.crashhandling.ShellCrashHandler; import com.android.wm.shell.dagger.back.ShellBackAnimationModule; import com.android.wm.shell.dagger.pip.PipModule; import com.android.wm.shell.desktopmode.CloseDesktopTaskTransitionHandler; @@ -169,6 +171,7 @@ import com.android.wm.shell.windowdecor.CaptionWindowDecorViewModel; import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel; import com.android.wm.shell.windowdecor.WindowDecorViewModel; import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer; +import com.android.wm.shell.windowdecor.common.AppHandleAndHeaderVisibilityHelper; import com.android.wm.shell.windowdecor.common.WindowDecorTaskResourceLoader; import com.android.wm.shell.windowdecor.common.viewhost.DefaultWindowDecorViewHostSupplier; import com.android.wm.shell.windowdecor.common.viewhost.PooledWindowDecorViewHostSupplier; @@ -450,7 +453,8 @@ public abstract class WMShellModule { Optional<DesktopImmersiveController> desktopImmersiveController, WindowDecorViewModel windowDecorViewModel, Optional<TaskChangeListener> taskChangeListener, - FocusTransitionObserver focusTransitionObserver) { + FocusTransitionObserver focusTransitionObserver, + Optional<DesksTransitionObserver> desksTransitionObserver) { return new FreeformTaskTransitionObserver( context, shellInit, @@ -458,7 +462,8 @@ public abstract class WMShellModule { desktopImmersiveController, windowDecorViewModel, taskChangeListener, - focusTransitionObserver); + focusTransitionObserver, + desksTransitionObserver); } @WMSingleton @@ -724,9 +729,11 @@ public abstract class WMShellModule { static DesksOrganizer provideDesksOrganizer( @NonNull ShellInit shellInit, @NonNull ShellCommandHandler shellCommandHandler, - @NonNull ShellTaskOrganizer shellTaskOrganizer + @NonNull ShellTaskOrganizer shellTaskOrganizer, + @NonNull LaunchAdjacentController launchAdjacentController ) { - return new RootTaskDesksOrganizer(shellInit, shellCommandHandler, shellTaskOrganizer); + return new RootTaskDesksOrganizer(shellInit, shellCommandHandler, shellTaskOrganizer, + launchAdjacentController); } @WMSingleton @@ -752,6 +759,7 @@ public abstract class WMShellModule { ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler, DragToDesktopTransitionHandler dragToDesktopTransitionHandler, @DynamicOverride DesktopUserRepositories desktopUserRepositories, + DesktopRepositoryInitializer desktopRepositoryInitializer, Optional<DesktopImmersiveController> desktopImmersiveController, DesktopModeLoggerTransitionObserver desktopModeLoggerTransitionObserver, LaunchAdjacentController launchAdjacentController, @@ -771,11 +779,12 @@ public abstract class WMShellModule { Optional<BubbleController> bubbleController, OverviewToDesktopTransitionObserver overviewToDesktopTransitionObserver, DesksOrganizer desksOrganizer, - DesksTransitionObserver desksTransitionObserver, + Optional<DesksTransitionObserver> desksTransitionObserver, UserProfileContexts userProfileContexts, DesktopModeCompatPolicy desktopModeCompatPolicy, DragToDisplayTransitionHandler dragToDisplayTransitionHandler, - DesktopModeMoveToDisplayTransitionHandler moveToDisplayTransitionHandler) { + DesktopModeMoveToDisplayTransitionHandler moveToDisplayTransitionHandler, + HomeIntentProvider homeIntentProvider) { return new DesktopTasksController( context, shellInit, @@ -797,6 +806,7 @@ public abstract class WMShellModule { dragToDesktopTransitionHandler, desktopImmersiveController.get(), desktopUserRepositories, + desktopRepositoryInitializer, recentsTransitionHandler, multiInstanceHelper, mainExecutor, @@ -812,11 +822,12 @@ public abstract class WMShellModule { bubbleController, overviewToDesktopTransitionObserver, desksOrganizer, - desksTransitionObserver, + desksTransitionObserver.get(), userProfileContexts, desktopModeCompatPolicy, dragToDisplayTransitionHandler, - moveToDisplayTransitionHandler); + moveToDisplayTransitionHandler, + homeIntentProvider); } @WMSingleton @@ -873,6 +884,7 @@ public abstract class WMShellModule { Transitions transitions, @DynamicOverride DesktopUserRepositories desktopUserRepositories, ShellTaskOrganizer shellTaskOrganizer, + DesksOrganizer desksOrganizer, InteractionJankMonitor interactionJankMonitor, @ShellMainThread Handler handler) { int maxTaskLimit = DesktopModeStatus.getMaxTaskLimit(context); @@ -885,6 +897,7 @@ public abstract class WMShellModule { transitions, desktopUserRepositories, shellTaskOrganizer, + desksOrganizer, maxTaskLimit <= 0 ? null : maxTaskLimit, interactionJankMonitor, context, @@ -1011,6 +1024,7 @@ public abstract class WMShellModule { Optional<DesktopTasksLimiter> desktopTasksLimiter, AppHandleEducationController appHandleEducationController, AppToWebEducationController appToWebEducationController, + AppHandleAndHeaderVisibilityHelper appHandleAndHeaderVisibilityHelper, WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository, Optional<DesktopActivityOrientationChangeHandler> activityOrientationChangeHandler, FocusTransitionObserver focusTransitionObserver, @@ -1034,10 +1048,10 @@ public abstract class WMShellModule { rootTaskDisplayAreaOrganizer, interactionJankMonitor, genericLinksParser, assistContentRequester, windowDecorViewHostSupplier, multiInstanceHelper, desktopTasksLimiter, appHandleEducationController, appToWebEducationController, - windowDecorCaptionHandleRepository, activityOrientationChangeHandler, - focusTransitionObserver, desktopModeEventLogger, desktopModeUiEventLogger, - taskResourceLoader, recentsTransitionHandler, desktopModeCompatPolicy, - desktopTilingDecorViewModel, + appHandleAndHeaderVisibilityHelper, windowDecorCaptionHandleRepository, + activityOrientationChangeHandler, focusTransitionObserver, desktopModeEventLogger, + desktopModeUiEventLogger, taskResourceLoader, recentsTransitionHandler, + desktopModeCompatPolicy, desktopTilingDecorViewModel, multiDisplayDragMoveIndicatorController)); } @@ -1065,6 +1079,16 @@ public abstract class WMShellModule { @WMSingleton @Provides + static AppHandleAndHeaderVisibilityHelper provideAppHandleAndHeaderVisibilityHelper( + @NonNull Context context, + @NonNull DisplayController displayController, + @NonNull DesktopModeCompatPolicy desktopModeCompatPolicy) { + return new AppHandleAndHeaderVisibilityHelper(context, displayController, + desktopModeCompatPolicy); + } + + @WMSingleton + @Provides static WindowDecorTaskResourceLoader provideWindowDecorTaskResourceLoader( @NonNull Context context, @NonNull ShellInit shellInit, @NonNull ShellController shellController, @@ -1203,7 +1227,6 @@ public abstract class WMShellModule { Optional<DesktopMixedTransitionHandler> desktopMixedTransitionHandler, Optional<BackAnimationController> backAnimationController, DesktopWallpaperActivityTokenProvider desktopWallpaperActivityTokenProvider, - @NonNull DesksTransitionObserver desksTransitionObserver, ShellInit shellInit) { return desktopUserRepositories.flatMap( repository -> @@ -1216,17 +1239,21 @@ public abstract class WMShellModule { desktopMixedTransitionHandler.get(), backAnimationController.get(), desktopWallpaperActivityTokenProvider, - desksTransitionObserver, shellInit))); } @WMSingleton @Provides - static DesksTransitionObserver provideDesksTransitionObserver( - @NonNull @DynamicOverride DesktopUserRepositories desktopUserRepositories, + static Optional<DesksTransitionObserver> provideDesksTransitionObserver( + Context context, + @DynamicOverride DesktopUserRepositories desktopUserRepositories, @NonNull DesksOrganizer desksOrganizer ) { - return new DesksTransitionObserver(desktopUserRepositories, desksOrganizer); + if (DesktopModeStatus.canEnterDesktopModeOrShowAppHandle(context)) { + return Optional.of( + new DesksTransitionObserver(desktopUserRepositories, desksOrganizer)); + } + return Optional.empty(); } @WMSingleton @@ -1288,10 +1315,12 @@ public abstract class WMShellModule { static Optional<DesktopDisplayEventHandler> provideDesktopDisplayEventHandler( Context context, ShellInit shellInit, + @ShellMainThread CoroutineScope mainScope, DisplayController displayController, Optional<DesktopUserRepositories> desktopUserRepositories, Optional<DesktopTasksController> desktopTasksController, - Optional<DesktopDisplayModeController> desktopDisplayModeController + Optional<DesktopDisplayModeController> desktopDisplayModeController, + DesktopRepositoryInitializer desktopRepositoryInitializer ) { if (!DesktopModeStatus.canEnterDesktopMode(context)) { return Optional.empty(); @@ -1300,7 +1329,9 @@ public abstract class WMShellModule { new DesktopDisplayEventHandler( context, shellInit, + mainScope, displayController, + desktopRepositoryInitializer, desktopUserRepositories.get(), desktopTasksController.get(), desktopDisplayModeController.get())); @@ -1441,7 +1472,9 @@ public abstract class WMShellModule { RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, IWindowManager windowManager, ShellTaskOrganizer shellTaskOrganizer, - DesktopWallpaperActivityTokenProvider desktopWallpaperActivityTokenProvider + DesktopWallpaperActivityTokenProvider desktopWallpaperActivityTokenProvider, + InputManager inputManager, + @ShellMainThread Handler mainHandler ) { if (!DesktopModeStatus.canEnterDesktopMode(context)) { return Optional.empty(); @@ -1453,7 +1486,9 @@ public abstract class WMShellModule { rootTaskDisplayAreaOrganizer, windowManager, shellTaskOrganizer, - desktopWallpaperActivityTokenProvider)); + desktopWallpaperActivityTokenProvider, + inputManager, + mainHandler)); } // @@ -1535,7 +1570,8 @@ public abstract class WMShellModule { Optional<DesktopTasksTransitionObserver> desktopTasksTransitionObserverOptional, Optional<DesktopDisplayEventHandler> desktopDisplayEventHandler, Optional<DesktopModeKeyGestureHandler> desktopModeKeyGestureHandler, - Optional<SystemModalsTransitionHandler> systemModalsTransitionHandler) { + Optional<SystemModalsTransitionHandler> systemModalsTransitionHandler, + ShellCrashHandler shellCrashHandler) { return new Object(); } @@ -1555,4 +1591,20 @@ public abstract class WMShellModule { return new UserProfileContexts(context, shellController, shellInit); } + @WMSingleton + @Provides + static ShellCrashHandler provideShellCrashHandler( + Context context, + ShellTaskOrganizer shellTaskOrganizer, + HomeIntentProvider homeIntentProvider, + ShellInit shellInit) { + return new ShellCrashHandler(context, shellTaskOrganizer, homeIntentProvider, shellInit); + } + + @WMSingleton + @Provides + static HomeIntentProvider provideHomeIntentProvider(Context context) { + return new HomeIntentProvider(context); + } + } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt index afc48acad4f5..683b74392fa6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt @@ -23,15 +23,21 @@ import com.android.internal.protolog.ProtoLog import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.DisplayController.OnDisplaysChangedListener import com.android.wm.shell.desktopmode.multidesks.OnDeskRemovedListener +import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE import com.android.wm.shell.shared.desktopmode.DesktopModeStatus import com.android.wm.shell.sysui.ShellInit +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.cancel +import kotlinx.coroutines.launch /** Handles display events in desktop mode */ class DesktopDisplayEventHandler( private val context: Context, shellInit: ShellInit, + private val mainScope: CoroutineScope, private val displayController: DisplayController, + private val desktopRepositoryInitializer: DesktopRepositoryInitializer, private val desktopUserRepositories: DesktopUserRepositories, private val desktopTasksController: DesktopTasksController, private val desktopDisplayModeController: DesktopDisplayModeController, @@ -61,15 +67,19 @@ class DesktopDisplayEventHandler( logV("Display #$displayId does not support desks") return } - logV("Creating new desk in new display#$displayId") - // TODO: b/362720497 - when SystemUI crashes with a freeform task open for any reason, the - // task is recreated and received in [FreeformTaskListener] before this display callback - // is invoked, which results in the repository trying to add the task to a desk before the - // desk has been recreated here, which may result in a crash-loop if the repository is - // checking that the desk exists before adding a task to it. See b/391984373. - desktopTasksController.createDesk(displayId) - // TODO: b/393978539 - consider activating the desk on creation when applicable, such as - // for connected displays. + + mainScope.launch { + desktopRepositoryInitializer.isInitialized.collect { initialized -> + if (!initialized) return@collect + if (desktopRepository.getNumberOfDesks(displayId) == 0) { + logV("Creating new desk in new display#$displayId") + // TODO: b/393978539 - consider activating the desk on creation when + // applicable, such as for connected displays. + desktopTasksController.createDesk(displayId) + } + cancel() + } + } } override fun onDisplayRemoved(displayId: Int) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayModeController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayModeController.kt index e89aafe267ed..904d86282c39 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayModeController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayModeController.kt @@ -22,6 +22,8 @@ import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED import android.app.WindowConfiguration.windowingModeToString import android.content.Context +import android.hardware.input.InputManager +import android.os.Handler import android.provider.Settings import android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS import android.view.Display.DEFAULT_DISPLAY @@ -29,11 +31,13 @@ import android.view.IWindowManager import android.view.WindowManager.TRANSIT_CHANGE import android.window.DesktopExperienceFlags import android.window.WindowContainerTransaction +import com.android.internal.annotations.VisibleForTesting import com.android.internal.protolog.ProtoLog import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE +import com.android.wm.shell.shared.annotations.ShellMainThread import com.android.wm.shell.transition.Transitions /** Controls the display windowing mode in desktop mode */ @@ -44,8 +48,26 @@ class DesktopDisplayModeController( private val windowManager: IWindowManager, private val shellTaskOrganizer: ShellTaskOrganizer, private val desktopWallpaperActivityTokenProvider: DesktopWallpaperActivityTokenProvider, + private val inputManager: InputManager, + @ShellMainThread private val mainHandler: Handler, ) { + private val onTabletModeChangedListener = + object : InputManager.OnTabletModeChangedListener { + override fun onTabletModeChanged(whenNanos: Long, inTabletMode: Boolean) { + refreshDisplayWindowingMode() + } + } + + init { + if (DesktopExperienceFlags.FORM_FACTOR_BASED_DESKTOP_FIRST_SWITCH.isTrue) { + inputManager.registerOnTabletModeChangedListener( + onTabletModeChangedListener, + mainHandler, + ) + } + } + fun refreshDisplayWindowingMode() { if (!DesktopExperienceFlags.ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING.isTrue) return @@ -89,10 +111,20 @@ class DesktopDisplayModeController( transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ null) } - private fun getTargetWindowingModeForDefaultDisplay(): Int { + @VisibleForTesting + fun getTargetWindowingModeForDefaultDisplay(): Int { if (isExtendedDisplayEnabled() && hasExternalDisplay()) { return WINDOWING_MODE_FREEFORM } + if (DesktopExperienceFlags.FORM_FACTOR_BASED_DESKTOP_FIRST_SWITCH.isTrue) { + if (isInClamshellMode()) { + return WINDOWING_MODE_FREEFORM + } + return WINDOWING_MODE_FULLSCREEN + } + + // If form factor-based desktop first switch is disabled, use the default display windowing + // mode here to keep the freeform mode for some form factors (e.g., FEATURE_PC). return windowManager.getWindowingMode(DEFAULT_DISPLAY) } @@ -108,6 +140,8 @@ class DesktopDisplayModeController( private fun hasExternalDisplay() = rootTaskDisplayAreaOrganizer.getDisplayIds().any { it != DEFAULT_DISPLAY } + private fun isInClamshellMode() = inputManager.isInTabletMode() == InputManager.SWITCH_STATE_OFF + private fun logV(msg: String, vararg arguments: Any?) { ProtoLog.v(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments) } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt index 1f7edb413908..4646662073e6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt @@ -452,6 +452,11 @@ class DesktopMixedTransitionHandler( private fun findTaskChange(info: TransitionInfo, taskId: Int): TransitionInfo.Change? = info.changes.firstOrNull { change -> change.taskInfo?.taskId == taskId } + private fun findLaunchChange(info: TransitionInfo): TransitionInfo.Change? = + info.changes.firstOrNull { change -> + change.mode == TRANSIT_OPEN && change.taskInfo != null && change.taskInfo!!.isFreeform + } + private fun findDesktopTaskLaunchChange( info: TransitionInfo, launchTaskId: Int?, @@ -459,14 +464,18 @@ class DesktopMixedTransitionHandler( return if (launchTaskId != null) { // Launching a known task (probably from background or moving to front), so // specifically look for it. - findTaskChange(info, launchTaskId) + val launchChange = findTaskChange(info, launchTaskId) + if ( + DesktopModeFlags.ENABLE_DESKTOP_OPENING_DEEPLINK_MINIMIZE_ANIMATION_BUGFIX.isTrue && + launchChange == null + ) { + findLaunchChange(info) + } else { + launchChange + } } else { // Launching a new task, so the first opening freeform task. - info.changes.firstOrNull { change -> - change.mode == TRANSIT_OPEN && - change.taskInfo != null && - change.taskInfo!!.isFreeform - } + findLaunchChange(info) } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt index 5269318943d9..1ea545f3ab67 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt @@ -56,7 +56,6 @@ class DesktopModeKeyGestureHandler( override fun handleKeyGestureEvent(event: KeyGestureEvent, focusedToken: IBinder?): Boolean { if ( - !isKeyGestureSupported(event.keyGestureType) || !desktopTasksController.isPresent || !desktopModeWindowDecorViewModel.isPresent ) { @@ -136,19 +135,6 @@ class DesktopModeKeyGestureHandler( } } - override fun isKeyGestureSupported(gestureType: Int): Boolean = - when (gestureType) { - KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY -> - enableMoveToNextDisplayShortcut() - KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW, - KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW, - KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAXIMIZE_FREEFORM_WINDOW, - KeyGestureEvent.KEY_GESTURE_TYPE_MINIMIZE_FREEFORM_WINDOW -> - DesktopModeFlags.ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS.isTrue && - manageKeyGestures() - else -> false - } - // TODO: b/364154795 - wait for the completion of moveToNextDisplay transition, otherwise it // will pick a wrong task when a user quickly perform other actions with keyboard shortcuts // after moveToNextDisplay, and move this to FocusTransitionObserver class. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeMoveToDisplayTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeMoveToDisplayTransitionHandler.kt index fbf170f13a40..91bd3c9b6c22 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeMoveToDisplayTransitionHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeMoveToDisplayTransitionHandler.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.desktopmode import android.animation.Animator +import android.animation.AnimatorSet import android.animation.ValueAnimator import android.os.IBinder import android.view.Choreographer @@ -28,9 +29,7 @@ import com.android.wm.shell.shared.animation.Interpolators import com.android.wm.shell.transition.Transitions import kotlin.time.Duration.Companion.milliseconds -/** - * Transition handler for moving a window to a different display. - */ +/** Transition handler for moving a window to a different display. */ class DesktopModeMoveToDisplayTransitionHandler( private val animationTransaction: SurfaceControl.Transaction ) : Transitions.TransitionHandler { @@ -47,46 +46,52 @@ class DesktopModeMoveToDisplayTransitionHandler( finishTransaction: SurfaceControl.Transaction, finishCallback: Transitions.TransitionFinishCallback, ): Boolean { - val change = info.changes.find { it.startDisplayId != it.endDisplayId } ?: return false - ValueAnimator.ofFloat(0f, 1f) - .apply { - duration = ANIM_DURATION.inWholeMilliseconds - interpolator = Interpolators.LINEAR - addUpdateListener { animation -> - animationTransaction - .setAlpha(change.leash, animation.animatedValue as Float) - .setFrameTimeline(Choreographer.getInstance().vsyncId) - .apply() + val changes = info.changes.filter { it.startDisplayId != it.endDisplayId } + if (changes.isEmpty()) return false + for (change in changes) { + val endBounds = change.endAbsBounds + // The position should be relative to the parent. For example, in ActivityEmbedding, the + // leash surface for the embedded Activity is parented to the container. + val endPosition = change.endRelOffset + startTransaction + .setPosition(change.leash, endPosition.x.toFloat(), endPosition.y.toFloat()) + .setWindowCrop(change.leash, endBounds.width(), endBounds.height()) + } + startTransaction.apply() + + val animator = AnimatorSet() + animator.playTogether( + changes.map { + ValueAnimator.ofFloat(0f, 1f).apply { + duration = ANIM_DURATION.inWholeMilliseconds + interpolator = Interpolators.LINEAR + addUpdateListener { animation -> + animationTransaction + .setAlpha(it.leash, animation.animatedValue as Float) + .setFrameTimeline(Choreographer.getInstance().vsyncId) + .apply() + } } - addListener( - object : Animator.AnimatorListener { - override fun onAnimationStart(animation: Animator) { - val endBounds = change.endAbsBounds - startTransaction - .setPosition( - change.leash, - endBounds.left.toFloat(), - endBounds.top.toFloat(), - ) - .setWindowCrop(change.leash, endBounds.width(), endBounds.height()) - .apply() - } + } + ) + animator.addListener( + object : Animator.AnimatorListener { + override fun onAnimationStart(animation: Animator) = Unit - override fun onAnimationEnd(animation: Animator) { - finishTransaction.apply() - finishCallback.onTransitionFinished(null) - } + override fun onAnimationEnd(animation: Animator) { + finishTransaction.apply() + finishCallback.onTransitionFinished(null) + } - override fun onAnimationCancel(animation: Animator) { - finishTransaction.apply() - finishCallback.onTransitionFinished(null) - } + override fun onAnimationCancel(animation: Animator) { + finishTransaction.apply() + finishCallback.onTransitionFinished(null) + } - override fun onAnimationRepeat(animation: Animator) = Unit - } - ) + override fun onAnimationRepeat(animation: Animator) = Unit } - .start() + ) + animator.start() return true } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt index c5ee3137e5ba..fa98d0339a65 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt @@ -22,6 +22,13 @@ import android.annotation.DimenRes import android.app.ActivityManager.RunningTaskInfo import android.app.TaskInfo import android.content.Context +import android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK +import android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK +import android.content.Intent.FLAG_ACTIVITY_NEW_TASK +import android.content.pm.ActivityInfo.LAUNCH_MULTIPLE +import android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE +import android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE_PER_TASK +import android.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED import android.content.pm.ActivityInfo.isFixedOrientationLandscape import android.content.pm.ActivityInfo.isFixedOrientationPortrait @@ -30,7 +37,9 @@ import android.content.res.Configuration.ORIENTATION_PORTRAIT import android.graphics.Rect import android.os.SystemProperties import android.util.Size +import android.window.DesktopModeFlags import com.android.wm.shell.R +import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.DisplayLayout import kotlin.math.ceil @@ -264,6 +273,58 @@ fun getAppHeaderHeight(context: Context): Int = @DimenRes fun getAppHeaderHeightId(): Int = R.dimen.desktop_mode_freeform_decor_caption_height /** + * Returns the task bounds a launching task should inherit from an existing running instance. + * Returns null if there are no bounds to inherit. + */ +fun getInheritedExistingTaskBounds( + taskRepository: DesktopRepository, + shellTaskOrganizer: ShellTaskOrganizer, + task: RunningTaskInfo, + deskId: Int, +): Rect? { + if (!DesktopModeFlags.INHERIT_TASK_BOUNDS_FOR_TRAMPOLINE_TASK_LAUNCHES.isTrue) return null + val activeTask = taskRepository.getExpandedTasksIdsInDeskOrdered(deskId).firstOrNull() + if (activeTask == null) return null + val lastTask = shellTaskOrganizer.getRunningTaskInfo(activeTask) + val lastTaskTopActivity = lastTask?.topActivity + val currentTaskTopActivity = task.topActivity + val intentFlags = task.baseIntent.flags + val launchMode = task.topActivityInfo?.launchMode ?: LAUNCH_MULTIPLE + return when { + // No running task activity to inherit bounds from. + lastTaskTopActivity == null -> null + // No current top activity to set bounds for. + currentTaskTopActivity == null -> null + // Top task is not an instance of the launching activity, do not inherit its bounds. + lastTaskTopActivity.packageName != currentTaskTopActivity.packageName -> null + // Top task is an instance of launching activity. Activity will be launching in a new + // task with the existing task also being closed. Inherit existing task bounds to + // prevent new task jumping. + (isLaunchingNewTask(launchMode, intentFlags) && isClosingExitingInstance(intentFlags)) -> + lastTask.configuration.windowConfiguration.bounds + else -> null + } +} + +/** + * Returns true if the launch mode or intent will result in a new task being created for the + * activity. + */ +private fun isLaunchingNewTask(launchMode: Int, intentFlags: Int) = + launchMode == LAUNCH_SINGLE_TASK || + launchMode == LAUNCH_SINGLE_INSTANCE || + launchMode == LAUNCH_SINGLE_INSTANCE_PER_TASK || + (intentFlags and FLAG_ACTIVITY_NEW_TASK) != 0 + +/** + * Returns true if the intent will result in an existing task instance being closed if a new one + * appears. + */ +private fun isClosingExitingInstance(intentFlags: Int) = + (intentFlags and FLAG_ACTIVITY_CLEAR_TASK) != 0 || + (intentFlags and FLAG_ACTIVITY_MULTIPLE_TASK) == 0 + +/** * Calculates the desired initial bounds for applications in desktop windowing. This is done as a * scale of the screen bounds. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java index 70539902f651..b3b4d59090e8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java @@ -32,6 +32,7 @@ import android.content.Context; import android.graphics.PointF; import android.graphics.Rect; import android.graphics.Region; +import android.view.Display; import android.view.SurfaceControl; import android.window.DesktopModeFlags; @@ -114,6 +115,7 @@ public class DesktopModeVisualIndicator { private final Context mContext; private final DisplayController mDisplayController; private final ActivityManager.RunningTaskInfo mTaskInfo; + private final Display mDisplay; private IndicatorType mCurrentType; private final DragStartState mDragStartState; @@ -145,9 +147,10 @@ public class DesktopModeVisualIndicator { mCurrentType = NO_INDICATOR; mDragStartState = dragStartState; mSnapEventHandler = snapEventHandler; + mDisplay = mDisplayController.getDisplay(mTaskInfo.displayId); mVisualIndicatorViewContainer.createView( mContext, - mDisplayController.getDisplay(mTaskInfo.displayId), + mDisplay, mDisplayController.getDisplayLayout(mTaskInfo.displayId), mTaskInfo, taskSurface @@ -175,6 +178,7 @@ public class DesktopModeVisualIndicator { /** Start the fade-in animation. */ void fadeInIndicator() { + if (mCurrentType == NO_INDICATOR) return; mVisualIndicatorViewContainer.fadeInIndicator( mDisplayController.getDisplayLayout(mTaskInfo.displayId), mCurrentType, mTaskInfo.displayId); @@ -193,7 +197,7 @@ public class DesktopModeVisualIndicator { if (inputCoordinates.x > layout.width()) return TO_SPLIT_RIGHT_INDICATOR; IndicatorType result; if (BubbleAnythingFlagHelper.enableBubbleToFullscreen() - && !DesktopModeStatus.canEnterDesktopMode(mContext)) { + && !DesktopModeStatus.isDesktopModeSupportedOnDisplay(mContext, mDisplay)) { // If desktop is not available, default to "no indicator" result = NO_INDICATOR; } else { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt index bbb300ea42da..8636bc1f56c2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt @@ -235,6 +235,10 @@ class DesktopRepository( /** Returns the default desk in the given display. */ private fun getDefaultDesk(displayId: Int): Desk? = desktopData.getDefaultDesk(displayId) + /** Returns whether the given desk is active in its display. */ + fun isDeskActive(deskId: Int): Boolean = + desktopData.getAllActiveDesks().any { desk -> desk.deskId == deskId } + /** Sets the given desk as the active one in the given display. */ fun setActiveDesk(displayId: Int, deskId: Int) { logD("setActiveDesk for displayId=%d and deskId=%d", displayId, deskId) @@ -485,7 +489,7 @@ class DesktopRepository( fun getExpandedTasksOrdered(displayId: Int): List<Int> = getFreeformTasksInZOrder(displayId).filter { !isMinimizedTask(it) } - @VisibleForTesting + /** Returns all active non-minimized tasks for [deskId] ordered from top to bottom. */ fun getExpandedTasksIdsInDeskOrdered(deskId: Int): List<Int> = getFreeformTasksIdsInDeskInZOrder(deskId).filter { !isMinimizedTask(it) } @@ -712,12 +716,15 @@ class DesktopRepository( } /** - * Returns the top transparent fullscreen task id for a given display's active desk, or null. + * Returns the top transparent fullscreen task id for a given display, or null. * * TODO: b/389960283 - add explicit [deskId] argument. */ fun getTopTransparentFullscreenTaskId(displayId: Int): Int? = - desktopData.getActiveDesk(displayId)?.topTransparentFullscreenTaskId + desktopData + .desksSequence(displayId) + .mapNotNull { it.topTransparentFullscreenTaskId } + .firstOrNull() /** * Clears the top transparent fullscreen task id info for a given display's active desk. @@ -814,7 +821,6 @@ class DesktopRepository( } /** Minimizes the task in its desk. */ - @VisibleForTesting fun minimizeTaskInDesk(displayId: Int, deskId: Int, taskId: Int) { logD("MinimizeTaskInDesk: displayId=%d deskId=%d, task=%d", displayId, deskId, taskId) desktopData.getDesk(deskId)?.minimizedTasks?.add(taskId) @@ -929,6 +935,12 @@ class DesktopRepository( listener.onDeskRemoved(displayId = desk.displayId, deskId = desk.deskId) } } + if ( + DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue && + DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue + ) { + removeDeskFromPersistentRepository(desk) + } return activeTasks } @@ -1027,6 +1039,24 @@ class DesktopRepository( } } + private fun removeDeskFromPersistentRepository(desk: Desk) { + mainCoroutineScope.launch { + try { + logD( + "updatePersistentRepositoryForRemovedDesk user=%d desk=%d", + userId, + desk.deskId, + ) + persistentRepository.removeDesktop(userId = userId, desktopId = desk.deskId) + } catch (throwable: Throwable) { + logE( + "An exception occurred while updating the persistent repository \n%s", + throwable.stackTrace, + ) + } + } + } + internal fun dump(pw: PrintWriter, prefix: String) { val innerPrefix = "$prefix " pw.println("${prefix}DesktopRepository") @@ -1045,6 +1075,7 @@ class DesktopRepository( } .forEach { (displayId, activeDeskId, desks) -> pw.println("${prefix}Display #$displayId:") + pw.println("${innerPrefix}numOfDesks=${desks.size}") pw.println("${innerPrefix}activeDesk=$activeDeskId") pw.println("${innerPrefix}desks:") val desksPrefix = "$innerPrefix " diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListener.kt index fcdf4af531ee..e04d14459284 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListener.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListener.kt @@ -53,24 +53,12 @@ class DesktopTaskChangeListener(private val desktopUserRepositories: DesktopUser // Case 1: When the task change is from a task in the desktop repository which is now // fullscreen, // remove the task from the desktop repository since it is no longer a freeform task. - if (!isFreeformTask(taskInfo)) { - if (desktopRepository.isActiveTask(taskInfo.taskId)) { - desktopRepository.removeTask(taskInfo.displayId, taskInfo.taskId) - } - } else { // Task change is a freeform task - if (!desktopRepository.isActiveTask(taskInfo.taskId)) { - // Case 2: When the task change is a freeform visible task, but the task is not - // yet active in the desktop repository, adds task to desktop repository. - desktopRepository.addTask(taskInfo.displayId, taskInfo.taskId, taskInfo.isVisible) - } else { - // Case 3: When the task change is a freeform task which already exists as an active - // task in the desktop repository, updates the task state. - desktopRepository.updateTask( - taskInfo.displayId, - taskInfo.taskId, - taskInfo.isVisible, - ) - } + if (!isFreeformTask(taskInfo) && desktopRepository.isActiveTask(taskInfo.taskId)) { + desktopRepository.removeTask(taskInfo.displayId, taskInfo.taskId) + } else if (isFreeformTask(taskInfo)) { + // If the task is already active in the repository, then moves task to the front, + // else adds the task. + desktopRepository.addTask(taskInfo.displayId, taskInfo.taskId, taskInfo.isVisible) } } 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 301b79afd537..d0356d55035d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -32,6 +32,8 @@ import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED import android.app.WindowConfiguration.WindowingMode import android.content.Context import android.content.Intent +import android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK +import android.content.Intent.FLAG_ACTIVITY_NEW_TASK import android.graphics.Point import android.graphics.PointF import android.graphics.Rect @@ -48,6 +50,7 @@ import android.view.DragEvent import android.view.MotionEvent import android.view.SurfaceControl import android.view.SurfaceControl.Transaction +import android.view.WindowManager import android.view.WindowManager.TRANSIT_CHANGE import android.view.WindowManager.TRANSIT_CLOSE import android.view.WindowManager.TRANSIT_NONE @@ -84,6 +87,7 @@ import com.android.wm.shell.bubbles.BubbleController import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.DisplayLayout import com.android.wm.shell.common.ExternalInterfaceBinder +import com.android.wm.shell.common.HomeIntentProvider import com.android.wm.shell.common.MultiInstanceHelper import com.android.wm.shell.common.MultiInstanceHelper.Companion.getComponent import com.android.wm.shell.common.RemoteCallable @@ -111,6 +115,9 @@ import com.android.wm.shell.desktopmode.multidesks.DeskTransition import com.android.wm.shell.desktopmode.multidesks.DesksOrganizer import com.android.wm.shell.desktopmode.multidesks.DesksTransitionObserver import com.android.wm.shell.desktopmode.multidesks.OnDeskRemovedListener +import com.android.wm.shell.desktopmode.multidesks.createDesk +import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer +import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer.DeskRecreationFactory import com.android.wm.shell.draganddrop.DragAndDropController import com.android.wm.shell.freeform.FreeformTaskTransitionStarter import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE @@ -190,6 +197,7 @@ class DesktopTasksController( private val dragToDesktopTransitionHandler: DragToDesktopTransitionHandler, private val desktopImmersiveController: DesktopImmersiveController, private val userRepositories: DesktopUserRepositories, + desktopRepositoryInitializer: DesktopRepositoryInitializer, private val recentsTransitionHandler: RecentsTransitionHandler, private val multiInstanceHelper: MultiInstanceHelper, @ShellMainThread private val mainExecutor: ShellExecutor, @@ -210,6 +218,7 @@ class DesktopTasksController( private val desktopModeCompatPolicy: DesktopModeCompatPolicy, private val dragToDisplayTransitionHandler: DragToDisplayTransitionHandler, private val moveToDisplayTransitionHandler: DesktopModeMoveToDisplayTransitionHandler, + private val homeIntentProvider: HomeIntentProvider, ) : RemoteCallable<DesktopTasksController>, Transitions.TransitionHandler, @@ -235,6 +244,10 @@ class DesktopTasksController( removeVisualIndicator() } + override fun onTransitionInterrupted() { + removeVisualIndicator() + } + private fun removeVisualIndicator() { visualIndicator?.fadeOutIndicator { releaseVisualIndicator() } } @@ -267,6 +280,19 @@ class DesktopTasksController( } userId = ActivityManager.getCurrentUser() taskRepository = userRepositories.getProfile(userId) + + if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) { + desktopRepositoryInitializer.deskRecreationFactory = + DeskRecreationFactory { deskUserId, destinationDisplayId, deskId -> + if (deskUserId != userId) { + // TODO: b/400984250 - add multi-user support for multi-desk restoration. + logW("Tried to recreated desk of another user.") + deskId + } else { + desksOrganizer.createDesk(destinationDisplayId) + } + } + } } private fun onInit() { @@ -762,7 +788,9 @@ class DesktopTasksController( fun minimizeTask(taskInfo: RunningTaskInfo, minimizeReason: MinimizeReason) { val wct = WindowContainerTransaction() - val isMinimizingToPip = taskInfo.pictureInPictureParams?.isAutoEnterEnabled() ?: false + val isMinimizingToPip = + DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PIP.isTrue && + (taskInfo.pictureInPictureParams?.isAutoEnterEnabled() ?: false) // If task is going to PiP, start a PiP transition instead of a minimize transition if (isMinimizingToPip) { val requestInfo = @@ -961,10 +989,13 @@ class DesktopTasksController( .apply { launchWindowingMode = WINDOWING_MODE_FREEFORM } .toBundle(), ) + val deskId = taskRepository.getDeskIdForTask(taskId) ?: getDefaultDeskId(DEFAULT_DISPLAY) startLaunchTransition( TRANSIT_OPEN, wct, taskId, + deskId = deskId, + displayId = DEFAULT_DISPLAY, remoteTransition = remoteTransition, unminimizeReason = unminimizeReason, ) @@ -982,19 +1013,26 @@ class DesktopTasksController( remoteTransition: RemoteTransition? = null, unminimizeReason: UnminimizeReason = UnminimizeReason.UNKNOWN, ) { - logV("moveTaskToFront taskId=%s", taskInfo.taskId) + val deskId = + taskRepository.getDeskIdForTask(taskInfo.taskId) ?: getDefaultDeskId(taskInfo.displayId) + logV("moveTaskToFront taskId=%s deskId=%s", taskInfo.taskId, deskId) // If a task is tiled, another task should be brought to foreground with it so let // tiling controller handle the request. if (snapEventHandler.moveTaskToFrontIfTiled(taskInfo)) { return } val wct = WindowContainerTransaction() - wct.reorder(taskInfo.token, /* onTop= */ true, /* includingParents= */ true) + if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) { + desksOrganizer.reorderTaskToFront(wct, deskId, taskInfo) + } else { + wct.reorder(taskInfo.token, /* onTop= */ true, /* includingParents= */ true) + } startLaunchTransition( transitionType = TRANSIT_TO_FRONT, wct = wct, launchingTaskId = taskInfo.taskId, remoteTransition = remoteTransition, + deskId = deskId, displayId = taskInfo.displayId, unminimizeReason = unminimizeReason, ) @@ -1006,14 +1044,22 @@ class DesktopTasksController( wct: WindowContainerTransaction, launchingTaskId: Int?, remoteTransition: RemoteTransition? = null, - displayId: Int = DEFAULT_DISPLAY, + deskId: Int, + displayId: Int, unminimizeReason: UnminimizeReason = UnminimizeReason.UNKNOWN, ): IBinder { + logV( + "startLaunchTransition type=%s launchingTaskId=%d deskId=%d displayId=%d", + WindowManager.transitTypeToString(transitionType), + launchingTaskId, + deskId, + displayId, + ) // TODO: b/397619806 - Consolidate sharable logic with [handleFreeformTaskLaunch]. var launchTransaction = wct val taskIdToMinimize = addAndGetMinimizeChanges( - displayId, + deskId, launchTransaction, newTaskId = launchingTaskId, launchingNewIntent = launchingTaskId == null, @@ -1027,21 +1073,20 @@ class DesktopTasksController( ) var activationRunOnTransitStart: RunOnTransitStart? = null val shouldActivateDesk = - (DesktopExperienceFlags.ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING.isTrue || - DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) && - !isDesktopModeShowing(displayId) + when { + DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue -> + !taskRepository.isDeskActive(deskId) + DesktopExperienceFlags.ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING.isTrue -> { + !isDesktopModeShowing(displayId) + } + else -> false + } if (shouldActivateDesk) { - val deskIdToActivate = - checkNotNull( - launchingTaskId?.let { taskRepository.getDeskIdForTask(it) } - ?: getDefaultDeskId(displayId) - ) val activateDeskWct = WindowContainerTransaction() // TODO: b/391485148 - pass in the launching task here to apply task-limit policy, // but make sure to not do it twice since it is also done at the start of this // function. - activationRunOnTransitStart = - addDeskActivationChanges(deskIdToActivate, activateDeskWct) + activationRunOnTransitStart = addDeskActivationChanges(deskId, activateDeskWct) // Desk activation must be handled before app launch-related transactions. activateDeskWct.merge(launchTransaction, /* transfer= */ true) launchTransaction = activateDeskWct @@ -1152,7 +1197,14 @@ class DesktopTasksController( } wct.sendPendingIntent(pendingIntent, intent, ops.toBundle()) - startLaunchTransition(TRANSIT_OPEN, wct, launchingTaskId = null) + val deskId = getDefaultDeskId(displayId) + startLaunchTransition( + TRANSIT_OPEN, + wct, + launchingTaskId = null, + deskId = deskId, + displayId = displayId, + ) } /** @@ -1169,6 +1221,11 @@ class DesktopTasksController( return } + if (splitScreenController.isTaskInSplitScreen(task.taskId)) { + moveSplitPairToDisplay(task, displayId) + return + } + val wct = WindowContainerTransaction() val displayAreaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(displayId) if (displayAreaInfo == null) { @@ -1176,39 +1233,6 @@ class DesktopTasksController( return } - // check if the task is part of splitscreen - if ( - Flags.enableNonDefaultDisplaySplit() && - Flags.enableMoveToNextDisplayShortcut() && - splitScreenController.isTaskInSplitScreen(task.taskId) - ) { - val activeDeskId = taskRepository.getActiveDeskId(displayId) - logV("moveToDisplay: moving split root to displayId=%d", displayId) - val stageCoordinatorRootTaskToken = - splitScreenController.multiDisplayProvider.getDisplayRootForDisplayId( - DEFAULT_DISPLAY - ) - wct.reparent(stageCoordinatorRootTaskToken, displayAreaInfo.token, true /* onTop */) - val deactivationRunnable = - if (activeDeskId != null) { - // Split is being placed on top of an existing desk in the target display. Make - // sure it is cleaned up. - performDesktopExitCleanUp( - wct = wct, - deskId = activeDeskId, - displayId = displayId, - willExitDesktop = true, - shouldEndUpAtHome = false, - ) - } else { - null - } - val transition = - transitions.startTransition(TRANSIT_CHANGE, wct, moveToDisplayTransitionHandler) - deactivationRunnable?.invoke(transition) - return - } - val destinationDeskId = taskRepository.getDefaultDeskId(displayId) if (destinationDeskId == null) { logW("moveToDisplay: desk not found for display: $displayId") @@ -1270,6 +1294,53 @@ class DesktopTasksController( } /** + * Move split pair associated with the [task] to display with [displayId]. + * + * No-op if task is already on that display per [RunningTaskInfo.displayId]. + */ + private fun moveSplitPairToDisplay(task: RunningTaskInfo, displayId: Int) { + if (!splitScreenController.isTaskInSplitScreen(task.taskId)) { + return + } + + if (!Flags.enableNonDefaultDisplaySplit() || !Flags.enableMoveToNextDisplayShortcut()) { + return + } + + val wct = WindowContainerTransaction() + val displayAreaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(displayId) + if (displayAreaInfo == null) { + logW("moveSplitPairToDisplay: display not found") + return + } + + val activeDeskId = taskRepository.getActiveDeskId(displayId) + logV("moveSplitPairToDisplay: moving split root to displayId=%d", displayId) + + val stageCoordinatorRootTaskToken = + splitScreenController.multiDisplayProvider.getDisplayRootForDisplayId(DEFAULT_DISPLAY) + wct.reparent(stageCoordinatorRootTaskToken, displayAreaInfo.token, true /* onTop */) + + val deactivationRunnable = + if (activeDeskId != null) { + // Split is being placed on top of an existing desk in the target display. Make + // sure it is cleaned up. + performDesktopExitCleanUp( + wct = wct, + deskId = activeDeskId, + displayId = displayId, + willExitDesktop = true, + shouldEndUpAtHome = false, + ) + } else { + null + } + val transition = transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ null) + deactivationRunnable?.invoke(transition) + return + } + + /** * Quick-resizes a desktop task, toggling between a fullscreen state (represented by the stable * bounds) and a free floating state (either the last saved bounds if available or the default * bounds otherwise). @@ -1664,15 +1735,7 @@ class DesktopTasksController( wct.reorder(runningTaskInfo.token, /* onTop= */ true) } else if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) { // Task is not running, start it - wct.startTask( - taskId, - ActivityOptions.makeBasic() - .apply { - launchWindowingMode = WINDOWING_MODE_FREEFORM - splashScreenStyle = SPLASH_SCREEN_STYLE_ICON - } - .toBundle(), - ) + wct.startTask(taskId, createActivityOptionsForStartTask().toBundle()) } } @@ -1691,34 +1754,7 @@ class DesktopTasksController( } private fun addLaunchHomePendingIntent(wct: WindowContainerTransaction, displayId: Int) { - val userHandle = UserHandle.of(userId) - val launchHomeIntent = - Intent(Intent.ACTION_MAIN).apply { - if (displayId != DEFAULT_DISPLAY) { - addCategory(Intent.CATEGORY_SECONDARY_HOME) - } else { - addCategory(Intent.CATEGORY_HOME) - } - } - val options = - ActivityOptions.makeBasic().apply { - launchWindowingMode = WINDOWING_MODE_FULLSCREEN - pendingIntentBackgroundActivityStartMode = - ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS - if (Flags.enablePerDisplayDesktopWallpaperActivity()) { - launchDisplayId = displayId - } - } - val pendingIntent = - PendingIntent.getActivityAsUser( - context, - /* requestCode= */ 0, - launchHomeIntent, - PendingIntent.FLAG_IMMUTABLE, - /* options= */ null, - userHandle, - ) - wct.sendPendingIntent(pendingIntent, launchHomeIntent, options.toBundle()) + homeIntentProvider.addLaunchHomePendingIntent(wct, displayId, userId) } private fun addWallpaperActivity(displayId: Int, wct: WindowContainerTransaction) { @@ -2103,6 +2139,7 @@ class DesktopTasksController( // TODO(b/337915660): Add a transition handler for these; animations // need updates in some cases. val baseActivity = callingTaskInfo.baseActivity ?: return + val userHandle = UserHandle.of(callingTaskInfo.userId) val fillIn: Intent = userProfileContexts .getOrCreate(callingTaskInfo.userId) @@ -2110,11 +2147,13 @@ class DesktopTasksController( .getLaunchIntentForPackage(baseActivity.packageName) ?: return fillIn.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK) val launchIntent = - PendingIntent.getActivity( + PendingIntent.getActivityAsUser( context, /* requestCode= */ 0, fillIn, PendingIntent.FLAG_IMMUTABLE, + /* options= */ null, + userHandle, ) val options = createNewWindowOptions(callingTaskInfo) when (options.launchWindowingMode) { @@ -2141,10 +2180,14 @@ class DesktopTasksController( WINDOWING_MODE_FREEFORM -> { val wct = WindowContainerTransaction() wct.sendPendingIntent(launchIntent, fillIn, options.toBundle()) + val deskId = + taskRepository.getDeskIdForTask(callingTaskInfo.taskId) + ?: getDefaultDeskId(callingTaskInfo.displayId) startLaunchTransition( transitionType = TRANSIT_OPEN, wct = wct, launchingTaskId = null, + deskId = deskId, displayId = callingTaskInfo.displayId, ) } @@ -2224,6 +2267,7 @@ class DesktopTasksController( logV("skip keyguard is locked") return null } + val deskId = getDefaultDeskId(task.displayId) val wct = WindowContainerTransaction() if (shouldFreeformTaskLaunchSwitchToFullscreen(task)) { logD("Bring desktop tasks to front on transition=taskId=%d", task.taskId) @@ -2246,17 +2290,24 @@ class DesktopTasksController( runOnTransitStart?.invoke(transition) return wct } - val deskId = getDefaultDeskId(task.displayId) val runOnTransitStart = addDeskActivationChanges(deskId, wct, task) runOnTransitStart?.invoke(transition) wct.reorder(task.token, true) return wct } + val inheritedTaskBounds = + getInheritedExistingTaskBounds(taskRepository, shellTaskOrganizer, task, deskId) + if (!taskRepository.isActiveTask(task.taskId) && inheritedTaskBounds != null) { + // Inherit bounds from closing task instance to prevent application jumping different + // cascading positions. + wct.setBounds(task.token, inheritedTaskBounds) + } // TODO(b/365723620): Handle non running tasks that were launched after reboot. // If task is already visible, it must have been handled already and added to desktop mode. - // Cascade task only if it's not visible yet. + // Cascade task only if it's not visible yet and has no inherited bounds. if ( - DesktopModeFlags.ENABLE_CASCADING_WINDOWS.isTrue() && + inheritedTaskBounds == null && + DesktopModeFlags.ENABLE_CASCADING_WINDOWS.isTrue() && !taskRepository.isVisibleTask(task.taskId) ) { val displayLayout = displayController.getDisplayLayout(task.displayId) @@ -2288,7 +2339,7 @@ class DesktopTasksController( reason = DesktopImmersiveController.ExitReason.TASK_LAUNCH, ) // 2) minimize a Task if needed. - val taskIdToMinimize = addAndGetMinimizeChanges(task.displayId, wct, task.taskId) + val taskIdToMinimize = addAndGetMinimizeChanges(deskId, wct, task.taskId) addPendingAppLaunchTransition(transition, task.taskId, taskIdToMinimize) if (taskIdToMinimize != null) { addPendingMinimizeTransition(transition, taskIdToMinimize, MinimizeReason.TASK_LIMIT) @@ -2334,7 +2385,7 @@ class DesktopTasksController( // The desk was already showing and we're launching a new Task - we // might need to minimize another Task. val taskIdToMinimize = - addAndGetMinimizeChanges(task.displayId, wct, task.taskId) + addAndGetMinimizeChanges(deskId, wct, task.taskId) taskIdToMinimize?.let { minimizingTaskId -> addPendingMinimizeTransition( transition, @@ -2492,9 +2543,17 @@ class DesktopTasksController( ) { val targetDisplayId = taskRepository.getDisplayForDesk(deskId) val displayLayout = displayController.getDisplayLayout(targetDisplayId) ?: return - val initialBounds = getInitialBounds(displayLayout, task, targetDisplayId) - if (canChangeTaskPosition(task)) { - wct.setBounds(task.token, initialBounds) + val inheritedTaskBounds = + getInheritedExistingTaskBounds(taskRepository, shellTaskOrganizer, task, deskId) + if (inheritedTaskBounds != null) { + // Inherit bounds from closing task instance to prevent application jumping different + // cascading positions. + wct.setBounds(task.token, inheritedTaskBounds) + } else { + val initialBounds = getInitialBounds(displayLayout, task, targetDisplayId) + if (canChangeTaskPosition(task)) { + wct.setBounds(task.token, initialBounds) + } } if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) { desksOrganizer.moveTaskToDesk(wct = wct, deskId = deskId, task = task) @@ -2670,7 +2729,7 @@ class DesktopTasksController( /** Returns the ID of the Task that will be minimized, or null if no task will be minimized. */ private fun addAndGetMinimizeChanges( - displayId: Int, + deskId: Int, wct: WindowContainerTransaction, newTaskId: Int?, launchingNewIntent: Boolean = false, @@ -2679,7 +2738,7 @@ class DesktopTasksController( require(newTaskId == null || !launchingNewIntent) return desktopTasksLimiter .get() - .addAndGetMinimizeTaskChanges(displayId, wct, newTaskId, launchingNewIntent) + .addAndGetMinimizeTaskChanges(deskId, wct, newTaskId, launchingNewIntent) } private fun addPendingMinimizeTransition( @@ -2776,15 +2835,36 @@ class DesktopTasksController( } prepareForDeskActivation(displayId, wct) desksOrganizer.activateDesk(wct, deskId) - if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) { - // TODO: 362720497 - do non-running tasks need to be restarted with |wct#startTask|? - } taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate( doesAnyTaskRequireTaskbarRounding(displayId) ) - // TODO: b/362720497 - activating a desk with the intention to move a new task to - // it means we may need to minimize something in the activating desk. Do so here - // similar to how it's done in #bringDesktopAppsToFront. + val expandedTasksOrderedFrontToBack = + taskRepository.getExpandedTasksIdsInDeskOrdered(deskId = deskId) + // If we're adding a new Task we might need to minimize an old one + val taskIdToMinimize = + desktopTasksLimiter + .getOrNull() + ?.getTaskIdToMinimize(expandedTasksOrderedFrontToBack, newTaskIdInFront) + if (taskIdToMinimize != null) { + val taskToMinimize = shellTaskOrganizer.getRunningTaskInfo(taskIdToMinimize) + // TODO(b/365725441): Handle non running task minimization + if (taskToMinimize != null) { + desksOrganizer.minimizeTask(wct, deskId, taskToMinimize) + } + } + if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue) { + expandedTasksOrderedFrontToBack + .filter { taskId -> taskId != taskIdToMinimize } + .reversed() + .forEach { taskId -> + val runningTaskInfo = shellTaskOrganizer.getRunningTaskInfo(taskId) + if (runningTaskInfo == null) { + wct.startTask(taskId, createActivityOptionsForStartTask().toBundle()) + } else { + desksOrganizer.reorderTaskToFront(wct, deskId, runningTaskInfo) + } + } + } return { transition -> val activateDeskTransition = if (newTaskIdInFront != null) { @@ -2802,6 +2882,9 @@ class DesktopTasksController( ) } desksTransitionObserver.addPendingTransition(activateDeskTransition) + taskIdToMinimize?.let { minimizingTask -> + addPendingMinimizeTransition(transition, minimizingTask, MinimizeReason.TASK_LIMIT) + } } } @@ -3381,7 +3464,14 @@ class DesktopTasksController( if (windowingMode == WINDOWING_MODE_FREEFORM) { if (DesktopModeFlags.ENABLE_DESKTOP_TAB_TEARING_MINIMIZE_ANIMATION_BUGFIX.isTrue()) { // TODO b/376389593: Use a custom tab tearing transition/animation - startLaunchTransition(TRANSIT_OPEN, wct, launchingTaskId = null) + val deskId = getDefaultDeskId(DEFAULT_DISPLAY) + startLaunchTransition( + TRANSIT_OPEN, + wct, + launchingTaskId = null, + deskId = deskId, + displayId = DEFAULT_DISPLAY, + ) } else { desktopModeDragAndDropTransitionHandler.handleDropEvent(wct) } @@ -3426,6 +3516,13 @@ class DesktopTasksController( } } + private fun createActivityOptionsForStartTask(): ActivityOptions { + return ActivityOptions.makeBasic().apply { + launchWindowingMode = WINDOWING_MODE_FREEFORM + splashScreenStyle = SPLASH_SCREEN_STYLE_ICON + } + } + private fun dump(pw: PrintWriter, prefix: String) { val innerPrefix = "$prefix " pw.println("${prefix}DesktopTasksController") diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt index da369f094405..4ca58823b52b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt @@ -22,6 +22,7 @@ import android.os.Handler import android.os.IBinder import android.view.SurfaceControl import android.view.WindowManager.TRANSIT_TO_BACK +import android.window.DesktopExperienceFlags import android.window.DesktopModeFlags import android.window.TransitionInfo import android.window.WindowContainerTransaction @@ -31,6 +32,7 @@ import com.android.internal.protolog.ProtoLog import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UnminimizeReason +import com.android.wm.shell.desktopmode.multidesks.DesksOrganizer import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE import com.android.wm.shell.shared.annotations.ShellMainThread import com.android.wm.shell.sysui.UserChangeListener @@ -48,6 +50,7 @@ class DesktopTasksLimiter( transitions: Transitions, private val desktopUserRepositories: DesktopUserRepositories, private val shellTaskOrganizer: ShellTaskOrganizer, + private val desksOrganizer: DesksOrganizer, private val maxTasksLimit: Int?, private val interactionJankMonitor: InteractionJankMonitor, private val context: Context, @@ -258,7 +261,7 @@ class DesktopTasksLimiter( * returning the task to minimize. */ fun addAndGetMinimizeTaskChanges( - displayId: Int, + deskId: Int, wct: WindowContainerTransaction, newFrontTaskId: Int?, launchingNewIntent: Boolean = false, @@ -267,15 +270,19 @@ class DesktopTasksLimiter( val taskRepository = desktopUserRepositories.current val taskIdToMinimize = getTaskIdToMinimize( - taskRepository.getExpandedTasksOrdered(displayId), + taskRepository.getExpandedTasksIdsInDeskOrdered(deskId), newFrontTaskId, launchingNewIntent, ) - // If it's a running task, reorder it to back. taskIdToMinimize ?.let { shellTaskOrganizer.getRunningTaskInfo(it) } - // TODO: b/391485148 - this won't really work with multi-desks enabled. - ?.let { wct.reorder(it.token, /* onTop= */ false) } + ?.let { task -> + if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) { + wct.reorder(task.token, /* onTop= */ false) + } else { + desksOrganizer.minimizeTask(wct, deskId, task) + } + } return taskIdToMinimize } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt index bd9c30e2a495..7dabeb7c9d15 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt @@ -36,7 +36,6 @@ import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.back.BackAnimationController import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.isExitDesktopModeTransition import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider -import com.android.wm.shell.desktopmode.multidesks.DesksTransitionObserver import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE import com.android.wm.shell.shared.TransitionUtil import com.android.wm.shell.shared.desktopmode.DesktopModeStatus @@ -58,7 +57,6 @@ class DesktopTasksTransitionObserver( private val desktopMixedTransitionHandler: DesktopMixedTransitionHandler, private val backAnimationController: BackAnimationController, private val desktopWallpaperActivityTokenProvider: DesktopWallpaperActivityTokenProvider, - private val desksTransitionObserver: DesksTransitionObserver, shellInit: ShellInit, ) : Transitions.TransitionObserver { @@ -88,7 +86,6 @@ class DesktopTasksTransitionObserver( finishTransaction: SurfaceControl.Transaction, ) { // TODO: b/332682201 Update repository state - desksTransitionObserver.onTransitionReady(transition, info) if ( DesktopModeFlags.INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC .isTrue() && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue() diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt index e943c42dcdfc..24b2e4879546 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt @@ -24,8 +24,10 @@ import android.os.SystemClock import android.os.SystemProperties import android.os.UserHandle import android.view.SurfaceControl +import android.view.SurfaceControl.Transaction import android.view.WindowManager.TRANSIT_CLOSE import android.window.DesktopModeFlags +import android.window.DesktopModeFlags.ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX import android.window.TransitionInfo import android.window.TransitionInfo.Change import android.window.TransitionRequestInfo @@ -185,18 +187,29 @@ sealed class DragToDesktopTransitionHandler( */ fun finishDragToDesktopTransition(wct: WindowContainerTransaction): IBinder? { if (!inProgress) { + logV("finishDragToDesktop: not in progress, returning") // Don't attempt to finish a drag to desktop transition since there is no transition in // progress which means that the drag to desktop transition was never successfully // started. return null } - if (requireTransitionState().startAborted) { + val state = requireTransitionState() + if (state.startAborted) { + logV("finishDragToDesktop: start was aborted, clearing state") // Don't attempt to complete the drag-to-desktop since the start transition didn't // succeed as expected. Just reset the state as if nothing happened. clearState() return null } - return transitions.startTransition(TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP, wct, this) + if (state.startInterrupted) { + logV("finishDragToDesktop: start was interrupted, returning") + // We should only have interrupted the start transition after receiving a cancel/end + // request, let that existing request play out and just return here. + return null + } + state.endTransitionToken = + transitions.startTransition(TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP, wct, this) + return state.endTransitionToken } /** @@ -220,6 +233,11 @@ sealed class DragToDesktopTransitionHandler( clearState() return } + if (state.startInterrupted) { + // We should only have interrupted the start transition after receiving a cancel/end + // request, let that existing request play out and just return here. + return + } state.cancelState = cancelState if (state.draggedTaskChange != null && cancelState == CancelState.STANDARD_CANCEL) { @@ -227,7 +245,7 @@ sealed class DragToDesktopTransitionHandler( // transient to start and merge. Animate the cancellation (scale back to original // bounds) first before actually starting the cancel transition so that the wallpaper // is visible behind the animating task. - startCancelAnimation() + state.activeCancelAnimation = startCancelAnimation() } else if ( state.draggedTaskChange != null && (cancelState == CancelState.CANCEL_SPLIT_LEFT || @@ -255,7 +273,7 @@ sealed class DragToDesktopTransitionHandler( ) { if (bubbleController.isEmpty || state !is TransitionState.FromFullscreen) { // TODO(b/388853233): add support for dragging split task to bubble - startCancelAnimation() + state.activeCancelAnimation = startCancelAnimation() } else { // Animation is handled by BubbleController val wct = WindowContainerTransaction() @@ -327,8 +345,9 @@ sealed class DragToDesktopTransitionHandler( val taskInfo = state.draggedTaskChange?.taskInfo ?: error("Expected non-null taskInfo") val dragPosition = PointF(state.dragAnimator.position) val scale = state.dragAnimator.scale + val cornerRadius = state.dragAnimator.cornerRadius state.dragAnimator.cancelAnimator() - requestBubble(wct, taskInfo, onLeft, scale, dragPosition) + requestBubble(wct, taskInfo, onLeft, scale, cornerRadius, dragPosition) } private fun requestBubble( @@ -336,13 +355,14 @@ sealed class DragToDesktopTransitionHandler( taskInfo: RunningTaskInfo, onLeft: Boolean, taskScale: Float = 1f, + cornerRadius: Float = 0f, dragPosition: PointF = PointF(0f, 0f), ) { val controller = bubbleController.orElseThrow { IllegalStateException("BubbleController not set") } controller.expandStackAndSelectBubble( taskInfo, - BubbleTransitions.DragData(onLeft, taskScale, dragPosition, wct), + BubbleTransitions.DragData(onLeft, taskScale, cornerRadius, dragPosition, wct), ) } @@ -355,6 +375,19 @@ sealed class DragToDesktopTransitionHandler( ): Boolean { val state = requireTransitionState() + if ( + handleCancelOrExitAfterInterrupt( + transition, + info, + startTransaction, + finishTransaction, + finishCallback, + state, + ) + ) { + return true + } + val isStartDragToDesktop = info.type == TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP && transition == state.startTransitionToken @@ -537,6 +570,58 @@ sealed class DragToDesktopTransitionHandler( } } + private fun handleCancelOrExitAfterInterrupt( + transition: IBinder, + info: TransitionInfo, + startTransaction: Transaction, + finishTransaction: Transaction, + finishCallback: Transitions.TransitionFinishCallback, + state: TransitionState, + ): Boolean { + if (!ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX.isTrue) { + return false + } + val isCancelDragToDesktop = + info.type == TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP && + transition == state.cancelTransitionToken + val isEndDragToDesktop = + info.type == TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP && + transition == state.endTransitionToken + // We should only receive cancel or end transitions through startAnimation() if the + // start transition was interrupted while a cancel- or end-transition had already + // been requested. Finish the cancel/end transition to avoid having to deal with more + // incoming transitions, and clear the state for the next start-drag transition. + if (!isCancelDragToDesktop && !isEndDragToDesktop) { + return false + } + if (!state.startInterrupted) { + logW( + "Not interrupted, but received startAnimation for cancel/end drag." + + "isCancel=$isCancelDragToDesktop, isEnd=$isEndDragToDesktop" + ) + return false + } + logV( + "startAnimation: interrupted -> " + + "isCancel=$isCancelDragToDesktop, isEnd=$isEndDragToDesktop" + ) + if (isEndDragToDesktop) { + setupEndDragToDesktop(info, startTransaction, finishTransaction) + animateEndDragToDesktop(startTransaction = startTransaction, finishCallback) + } else { // isCancelDragToDesktop + // Similar to when we merge the cancel transition: ensure all tasks involved in the + // cancel transition are shown, and finish the transition immediately. + info.changes.forEach { change -> + startTransaction.show(change.leash) + finishTransaction.show(change.leash) + } + } + startTransaction.apply() + finishCallback.onTransitionFinished(/* wct= */ null) + clearState() + return true + } + /** * Calculates start drag to desktop layers for transition [info]. The leash layer is calculated * based on its change position in the transition, e.g. `appLayer = appLayers - i`, where i is @@ -588,6 +673,7 @@ sealed class DragToDesktopTransitionHandler( ?: error("Start transition expected to be waiting for merge but wasn't") if (isEndTransition) { logV("mergeAnimation: end-transition, target=$mergeTarget") + state.mergedEndTransition = true setupEndDragToDesktop( info, startTransaction = startT, @@ -615,6 +701,41 @@ sealed class DragToDesktopTransitionHandler( return } logW("unhandled merge transition: transitionInfo=$info") + // Handle unknown incoming transitions by finishing the start transition. For now, only do + // this if we've already requested a cancel- or end transition. If we've already merged the + // end-transition, or if the end-transition is running on its own, then just wait until that + // finishes instead. If we've merged the cancel-transition we've finished the + // start-transition and won't reach this code. + if ( + mergeTarget == state.startTransitionToken && + isCancelOrEndTransitionRequested(state) && + !state.mergedEndTransition + ) { + interruptStartTransition(state) + } + } + + private fun isCancelOrEndTransitionRequested(state: TransitionState): Boolean = + state.cancelTransitionToken != null || state.endTransitionToken != null + + private fun interruptStartTransition(state: TransitionState) { + if (!ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX.isTrue) { + return + } + logV("interruptStartTransition") + state.startTransitionFinishCb?.onTransitionFinished(/* wct= */ null) + state.dragAnimator.cancelAnimator() + state.activeCancelAnimation?.removeAllListeners() + state.activeCancelAnimation?.cancel() + state.activeCancelAnimation = null + // Keep the transition state so we can deal with Cancel/End properly in #startAnimation. + state.startInterrupted = true + dragToDesktopStateListener?.onTransitionInterrupted() + // Cancel CUJs here as they won't be accurate now that an incoming transition is playing. + interactionJankMonitor.cancel(CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD) + interactionJankMonitor.cancel(CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE) + LatencyTracker.getInstance(context) + .onActionCancel(LatencyTracker.ACTION_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG) } protected open fun setupEndDragToDesktop( @@ -781,7 +902,7 @@ sealed class DragToDesktopTransitionHandler( } ?: false } - private fun startCancelAnimation() { + private fun startCancelAnimation(): Animator { val state = requireTransitionState() val dragToDesktopAnimator = state.dragAnimator @@ -798,7 +919,7 @@ sealed class DragToDesktopTransitionHandler( val dx = targetX - x val dy = targetY - y val tx: SurfaceControl.Transaction = transactionSupplier.get() - ValueAnimator.ofFloat(DRAG_FREEFORM_SCALE, 1f) + return ValueAnimator.ofFloat(DRAG_FREEFORM_SCALE, 1f) .setDuration(DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS) .apply { addUpdateListener { animator -> @@ -816,6 +937,7 @@ sealed class DragToDesktopTransitionHandler( addListener( object : AnimatorListenerAdapter() { override fun onAnimationEnd(animation: Animator) { + state.activeCancelAnimation = null dragToDesktopStateListener?.onCancelToDesktopAnimationEnd() // Start the cancel transition to restore order. startCancelDragToDesktopTransition() @@ -908,10 +1030,16 @@ sealed class DragToDesktopTransitionHandler( val dragLayer: Int, ) + /** Listener for various events happening during the DragToDesktop transition. */ interface DragToDesktopStateListener { + /** Indicates that the animation into Desktop has started. */ fun onCommitToDesktopAnimationStart() + /** Called when the animation to cancel the desktop-drag has finished. */ fun onCancelToDesktopAnimationEnd() + + /** Indicates that the drag-to-desktop transition has been interrupted. */ + fun onTransitionInterrupted() } sealed class TransitionState { @@ -928,6 +1056,10 @@ sealed class DragToDesktopTransitionHandler( abstract var cancelState: CancelState abstract var startAborted: Boolean abstract val visualIndicator: DesktopModeVisualIndicator? + abstract var startInterrupted: Boolean + abstract var endTransitionToken: IBinder? + abstract var mergedEndTransition: Boolean + abstract var activeCancelAnimation: Animator? data class FromFullscreen( override val draggedTaskId: Int, @@ -943,6 +1075,10 @@ sealed class DragToDesktopTransitionHandler( override var cancelState: CancelState = CancelState.NO_CANCEL, override var startAborted: Boolean = false, override val visualIndicator: DesktopModeVisualIndicator?, + override var startInterrupted: Boolean = false, + override var endTransitionToken: IBinder? = null, + override var mergedEndTransition: Boolean = false, + override var activeCancelAnimation: Animator? = null, var otherRootChanges: MutableList<Change> = mutableListOf(), ) : TransitionState() @@ -960,6 +1096,10 @@ sealed class DragToDesktopTransitionHandler( override var cancelState: CancelState = CancelState.NO_CANCEL, override var startAborted: Boolean = false, override val visualIndicator: DesktopModeVisualIndicator?, + override var startInterrupted: Boolean = false, + override var endTransitionToken: IBinder? = null, + override var mergedEndTransition: Boolean = false, + override var activeCancelAnimation: Animator? = null, var splitRootChange: Change? = null, var otherSplitTask: Int, ) : TransitionState() diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksOrganizer.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksOrganizer.kt index fc359d7d67b6..5a988fcd1b77 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksOrganizer.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksOrganizer.kt @@ -18,6 +18,8 @@ package com.android.wm.shell.desktopmode.multidesks import android.app.ActivityManager import android.window.TransitionInfo import android.window.WindowContainerTransaction +import com.android.wm.shell.desktopmode.multidesks.DesksOrganizer.OnCreateCallback +import kotlin.coroutines.suspendCoroutine /** An organizer of desk containers in which to host child desktop windows. */ interface DesksOrganizer { @@ -40,6 +42,13 @@ interface DesksOrganizer { task: ActivityManager.RunningTaskInfo, ) + /** Reorders a desk's task to the front. */ + fun reorderTaskToFront( + wct: WindowContainerTransaction, + deskId: Int, + task: ActivityManager.RunningTaskInfo, + ) + /** Minimizes the given task of the given deskId. */ fun minimizeTask( wct: WindowContainerTransaction, @@ -47,6 +56,13 @@ interface DesksOrganizer { task: ActivityManager.RunningTaskInfo, ) + /** Unminimize the given task of the given desk. */ + fun unminimizeTask( + wct: WindowContainerTransaction, + deskId: Int, + task: ActivityManager.RunningTaskInfo, + ) + /** Whether the change is for the given desk id. */ fun isDeskChange(change: TransitionInfo.Change, deskId: Int): Boolean @@ -68,3 +84,9 @@ interface DesksOrganizer { fun onCreated(deskId: Int) } } + +/** Creates a new desk container in the given display. */ +suspend fun DesksOrganizer.createDesk(displayId: Int): Int = suspendCoroutine { cont -> + val onCreateCallback = OnCreateCallback { deskId -> cont.resumeWith(Result.success(deskId)) } + createDesk(displayId, onCreateCallback) +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizer.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizer.kt index f576258ebdaa..49ca58e7b32a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizer.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizer.kt @@ -33,6 +33,7 @@ import androidx.core.util.forEach import com.android.internal.annotations.VisibleForTesting import com.android.internal.protolog.ProtoLog import com.android.wm.shell.ShellTaskOrganizer +import com.android.wm.shell.common.LaunchAdjacentController import com.android.wm.shell.desktopmode.multidesks.DesksOrganizer.OnCreateCallback import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE import com.android.wm.shell.sysui.ShellCommandHandler @@ -44,6 +45,7 @@ class RootTaskDesksOrganizer( shellInit: ShellInit, shellCommandHandler: ShellCommandHandler, private val shellTaskOrganizer: ShellTaskOrganizer, + private val launchAdjacentController: LaunchAdjacentController, ) : DesksOrganizer, ShellTaskOrganizer.TaskListener { private val createDeskRootRequests = mutableListOf<CreateDeskRequest>() @@ -110,7 +112,31 @@ class RootTaskDesksOrganizer( wct.reparent(task.token, root.taskInfo.token, /* onTop= */ true) } + override fun reorderTaskToFront( + wct: WindowContainerTransaction, + deskId: Int, + task: RunningTaskInfo, + ) { + logV("reorderTaskToFront task=${task.taskId} desk=$deskId") + val root = deskRootsByDeskId[deskId] ?: error("Root not found for desk: $deskId") + if (task.taskId in root.children) { + wct.reorder(task.token, /* onTop= */ true, /* includingParents= */ true) + return + } + val minimizationRoot = + checkNotNull(deskMinimizationRootsByDeskId[deskId]) { + "Minimization root not found for desk: $deskId" + } + if (task.taskId in minimizationRoot.children) { + unminimizeTask(wct, deskId, task) + wct.reorder(task.token, /* onTop= */ true, /* includingParents= */ true) + return + } + logE("Attempted to reorder task=${task.taskId} in desk=$deskId but it was not a child") + } + override fun minimizeTask(wct: WindowContainerTransaction, deskId: Int, task: RunningTaskInfo) { + logV("minimizeTask task=${task.taskId} desk=$deskId") val deskRoot = checkNotNull(deskRootsByDeskId[deskId]) { "Root not found for desk: $deskId" } val minimizationRoot = @@ -129,6 +155,30 @@ class RootTaskDesksOrganizer( wct.reparent(task.token, minimizationRoot.token, /* onTop= */ true) } + override fun unminimizeTask( + wct: WindowContainerTransaction, + deskId: Int, + task: RunningTaskInfo, + ) { + val taskId = task.taskId + logV("unminimizeTask task=$taskId desk=$deskId") + val deskRoot = + checkNotNull(deskRootsByDeskId[deskId]) { "Root not found for desk: $deskId" } + val minimizationRoot = + checkNotNull(deskMinimizationRootsByDeskId[deskId]) { + "Minimization root not found for desk: $deskId" + } + if (taskId in deskRoot.children) { + logV("Task #$taskId is already unminimized in desk=$deskId") + return + } + if (taskId !in minimizationRoot.children) { + logE("Attempted to unminimize task=$taskId in desk=$deskId but it was not a child") + return + } + wct.reparent(task.token, deskRoot.token, /* onTop= */ true) + } + override fun isDeskChange(change: TransitionInfo.Change, deskId: Int): Boolean = (isDeskRootChange(change) && change.taskId == deskId) || (getDeskMinimizationRootInChange(change)?.deskId == deskId) @@ -164,6 +214,21 @@ class RootTaskDesksOrganizer( change.mode == TRANSIT_TO_FRONT override fun onTaskAppeared(taskInfo: RunningTaskInfo, leash: SurfaceControl) { + handleTaskAppeared(taskInfo, leash) + updateLaunchAdjacentController() + } + + override fun onTaskInfoChanged(taskInfo: RunningTaskInfo) { + handleTaskInfoChanged(taskInfo) + updateLaunchAdjacentController() + } + + override fun onTaskVanished(taskInfo: RunningTaskInfo) { + handleTaskVanished(taskInfo) + updateLaunchAdjacentController() + } + + private fun handleTaskAppeared(taskInfo: RunningTaskInfo, leash: SurfaceControl) { // Check whether this task is appearing inside a desk. if (taskInfo.parentTaskId in deskRootsByDeskId) { val deskId = taskInfo.parentTaskId @@ -216,7 +281,7 @@ class RootTaskDesksOrganizer( hideMinimizationRoot(deskMinimizationRoot) } - override fun onTaskInfoChanged(taskInfo: RunningTaskInfo) { + private fun handleTaskInfoChanged(taskInfo: RunningTaskInfo) { if (deskRootsByDeskId.contains(taskInfo.taskId)) { val deskId = taskInfo.taskId deskRootsByDeskId[deskId] = deskRootsByDeskId[deskId].copy(taskInfo = taskInfo) @@ -254,7 +319,7 @@ class RootTaskDesksOrganizer( logE("onTaskInfoChanged: unknown task: ${taskInfo.taskId}") } - override fun onTaskVanished(taskInfo: RunningTaskInfo) { + private fun handleTaskVanished(taskInfo: RunningTaskInfo) { if (deskRootsByDeskId.contains(taskInfo.taskId)) { val deskId = taskInfo.taskId val deskRoot = deskRootsByDeskId[deskId] @@ -336,6 +401,18 @@ class RootTaskDesksOrganizer( deskRootsByDeskId.forEach { _, deskRoot -> deskRoot.children -= taskId } } + private fun updateLaunchAdjacentController() { + deskRootsByDeskId.forEach { deskId, root -> + if (root.taskInfo.isVisible) { + // Disable launch adjacent handling if any desk is active, otherwise the split + // launch root and the desk root will both be eligible to take launching tasks. + launchAdjacentController.launchAdjacentEnabled = false + return + } + } + launchAdjacentController.launchAdjacentEnabled = true + } + @VisibleForTesting data class DeskRoot( val deskId: Int, @@ -377,6 +454,9 @@ class RootTaskDesksOrganizer( override fun dump(pw: PrintWriter, prefix: String) { val innerPrefix = "$prefix " pw.println("$prefix$TAG") + pw.println( + "${innerPrefix}launchAdjacentEnabled=" + launchAdjacentController.launchAdjacentEnabled + ) pw.println("${innerPrefix}Desk Roots:") deskRootsByDeskId.forEach { deskId, root -> val minimizationRoot = deskMinimizationRootsByDeskId[deskId] diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepository.kt index 1566544f5303..f71eacab518d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepository.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepository.kt @@ -132,10 +132,7 @@ class DesktopPersistentRepository(private val dataStore: DataStore<DesktopPersis .toBuilder() .putDesktopRepoByUser( userId, - currentRepository - .toBuilder() - .putDesktop(desktopId, desktop.build()) - .build(), + currentRepository.toBuilder().putDesktop(desktopId, desktop.build()).build(), ) .build() } @@ -149,6 +146,33 @@ class DesktopPersistentRepository(private val dataStore: DataStore<DesktopPersis } } + /** Removes the desktop from the persistent repository. */ + suspend fun removeDesktop(userId: Int, desktopId: Int) { + try { + dataStore.updateData { persistentRepositories: DesktopPersistentRepositories -> + val currentRepository = + persistentRepositories.getDesktopRepoByUserOrDefault( + userId, + DesktopRepositoryState.getDefaultInstance(), + ) + persistentRepositories + .toBuilder() + .putDesktopRepoByUser( + userId, + currentRepository.toBuilder().removeDesktop(desktopId).build(), + ) + .build() + } + } catch (throwable: Throwable) { + Log.e( + TAG, + "Error in removing desktop related data, data is " + + "stored in a file named $DESKTOP_REPOSITORIES_DATASTORE_FILE", + throwable, + ) + } + } + suspend fun removeUsers(uids: List<Int>) { try { dataStore.updateData { persistentRepositories: DesktopPersistentRepositories -> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializer.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializer.kt index a26ebbf4c99a..8191181cac11 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializer.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializer.kt @@ -17,8 +17,22 @@ package com.android.wm.shell.desktopmode.persistence import com.android.wm.shell.desktopmode.DesktopUserRepositories +import kotlinx.coroutines.flow.StateFlow /** Interface for initializing the [DesktopUserRepositories]. */ -fun interface DesktopRepositoryInitializer { +interface DesktopRepositoryInitializer { + /** A factory used to recreate a desk from persistence. */ + var deskRecreationFactory: DeskRecreationFactory + + /** A flow that emits true when the repository has been initialized. */ + val isInitialized: StateFlow<Boolean> + + /** Initialize the user repositories from a persistent data store. */ fun initialize(userRepositories: DesktopUserRepositories) + + /** A factory for recreating desks. */ + fun interface DeskRecreationFactory { + /** Recreates a restored desk and returns the new desk id. */ + suspend fun recreateDesk(userId: Int, destinationDisplayId: Int, deskId: Int): Int + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerImpl.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerImpl.kt index 0507e59c06e1..49cb7391fe97 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerImpl.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerImpl.kt @@ -17,13 +17,19 @@ package com.android.wm.shell.desktopmode.persistence import android.content.Context +import android.view.Display import android.window.DesktopExperienceFlags import android.window.DesktopModeFlags +import com.android.internal.protolog.ProtoLog import com.android.wm.shell.desktopmode.DesktopRepository import com.android.wm.shell.desktopmode.DesktopUserRepositories +import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer.DeskRecreationFactory +import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE import com.android.wm.shell.shared.annotations.ShellMainThread import com.android.wm.shell.shared.desktopmode.DesktopModeStatus import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch /** @@ -37,62 +43,136 @@ class DesktopRepositoryInitializerImpl( private val persistentRepository: DesktopPersistentRepository, @ShellMainThread private val mainCoroutineScope: CoroutineScope, ) : DesktopRepositoryInitializer { + + override var deskRecreationFactory: DeskRecreationFactory = DefaultDeskRecreationFactory() + + private val _isInitialized = MutableStateFlow(false) + override val isInitialized: StateFlow<Boolean> = _isInitialized + override fun initialize(userRepositories: DesktopUserRepositories) { - if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) return + if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue) { + _isInitialized.value = true + return + } // TODO: b/365962554 - Handle the case that user moves to desktop before it's initialized mainCoroutineScope.launch { - val desktopUserPersistentRepositoryMap = - persistentRepository.getUserDesktopRepositoryMap() ?: return@launch - for (userId in desktopUserPersistentRepositoryMap.keys) { - val repository = userRepositories.getProfile(userId) - val desktopRepositoryState = - persistentRepository.getDesktopRepositoryState(userId) ?: continue - val desktopByDesktopIdMap = desktopRepositoryState.desktopMap - for (desktopId in desktopByDesktopIdMap.keys) { - val persistentDesktop = - persistentRepository.readDesktop(userId, desktopId) ?: continue - val maxTasks = - DesktopModeStatus.getMaxTaskLimit(context).takeIf { it > 0 } - ?: persistentDesktop.zOrderedTasksCount - var visibleTasksCount = 0 - repository.addDesk( - displayId = persistentDesktop.displayId, - deskId = - if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) { - persistentDesktop.desktopId - } else { - // When disabled, desk ids are always the display id. - persistentDesktop.displayId - }, + try { + val desktopUserPersistentRepositoryMap = + persistentRepository.getUserDesktopRepositoryMap() ?: return@launch + for (userId in desktopUserPersistentRepositoryMap.keys) { + val repository = userRepositories.getProfile(userId) + val desktopRepositoryState = + persistentRepository.getDesktopRepositoryState(userId) ?: continue + val desksToRestore = getDesksToRestore(desktopRepositoryState, userId) + logV( + "initialize() will restore desks=%s user=%d", + desksToRestore.map { it.desktopId }, + userId, ) - persistentDesktop.zOrderedTasksList - // Reverse it so we initialize the repo from bottom to top. - .reversed() - .mapNotNull { taskId -> persistentDesktop.tasksByTaskIdMap[taskId] } - // TODO: b/362720497 - add tasks to their respective desk when multi-desk - // persistence is implemented. - .forEach { task -> - if ( - task.desktopTaskState == DesktopTaskState.VISIBLE && - visibleTasksCount < maxTasks - ) { - visibleTasksCount++ - repository.addTask( - persistentDesktop.displayId, - task.taskId, - isVisible = false, - ) - } else { - repository.addTask( - persistentDesktop.displayId, - task.taskId, + desksToRestore.forEach { persistentDesktop -> + val maxTasks = getTaskLimit(persistentDesktop) + val displayId = persistentDesktop.displayId + val deskId = persistentDesktop.desktopId + // TODO: b/401107440 - Implement desk restoration to other displays. + val newDisplayId = Display.DEFAULT_DISPLAY + val newDeskId = + deskRecreationFactory.recreateDesk( + userId = userId, + destinationDisplayId = newDisplayId, + deskId = deskId, + ) + logV( + "Recreated desk=%d in display=%d using new deskId=%d and displayId=%d", + deskId, + displayId, + newDeskId, + newDisplayId, + ) + if (newDeskId != deskId || newDisplayId != displayId) { + logV("Removing obsolete desk from persistence under deskId=%d", deskId) + persistentRepository.removeDesktop(userId, deskId) + } + + // TODO: b/393961770 - [DesktopRepository] doesn't save desks to the + // persistent repository until a task is added to them. Update it so that + // empty desks can be restored too. + repository.addDesk(displayId = displayId, deskId = newDeskId) + var visibleTasksCount = 0 + persistentDesktop.zOrderedTasksList + // Reverse it so we initialize the repo from bottom to top. + .reversed() + .mapNotNull { taskId -> persistentDesktop.tasksByTaskIdMap[taskId] } + .forEach { task -> + // Visible here means non-minimized a.k.a. expanded, it does not + // mean + // it is visible in WM (and |DesktopRepository|) terms. + val isVisible = + task.desktopTaskState == DesktopTaskState.VISIBLE && + visibleTasksCount < maxTasks + + repository.addTaskToDesk( + displayId = displayId, + deskId = newDeskId, + taskId = task.taskId, isVisible = false, ) - repository.minimizeTask(persistentDesktop.displayId, task.taskId) + + if (isVisible) { + visibleTasksCount++ + } else { + repository.minimizeTaskInDesk( + displayId = displayId, + deskId = newDeskId, + taskId = task.taskId, + ) + } } - } + } } + } finally { + _isInitialized.value = true } } } + + private suspend fun getDesksToRestore( + state: DesktopRepositoryState, + userId: Int, + ): Set<Desktop> { + // TODO: b/365873835 - what about desks that won't be restored? + // - invalid desk ids from multi-desk -> single-desk switching can be ignored / deleted. + val limitToSingleDeskPerDisplay = + !DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue + return state.desktopMap.keys + .mapNotNull { deskId -> + persistentRepository.readDesktop(userId, deskId)?.takeIf { desk -> + // Do not restore invalid desks when multi-desks is disabled. This is + // possible if the feature is disabled after having created multiple desks. + val isValidSingleDesk = desk.desktopId == desk.displayId + (!limitToSingleDeskPerDisplay || isValidSingleDesk) + } + } + .toSet() + } + + private fun getTaskLimit(persistedDesk: Desktop): Int = + DesktopModeStatus.getMaxTaskLimit(context).takeIf { it > 0 } + ?: persistedDesk.zOrderedTasksCount + + private fun logV(msg: String, vararg arguments: Any?) { + ProtoLog.v(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments) + } + + /** A default implementation of [DeskRecreationFactory] that reuses the desk id. */ + private class DefaultDeskRecreationFactory : DeskRecreationFactory { + override suspend fun recreateDesk( + userId: Int, + destinationDisplayId: Int, + deskId: Int, + ): Int = deskId + } + + companion object { + private const val TAG = "DesktopRepositoryInitializerImpl" + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java index 897e2d1601a5..2fe786531af5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java @@ -24,6 +24,7 @@ import android.app.ActivityManager.RunningTaskInfo; import android.content.Context; import android.util.SparseArray; import android.view.SurfaceControl; +import android.window.DesktopExperienceFlags; import android.window.DesktopModeFlags; import com.android.internal.protolog.ProtoLog; @@ -167,6 +168,11 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener, } private void updateLaunchAdjacentController() { + if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue()) { + // With multiple desks, freeform tasks are children of a root task controlled by + // DesksOrganizer, so toggling launch-adjacent should be managed there. + return; + } for (int i = 0; i < mTasks.size(); i++) { if (mTasks.valueAt(i).mTaskInfo.isVisible) { mLaunchAdjacentController.setLaunchAdjacentEnabled(false); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java index 8059b94685ba..f89ba0a168d1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java @@ -29,6 +29,7 @@ import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import com.android.wm.shell.desktopmode.DesktopImmersiveController; +import com.android.wm.shell.desktopmode.multidesks.DesksTransitionObserver; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.FocusTransitionObserver; import com.android.wm.shell.transition.Transitions; @@ -52,6 +53,7 @@ public class FreeformTaskTransitionObserver implements Transitions.TransitionObs private final WindowDecorViewModel mWindowDecorViewModel; private final Optional<TaskChangeListener> mTaskChangeListener; private final FocusTransitionObserver mFocusTransitionObserver; + private final Optional<DesksTransitionObserver> mDesksTransitionObserver; private final Map<IBinder, List<ActivityManager.RunningTaskInfo>> mTransitionToTaskInfo = new HashMap<>(); @@ -63,12 +65,14 @@ public class FreeformTaskTransitionObserver implements Transitions.TransitionObs Optional<DesktopImmersiveController> desktopImmersiveController, WindowDecorViewModel windowDecorViewModel, Optional<TaskChangeListener> taskChangeListener, - FocusTransitionObserver focusTransitionObserver) { + FocusTransitionObserver focusTransitionObserver, + Optional<DesksTransitionObserver> desksTransitionObserver) { mTransitions = transitions; mDesktopImmersiveController = desktopImmersiveController; mWindowDecorViewModel = windowDecorViewModel; mTaskChangeListener = taskChangeListener; mFocusTransitionObserver = focusTransitionObserver; + mDesksTransitionObserver = desksTransitionObserver; if (FreeformComponents.requiresFreeformComponents(context)) { shellInit.addInitCallback(this::onInit, this); } @@ -85,6 +89,10 @@ public class FreeformTaskTransitionObserver implements Transitions.TransitionObs @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startT, @NonNull SurfaceControl.Transaction finishT) { + // Update desk state first, otherwise [TaskChangeListener] may update desktop task state + // under an outdated active desk if a desk switch and a task update happen in the same + // transition, such as when unminimizing a task from an inactive desk. + mDesksTransitionObserver.ifPresent(o -> o.onTransitionReady(transition, info)); if (DesktopModeFlags.ENABLE_FULLY_IMMERSIVE_IN_DESKTOP.isTrue()) { // TODO(b/367268953): Remove when DesktopTaskListener is introduced and the repository // is updated from there **before** the |mWindowDecorViewModel| methods are invoked. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java index 65099c2dfb9d..671eae3d84ef 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java @@ -153,7 +153,12 @@ public class PhonePipMenuController implements PipMenuController, mPipUiEventLogger = pipUiEventLogger; mPipTransitionState.addPipTransitionStateChangedListener(this); - + // Clear actions after exit PiP. Otherwise, next PiP could accidentally inherit the + // actions provided by the previous app in PiP mode. + mPipBoundsState.addOnPipComponentChangedListener(((oldPipComponent, newPipComponent) -> { + if (mAppActions != null) mAppActions.clear(); + mCloseAction = null; + })); mPipTaskListener.addParamsChangedListener(new PipTaskListener.PipParamsChangedCallback() { @Override public void onActionsChanged(List<RemoteAction> actions, RemoteAction closeAction) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java index 383afcf6f821..f81f330e50c4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java @@ -20,6 +20,7 @@ import android.app.PictureInPictureParams; import android.content.Context; import android.graphics.Matrix; import android.graphics.Rect; +import android.os.Bundle; import android.os.SystemProperties; import android.view.SurfaceControl; import android.window.WindowContainerToken; @@ -47,7 +48,7 @@ import java.util.function.Supplier; /** * Scheduler for Shell initiated PiP transitions and animations. */ -public class PipScheduler { +public class PipScheduler implements PipTransitionState.PipTransitionStateChangedListener { private static final String TAG = PipScheduler.class.getSimpleName(); /** @@ -71,6 +72,7 @@ public class PipScheduler { private final PipSurfaceTransactionHelper mPipSurfaceTransactionHelper; @Nullable private Runnable mUpdateMovementBoundsRunnable; + @Nullable private PipAlphaAnimator mOverlayFadeoutAnimator; private PipAlphaAnimatorSupplier mPipAlphaAnimatorSupplier; private Supplier<PictureInPictureParams> mPipParamsSupplier; @@ -85,6 +87,7 @@ public class PipScheduler { mPipBoundsState = pipBoundsState; mMainExecutor = mainExecutor; mPipTransitionState = pipTransitionState; + mPipTransitionState.addPipTransitionStateChangedListener(this); mPipDesktopState = pipDesktopState; mSplitScreenControllerOptional = splitScreenControllerOptional; @@ -238,12 +241,16 @@ public class PipScheduler { void startOverlayFadeoutAnimation(@NonNull SurfaceControl overlayLeash, boolean withStartDelay, @NonNull Runnable onAnimationEnd) { - PipAlphaAnimator animator = mPipAlphaAnimatorSupplier.get(mContext, overlayLeash, + mOverlayFadeoutAnimator = mPipAlphaAnimatorSupplier.get(mContext, overlayLeash, null /* startTx */, null /* finishTx */, PipAlphaAnimator.FADE_OUT); - animator.setDuration(CONTENT_OVERLAY_FADE_OUT_DURATION_MS); - animator.setStartDelay(withStartDelay ? EXTRA_CONTENT_OVERLAY_FADE_OUT_DELAY_MS : 0); - animator.setAnimationEndCallback(onAnimationEnd); - animator.start(); + mOverlayFadeoutAnimator.setDuration(CONTENT_OVERLAY_FADE_OUT_DURATION_MS); + mOverlayFadeoutAnimator.setStartDelay(withStartDelay + ? EXTRA_CONTENT_OVERLAY_FADE_OUT_DELAY_MS : 0); + mOverlayFadeoutAnimator.setAnimationEndCallback(() -> { + onAnimationEnd.run(); + mOverlayFadeoutAnimator = null; + }); + mOverlayFadeoutAnimator.start(); } void setUpdateMovementBoundsRunnable(@Nullable Runnable updateMovementBoundsRunnable) { @@ -289,6 +296,21 @@ public class PipScheduler { mSurfaceControlTransactionFactory = factory; } + @Override + public void onPipTransitionStateChanged(@PipTransitionState.TransitionState int oldState, + @PipTransitionState.TransitionState int newState, + @android.annotation.Nullable Bundle extra) { + switch (newState) { + case PipTransitionState.EXITING_PIP: + case PipTransitionState.SCHEDULED_BOUNDS_CHANGE: + if (mOverlayFadeoutAnimator != null && mOverlayFadeoutAnimator.isStarted()) { + mOverlayFadeoutAnimator.end(); + mOverlayFadeoutAnimator = null; + } + break; + } + } + @VisibleForTesting interface PipAlphaAnimatorSupplier { PipAlphaAnimator get(@NonNull Context context, @@ -303,6 +325,17 @@ public class PipScheduler { mPipAlphaAnimatorSupplier = supplier; } + @VisibleForTesting + void setOverlayFadeoutAnimator(@NonNull PipAlphaAnimator animator) { + mOverlayFadeoutAnimator = animator; + } + + @VisibleForTesting + @Nullable + PipAlphaAnimator getOverlayFadeoutAnimator() { + return mOverlayFadeoutAnimator; + } + void setPipParamsSupplier(@NonNull Supplier<PictureInPictureParams> pipParamsSupplier) { mPipParamsSupplier = pipParamsSupplier; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java index d6634845ee21..294ef48c01d0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java @@ -61,7 +61,7 @@ public class PipTaskListener implements ShellTaskOrganizer.TaskListener, private final PipBoundsState mPipBoundsState; private final PipBoundsAlgorithm mPipBoundsAlgorithm; private final ShellExecutor mMainExecutor; - private final PictureInPictureParams mPictureInPictureParams = + private PictureInPictureParams mPictureInPictureParams = new PictureInPictureParams.Builder().build(); private boolean mWaitingForAspectRatioChange = false; @@ -92,6 +92,11 @@ public class PipTaskListener implements ShellTaskOrganizer.TaskListener, } mPipResizeAnimatorSupplier = PipResizeAnimator::new; mPipScheduler.setPipParamsSupplier(this::getPictureInPictureParams); + // Reset {@link #mPictureInPictureParams} after exiting PiP. For instance, next Activity + // with null aspect ratio would accidentally inherit the aspect ratio from a previous + // PiP Activity. + mPipBoundsState.addOnPipComponentChangedListener(((oldPipComponent, newPipComponent) -> + mPictureInPictureParams = new PictureInPictureParams.Builder().build())); } void setPictureInPictureParams(@Nullable PictureInPictureParams params) { @@ -138,9 +143,8 @@ public class PipTaskListener implements ShellTaskOrganizer.TaskListener, if (mPictureInPictureParams.hasSetAspectRatio() && mPipBoundsAlgorithm.isValidPictureInPictureAspectRatio(newAspectRatio) && PipUtils.aspectRatioChanged(newAspectRatio, mPipBoundsState.getAspectRatio())) { - mPipTransitionState.setOnIdlePipTransitionStateRunnable(() -> { - onAspectRatioChanged(newAspectRatio); - }); + mPipTransitionState.setOnIdlePipTransitionStateRunnable( + () -> onAspectRatioChanged(newAspectRatio)); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 7472b0ea56ca..c370c0cb0930 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -2920,7 +2920,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, prepareEnterSplitScreen(out); mSplitTransitions.setEnterTransition(transition, request.getRemoteTransition(), TRANSIT_SPLIT_SCREEN_PAIR_OPEN, !mIsDropEntering, SNAP_TO_2_50_50); - } else if (isSplitScreenVisible() && isOpening) { + } else if (enableFlexibleTwoAppSplit() && isSplitScreenVisible() && isOpening) { // launching into an existing split stage; possibly launchAdjacent // If we're replacing a pip-able app, we need to let mixed handler take care of // it. Otherwise we'll just treat it as an enter+resize diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHandleManageWindowsMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHandleManageWindowsMenu.kt index 01fc6440712d..adc5cdf340fd 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHandleManageWindowsMenu.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHandleManageWindowsMenu.kt @@ -43,7 +43,7 @@ class DesktopHandleManageWindowsMenu( private val captionWidth: Int, private val windowManagerWrapper: WindowManagerWrapper, context: Context, - snapshotList: List<Pair<Int, TaskSnapshot>>, + snapshotList: List<Pair<Int, TaskSnapshot?>>, onIconClickListener: ((Int) -> Unit), onOutsideClickListener: (() -> Unit) ) : ManageWindowsViewContainer( diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenu.kt index 02a5433147ca..3a75933f59d7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenu.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenu.kt @@ -54,7 +54,7 @@ class DesktopHeaderManageWindowsMenu( private val desktopUserRepositories: DesktopUserRepositories, private val surfaceControlBuilderSupplier: Supplier<SurfaceControl.Builder>, private val surfaceControlTransactionSupplier: Supplier<SurfaceControl.Transaction>, - snapshotList: List<Pair<Int, TaskSnapshot>>, + snapshotList: List<Pair<Int, TaskSnapshot?>>, onIconClickListener: ((Int) -> Unit), onOutsideClickListener: (() -> Unit) ) : ManageWindowsViewContainer( diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index 7ef1a93cbe45..a1d2774ee428 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -17,11 +17,9 @@ package com.android.wm.shell.windowdecor; import static android.app.ActivityTaskManager.INVALID_TASK_ID; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; -import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.view.InputDevice.SOURCE_TOUCHSCREEN; import static android.view.MotionEvent.ACTION_CANCEL; import static android.view.MotionEvent.ACTION_HOVER_ENTER; @@ -79,7 +77,6 @@ import android.view.SurfaceControl.Transaction; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewRootImpl; -import android.view.WindowManager; import android.window.DesktopModeFlags; import android.window.TaskSnapshot; import android.window.WindowContainerToken; @@ -121,7 +118,6 @@ import com.android.wm.shell.desktopmode.DesktopTasksController; import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition; import com.android.wm.shell.desktopmode.DesktopTasksLimiter; import com.android.wm.shell.desktopmode.DesktopUserRepositories; -import com.android.wm.shell.desktopmode.DesktopWallpaperActivity; import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository; import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction; import com.android.wm.shell.desktopmode.common.ToggleTaskSizeUtilsKt; @@ -146,6 +142,7 @@ import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.FocusTransitionObserver; import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration.ExclusionRegionListener; +import com.android.wm.shell.windowdecor.common.AppHandleAndHeaderVisibilityHelper; import com.android.wm.shell.windowdecor.common.WindowDecorTaskResourceLoader; import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost; import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier; @@ -207,6 +204,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, private final Optional<DesktopTasksLimiter> mDesktopTasksLimiter; private final AppHandleEducationController mAppHandleEducationController; private final AppToWebEducationController mAppToWebEducationController; + private final AppHandleAndHeaderVisibilityHelper mAppHandleAndHeaderVisibilityHelper; private final AppHeaderViewHolder.Factory mAppHeaderViewHolderFactory; private boolean mTransitionDragActive; @@ -294,6 +292,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, Optional<DesktopTasksLimiter> desktopTasksLimiter, AppHandleEducationController appHandleEducationController, AppToWebEducationController appToWebEducationController, + AppHandleAndHeaderVisibilityHelper appHandleAndHeaderVisibilityHelper, WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository, Optional<DesktopActivityOrientationChangeHandler> activityOrientationChangeHandler, FocusTransitionObserver focusTransitionObserver, @@ -338,6 +337,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, desktopTasksLimiter, appHandleEducationController, appToWebEducationController, + appHandleAndHeaderVisibilityHelper, windowDecorCaptionHandleRepository, activityOrientationChangeHandler, new TaskPositionerFactory(), @@ -386,6 +386,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, Optional<DesktopTasksLimiter> desktopTasksLimiter, AppHandleEducationController appHandleEducationController, AppToWebEducationController appToWebEducationController, + AppHandleAndHeaderVisibilityHelper appHandleAndHeaderVisibilityHelper, WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository, Optional<DesktopActivityOrientationChangeHandler> activityOrientationChangeHandler, TaskPositionerFactory taskPositionerFactory, @@ -431,6 +432,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, mDesktopTasksLimiter = desktopTasksLimiter; mAppHandleEducationController = appHandleEducationController; mAppToWebEducationController = appToWebEducationController; + mAppHandleAndHeaderVisibilityHelper = appHandleAndHeaderVisibilityHelper; mWindowDecorCaptionHandleRepository = windowDecorCaptionHandleRepository; mActivityOrientationChangeHandler = activityOrientationChangeHandler; mAssistContentRequester = assistContentRequester; @@ -528,6 +530,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, @Override public void setSplitScreenController(SplitScreenController splitScreenController) { mSplitScreenController = splitScreenController; + mAppHandleAndHeaderVisibilityHelper.setSplitScreenController(splitScreenController); } @Override @@ -1215,7 +1218,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, if (id == R.id.caption_handle) { handleCaptionThroughStatusBar(e, decoration); final boolean wasDragging = mIsDragging; - updateDragStatus(e.getActionMasked()); + updateDragStatus(decoration, e); final boolean upOrCancel = e.getActionMasked() == ACTION_UP || e.getActionMasked() == ACTION_CANCEL; if (wasDragging && upOrCancel) { @@ -1231,6 +1234,13 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, return false; } + private void setIsDragging( + @Nullable DesktopModeWindowDecoration decor, boolean isDragging) { + mIsDragging = isDragging; + if (decor == null) return; + decor.setIsDragging(isDragging); + } + private boolean handleFreeformMotionEvent(DesktopModeWindowDecoration decoration, RunningTaskInfo taskInfo, View v, MotionEvent e) { final int id = v.getId(); @@ -1250,7 +1260,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, final Rect initialBounds = mDragPositioningCallback.onDragPositioningStart( 0 /* ctrlType */, e.getDisplayId(), e.getRawX(0), e.getRawY(0)); - updateDragStatus(e.getActionMasked()); + updateDragStatus(decoration, e); mOnDragStartInitialBounds.set(initialBounds); } // Do not consume input event if a button is touched, otherwise it would @@ -1277,7 +1287,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, newTaskBounds); // Flip mIsDragging only if the bounds actually changed. if (mIsDragging || !newTaskBounds.equals(mOnDragStartInitialBounds)) { - updateDragStatus(e.getActionMasked()); + updateDragStatus(decoration, e); } return true; } @@ -1310,7 +1320,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, // onClick call that results. return false; } else { - updateDragStatus(e.getActionMasked()); + updateDragStatus(decoration, e); return true; } } @@ -1318,16 +1328,16 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, return true; } - private void updateDragStatus(int eventAction) { - switch (eventAction) { + private void updateDragStatus(DesktopModeWindowDecoration decor, MotionEvent e) { + switch (e.getActionMasked()) { case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: { - mIsDragging = false; + setIsDragging(decor, false /* isDragging */); break; } case MotionEvent.ACTION_MOVE: { - mIsDragging = true; + setIsDragging(decor, true /* isDragging */); break; } } @@ -1717,32 +1727,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, } private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) { - if (mDisplayController.getDisplay(taskInfo.displayId) == null) { - // If DisplayController doesn't have it tracked, it could be a private/managed display. - return false; - } - if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) return true; - if (mSplitScreenController != null - && mSplitScreenController.isTaskRootOrStageRoot(taskInfo.taskId)) { - return false; - } - if (mDesktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(taskInfo)) { - return false; - } - final boolean isOnLargeScreen = - mDisplayController.getDisplay(taskInfo.displayId).getMinSizeDimensionDp() - >= WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP; - if (!DesktopModeStatus.canEnterDesktopMode(mContext) - && DesktopModeStatus.overridesShowAppHandle(mContext) && !isOnLargeScreen) { - // Devices with multiple screens may enable the app handle but it should not show on - // small screens - return false; - } - return DesktopModeStatus.canEnterDesktopModeOrShowAppHandle(mContext) - && !DesktopWallpaperActivity.isWallpaperTask(taskInfo) - && taskInfo.getWindowingMode() != WINDOWING_MODE_PINNED - && taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD - && !taskInfo.configuration.windowConfiguration.isAlwaysOnTop(); + return mAppHandleAndHeaderVisibilityHelper.shouldShowAppHandleOrHeader(taskInfo); } private void createWindowDecoration( diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java index 2a5315739396..673c3f66cb6c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java @@ -29,6 +29,7 @@ import static android.window.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_ import static com.android.wm.shell.shared.desktopmode.DesktopModeStatus.canEnterDesktopMode; import static com.android.wm.shell.shared.desktopmode.DesktopModeStatus.canEnterDesktopModeOrShowAppHandle; +import static com.android.wm.shell.shared.desktopmode.DesktopModeStatus.isDesktopModeSupportedOnDisplay; import static com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON; import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; import static com.android.wm.shell.windowdecor.DragPositioningCallbackUtility.DragEventListener; @@ -207,7 +208,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin private final WindowDecorCaptionHandleRepository mWindowDecorCaptionHandleRepository; private final DesktopUserRepositories mDesktopUserRepositories; private boolean mIsRecentsTransitionRunning = false; - + private boolean mIsDragging = false; private Runnable mLoadAppInfoRunnable; private Runnable mSetAppInfoRunnable; @@ -513,7 +514,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin updateRelayoutParams(mRelayoutParams, mContext, taskInfo, mSplitScreenController, applyStartTransactionOnDraw, shouldSetTaskVisibilityPositionAndCrop, mIsStatusBarVisible, mIsKeyguardVisibleAndOccluded, inFullImmersive, - mDisplayController.getInsetsState(taskInfo.displayId), hasGlobalFocus, + mIsDragging, mDisplayController.getInsetsState(taskInfo.displayId), hasGlobalFocus, displayExclusionRegion, mIsRecentsTransitionRunning, mDesktopModeCompatPolicy.shouldExcludeCaptionFromAppBounds(taskInfo)); @@ -911,6 +912,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin boolean isStatusBarVisible, boolean isKeyguardVisibleAndOccluded, boolean inFullImmersiveMode, + boolean isDragging, @NonNull InsetsState displayInsetsState, boolean hasGlobalFocus, @NonNull Region displayExclusionRegion, @@ -933,9 +935,13 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin relayoutParams.mAsyncViewHost = isAppHandle; final boolean showCaption; - if (DesktopModeFlags.ENABLE_FULLY_IMMERSIVE_IN_DESKTOP.isTrue()) { + if (DesktopModeFlags.ENABLE_DESKTOP_IMMERSIVE_DRAG_BUGFIX.isTrue() && isDragging) { + // If the task is being dragged, the caption should not be hidden so that it continues + // receiving input + showCaption = true; + } else if (DesktopModeFlags.ENABLE_FULLY_IMMERSIVE_IN_DESKTOP.isTrue()) { if (inFullImmersiveMode) { - showCaption = isStatusBarVisible && !isKeyguardVisibleAndOccluded; + showCaption = (isStatusBarVisible && !isKeyguardVisibleAndOccluded); } else { showCaption = taskInfo.isFreeform() || (isStatusBarVisible && !isKeyguardVisibleAndOccluded); @@ -1405,7 +1411,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin supportsMultiInstance, shouldShowManageWindowsButton, shouldShowChangeAspectRatioButton, - canEnterDesktopMode(mContext), + isDesktopModeSupportedOnDisplay(mContext, mDisplay), isBrowserApp, isBrowserApp ? getAppLink() : getBrowserLink(), mResult.mCaptionWidth, @@ -1799,6 +1805,13 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin } /** + * Declares whether the window decoration is being dragged. + */ + void setIsDragging(boolean isDragging) { + mIsDragging = isDragging; + } + + /** * Called when there is a {@link MotionEvent#ACTION_HOVER_EXIT} on the maximize window button. */ void onMaximizeButtonHoverExit() { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt index ce786177290f..c544468f5191 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt @@ -39,7 +39,6 @@ import android.widget.ImageButton import android.widget.ImageView import android.widget.LinearLayout import android.widget.Space -import android.widget.TextView import android.window.DesktopModeFlags import android.window.SurfaceSyncGroup import androidx.annotation.StringRes @@ -59,10 +58,10 @@ import com.android.wm.shell.splitscreen.SplitScreenController import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewContainer import com.android.wm.shell.windowdecor.common.DecorThemeUtil +import com.android.wm.shell.windowdecor.common.DrawableInsets import com.android.wm.shell.windowdecor.common.WindowDecorTaskResourceLoader import com.android.wm.shell.windowdecor.common.calculateMenuPosition -import com.android.wm.shell.windowdecor.common.DrawableInsets -import com.android.wm.shell.windowdecor.common.createRippleDrawable +import com.android.wm.shell.windowdecor.common.createBackgroundDrawable import com.android.wm.shell.windowdecor.extension.isFullscreen import com.android.wm.shell.windowdecor.extension.isMultiWindow import com.android.wm.shell.windowdecor.extension.isPinned @@ -478,13 +477,13 @@ class HandleMenu( R.dimen.desktop_mode_handle_menu_icon_button_ripple_inset_base) private val iconButtonRippleRadius = context.resources.getDimensionPixelSize( R.dimen.desktop_mode_handle_menu_icon_button_ripple_radius) - private val iconButtonDrawableInsetsBase = DrawableInsets(t=iconButtondrawableBaseInset, - b=iconButtondrawableBaseInset, l=iconButtondrawableBaseInset, - r=iconButtondrawableBaseInset) - private val iconButtonDrawableInsetsLeft = DrawableInsets(t=iconButtondrawableBaseInset, - b=iconButtondrawableBaseInset, l=iconButtondrawableShiftInset, r=0) - private val iconButtonDrawableInsetsRight = DrawableInsets(t=iconButtondrawableBaseInset, - b=iconButtondrawableBaseInset, l=0, r=iconButtondrawableShiftInset) + private val iconButtonDrawableInsetsBase = DrawableInsets(t = iconButtondrawableBaseInset, + b = iconButtondrawableBaseInset, l = iconButtondrawableBaseInset, + r = iconButtondrawableBaseInset) + private val iconButtonDrawableInsetsLeft = DrawableInsets(t = iconButtondrawableBaseInset, + b = iconButtondrawableBaseInset, l = iconButtondrawableShiftInset, r = 0) + private val iconButtonDrawableInsetsRight = DrawableInsets(t = iconButtondrawableBaseInset, + b = iconButtondrawableBaseInset, l = 0, r = iconButtondrawableShiftInset) // App Info Pill. private val appInfoPill = rootView.requireViewById<View>(R.id.app_info_pill) @@ -702,7 +701,7 @@ class HandleMenu( imageTintList = ColorStateList.valueOf(style.textColor) this.taskInfo = this@HandleMenuView.taskInfo - background = createRippleDrawable( + background = createBackgroundDrawable( color = style.textColor, cornerRadius = iconButtonRippleRadius, drawableInsets = iconButtonDrawableInsetsBase @@ -740,7 +739,7 @@ class HandleMenu( else iconButtonDrawableInsetsRight fullscreenBtn.apply { - background = createRippleDrawable( + background = createBackgroundDrawable( color = style.textColor, cornerRadius = iconButtonRippleRadius, drawableInsets = startInsets @@ -748,7 +747,7 @@ class HandleMenu( } splitscreenBtn.apply { - background = createRippleDrawable( + background = createBackgroundDrawable( color = style.textColor, cornerRadius = iconButtonRippleRadius, drawableInsets = iconButtonDrawableInsetsBase @@ -756,7 +755,7 @@ class HandleMenu( } floatingBtn.apply { - background = createRippleDrawable( + background = createBackgroundDrawable( color = style.textColor, cornerRadius = iconButtonRippleRadius, drawableInsets = iconButtonDrawableInsetsBase @@ -764,7 +763,7 @@ class HandleMenu( } desktopBtn.apply { - background = createRippleDrawable( + background = createBackgroundDrawable( color = style.textColor, cornerRadius = iconButtonRippleRadius, drawableInsets = endInsets diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt index 22bc9782170b..cf536eba8382 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt @@ -42,8 +42,6 @@ class MoveToDesktopAnimator @JvmOverloads constructor( .setDuration(ANIMATION_DURATION.toLong()) .apply { val t = SurfaceControl.Transaction() - val cornerRadius = context.resources - .getDimensionPixelSize(R.dimen.desktop_mode_dragged_task_radius).toFloat() addUpdateListener { setTaskPosition(mostRecentInput.x, mostRecentInput.y) t.setScale(taskSurface, scale, scale) @@ -57,6 +55,8 @@ class MoveToDesktopAnimator @JvmOverloads constructor( val taskId get() = taskInfo.taskId val position: PointF = PointF(0.0f, 0.0f) + val cornerRadius: Float = context.resources + .getDimensionPixelSize(R.dimen.desktop_mode_dragged_task_radius).toFloat() /** * Whether motion events from the drag gesture should affect the dragged surface or not. Used diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java index 7baef2b2dc97..bde46a1bc375 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java @@ -361,6 +361,9 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> outResult.mRootView = rootView; final boolean fontScaleChanged = mWindowDecorConfig != null && mWindowDecorConfig.fontScale != mTaskInfo.configuration.fontScale; + final boolean localeListChanged = mWindowDecorConfig != null + && !mWindowDecorConfig.getLocales() + .equals(mTaskInfo.getConfiguration().getLocales()); final int oldDensityDpi = mWindowDecorConfig != null ? mWindowDecorConfig.densityDpi : DENSITY_DPI_UNDEFINED; final int oldNightMode = mWindowDecorConfig != null @@ -376,7 +379,8 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> || oldLayoutResId != mLayoutResId || oldNightMode != newNightMode || mDecorWindowContext == null - || fontScaleChanged) { + || fontScaleChanged + || localeListChanged) { releaseViews(wct); if (!obtainDisplayOrRegisterListener()) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/AppHandleAndHeaderVisibilityHelper.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/AppHandleAndHeaderVisibilityHelper.kt new file mode 100644 index 000000000000..39ccf5bd03a7 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/AppHandleAndHeaderVisibilityHelper.kt @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.windowdecor.common + +import android.app.ActivityManager +import android.app.WindowConfiguration +import android.content.Context +import android.view.WindowManager +import android.window.DesktopExperienceFlags.ENABLE_BUG_FIXES_FOR_SECONDARY_DISPLAY +import com.android.wm.shell.common.DisplayController +import com.android.wm.shell.desktopmode.DesktopWallpaperActivity.Companion.isWallpaperTask +import com.android.wm.shell.shared.desktopmode.DesktopModeCompatPolicy +import com.android.wm.shell.shared.desktopmode.DesktopModeStatus +import com.android.wm.shell.splitscreen.SplitScreenController + +/** + * Resolves whether, given a task and its associated display that it is currently on, to show the + * app handle/header or not. + */ +class AppHandleAndHeaderVisibilityHelper ( + private val context: Context, + private val displayController: DisplayController, + private val desktopModeCompatPolicy: DesktopModeCompatPolicy +) { + var splitScreenController: SplitScreenController? = null + + /** + * Returns, given a task's attribute and its display attribute, whether the app + * handle/header should show or not for this task. + */ + fun shouldShowAppHandleOrHeader(taskInfo: ActivityManager.RunningTaskInfo): Boolean { + if (!ENABLE_BUG_FIXES_FOR_SECONDARY_DISPLAY.isTrue) { + return allowedForTask(taskInfo) + } + return allowedForTask(taskInfo) && allowedForDisplay(taskInfo.displayId) + } + + private fun allowedForTask(taskInfo: ActivityManager.RunningTaskInfo): Boolean { + // TODO (b/382023296): Remove once we no longer rely on + // Flags.enableBugFixesForSecondaryDisplay as it is taken care of in #allowedForDisplay + if (displayController.getDisplay(taskInfo.displayId) == null) { + // If DisplayController doesn't have it tracked, it could be a private/managed display. + return false + } + if (taskInfo.windowingMode == WindowConfiguration.WINDOWING_MODE_FREEFORM) return true + if (splitScreenController?.isTaskRootOrStageRoot(taskInfo.taskId) == true) { + return false + } + + if (desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(taskInfo)) { + return false + } + + // TODO (b/382023296): Remove once we no longer rely on + // Flags.enableBugFixesForSecondaryDisplay as it is taken care of in #allowedForDisplay + val isOnLargeScreen = + displayController.getDisplay(taskInfo.displayId).minSizeDimensionDp >= + WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP + if (!DesktopModeStatus.canEnterDesktopMode(context) + && DesktopModeStatus.overridesShowAppHandle(context) + && !isOnLargeScreen + ) { + // Devices with multiple screens may enable the app handle but it should not show on + // small screens + return false + } + return DesktopModeStatus.canEnterDesktopModeOrShowAppHandle(context) + && !isWallpaperTask(taskInfo) + && taskInfo.windowingMode != WindowConfiguration.WINDOWING_MODE_PINNED + && taskInfo.activityType == WindowConfiguration.ACTIVITY_TYPE_STANDARD + && !taskInfo.configuration.windowConfiguration.isAlwaysOnTop + } + + private fun allowedForDisplay(displayId: Int): Boolean { + // If DisplayController doesn't have it tracked, it could be a private/managed display. + val display = displayController.getDisplay(displayId) + if (display == null) return false + + if (DesktopModeStatus.isDesktopModeSupportedOnDisplay(context, display)) { + return true + } + // If on default display and on Large Screen (unfolded), show app handle + return DesktopModeStatus.overridesShowAppHandle(context) + && display.minSizeDimensionDp >= WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/ButtonBackgroundDrawableUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/ButtonBackgroundDrawableUtils.kt index f44b15a23b90..f08cfa987cc7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/ButtonBackgroundDrawableUtils.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/ButtonBackgroundDrawableUtils.kt @@ -20,7 +20,6 @@ import android.content.res.ColorStateList import android.graphics.Color import android.graphics.drawable.Drawable import android.graphics.drawable.LayerDrawable -import android.graphics.drawable.RippleDrawable import android.graphics.drawable.ShapeDrawable import android.graphics.drawable.shapes.RoundRectShape @@ -48,45 +47,6 @@ fun replaceColorAlpha(@ColorInt color: Int, alpha: Int): Int { } /** - * Creates a RippleDrawable with specified color, corner radius, and insets. - */ -fun createRippleDrawable( - @ColorInt color: Int, - cornerRadius: Int, - drawableInsets: DrawableInsets, -): RippleDrawable { - return RippleDrawable( - ColorStateList( - arrayOf( - intArrayOf(android.R.attr.state_hovered), - intArrayOf(android.R.attr.state_pressed), - intArrayOf(), - ), - intArrayOf( - replaceColorAlpha(color, OPACITY_11), - replaceColorAlpha(color, OPACITY_15), - Color.TRANSPARENT, - ) - ), - null /* content */, - LayerDrawable(arrayOf( - ShapeDrawable().apply { - shape = RoundRectShape( - FloatArray(8) { cornerRadius.toFloat() }, - null /* inset */, - null /* innerRadii */ - ) - paint.color = Color.WHITE - } - )).apply { - require(numberOfLayers == 1) { "Must only contain one layer" } - setLayerInset(0 /* index */, - drawableInsets.l, drawableInsets.t, drawableInsets.r, drawableInsets.b) - } - ) -} - -/** * Creates a background drawable with specified color, corner radius, and insets. */ fun createBackgroundDrawable( diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoader.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoader.kt index 801048adda4d..957898fd0088 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoader.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoader.kt @@ -21,6 +21,7 @@ import android.content.Context import android.content.pm.ActivityInfo import android.content.pm.PackageManager import android.graphics.Bitmap +import android.os.LocaleList import android.os.UserHandle import androidx.tracing.Trace import com.android.internal.annotations.VisibleForTesting @@ -80,6 +81,13 @@ class WindowDecorTaskResourceLoader( */ private val existingTasks = mutableSetOf<Int>() + /** + * A map of task -> localeList to keep track of the language of app name that's currently + * cached in |taskToResourceCache|. + */ + @VisibleForTesting + val localeListOnCache = ConcurrentHashMap<Int, LocaleList>() + init { shellInit.addInitCallback(this::onInit, this) } @@ -99,11 +107,14 @@ class WindowDecorTaskResourceLoader( fun getName(taskInfo: RunningTaskInfo): CharSequence { checkWindowDecorExists(taskInfo) val cachedResources = taskToResourceCache[taskInfo.taskId] - if (cachedResources != null) { + val localeListActiveOnCacheTime = localeListOnCache[taskInfo.taskId] + if (cachedResources != null && + taskInfo.getConfiguration().getLocales().equals(localeListActiveOnCacheTime)) { return cachedResources.appName } val resources = loadAppResources(taskInfo) taskToResourceCache[taskInfo.taskId] = resources + localeListOnCache[taskInfo.taskId] = taskInfo.getConfiguration().getLocales() return resources.appName } @@ -117,6 +128,7 @@ class WindowDecorTaskResourceLoader( } val resources = loadAppResources(taskInfo) taskToResourceCache[taskInfo.taskId] = resources + localeListOnCache[taskInfo.taskId] = taskInfo.getConfiguration().getLocales() return resources.appIcon } @@ -130,6 +142,7 @@ class WindowDecorTaskResourceLoader( } val resources = loadAppResources(taskInfo) taskToResourceCache[taskInfo.taskId] = resources + localeListOnCache[taskInfo.taskId] = taskInfo.getConfiguration().getLocales() return resources.veilIcon } @@ -142,6 +155,7 @@ class WindowDecorTaskResourceLoader( fun onWindowDecorClosed(taskInfo: RunningTaskInfo) { existingTasks.remove(taskInfo.taskId) taskToResourceCache.remove(taskInfo.taskId) + localeListOnCache.remove(taskInfo.taskId) } private fun checkWindowDecorExists(taskInfo: RunningTaskInfo) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt index c3f5b3f20f4a..30712b55bdfa 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt @@ -466,11 +466,7 @@ class AppHeaderViewHolder( override fun onHandleMenuOpened() {} - override fun onHandleMenuClosed() { - openMenuButton.post { - openMenuButton.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED) - } - } + override fun onHandleMenuClosed() {} fun onMaximizeWindowHoverExit() { maximizeButtonView.cancelHoverAnimation() diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/UnminimizeAppFromTaskbar.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/UnminimizeAppFromTaskbar.kt index 3e98e4342b3f..7d9f2bf8fdf6 100644 --- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/UnminimizeAppFromTaskbar.kt +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/UnminimizeAppFromTaskbar.kt @@ -19,7 +19,7 @@ package com.android.wm.shell.scenarios import android.app.Instrumentation import android.tools.NavBar import android.tools.Rotation -import android.tools.device.apphelpers.BrowserAppHelper +import android.tools.device.apphelpers.GmailAppHelper import android.tools.flicker.rules.ChangeDisplayOrientationRule import android.tools.traces.parsers.WindowManagerStateHelper import androidx.test.platform.app.InstrumentationRegistry @@ -44,8 +44,8 @@ abstract class UnminimizeAppFromTaskbar(val rotation: Rotation = Rotation.ROTATI private val wmHelper = WindowManagerStateHelper(instrumentation) private val device = UiDevice.getInstance(instrumentation) private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation)) - private val browserHelper = BrowserAppHelper(instrumentation) - private val browserApp = DesktopModeAppHelper(browserHelper) + private val gmailHelper = GmailAppHelper(instrumentation) + private val gmailApp = DesktopModeAppHelper(gmailHelper) @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation) @@ -59,20 +59,20 @@ abstract class UnminimizeAppFromTaskbar(val rotation: Rotation = Rotation.ROTATI ChangeDisplayOrientationRule.setRotation(rotation) testApp.enterDesktopMode(wmHelper, device) tapl.showTaskbarIfHidden() - browserApp.launchViaIntent(wmHelper) - browserApp.minimizeDesktopApp(wmHelper, device) + gmailApp.launchViaIntent(wmHelper) + gmailApp.minimizeDesktopApp(wmHelper, device) } @Test open fun unminimizeApp() { tapl.launchedAppState.taskbar - .getAppIcon(browserHelper.appName) - .launch(browserHelper.packageName) + .getAppIcon(gmailHelper.appName) + .launch(gmailHelper.packageName) } @After fun teardown() { testApp.exit(wmHelper) - browserApp.exit(wmHelper) + gmailApp.exit(wmHelper) } }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/AndroidTestTemplate.xml index 1de47df78853..e51447f5cfcb 100644 --- a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/AndroidTestTemplate.xml +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/AndroidTestTemplate.xml @@ -50,6 +50,8 @@ <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/> <!-- Disable AOD --> <option name="run-command" value="settings put secure doze_always_on 0"/> + <!-- Disable explore hub mode --> + <option name="run-command" value="settings put secure glanceable_hub_enabled 0"/> <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/> <option name="run-command" value="settings put system show_touches 1"/> <option name="run-command" value="settings put system pointer_location 1"/> diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/AndroidTestTemplate.xml index 34d001c858f6..7659ec903480 100644 --- a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/AndroidTestTemplate.xml +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/AndroidTestTemplate.xml @@ -50,6 +50,8 @@ <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/> <!-- Disable AOD --> <option name="run-command" value="settings put secure doze_always_on 0"/> + <!-- Disable explore hub mode --> + <option name="run-command" value="settings put secure glanceable_hub_enabled 0"/> <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/> <option name="run-command" value="settings put system show_touches 1"/> <option name="run-command" value="settings put system pointer_location 1"/> diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/AndroidTestTemplate.xml index 34d001c858f6..7659ec903480 100644 --- a/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/AndroidTestTemplate.xml +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/AndroidTestTemplate.xml @@ -50,6 +50,8 @@ <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/> <!-- Disable AOD --> <option name="run-command" value="settings put secure doze_always_on 0"/> + <!-- Disable explore hub mode --> + <option name="run-command" value="settings put secure glanceable_hub_enabled 0"/> <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/> <option name="run-command" value="settings put system show_touches 1"/> <option name="run-command" value="settings put system pointer_location 1"/> diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml index 9c1a8f17aeee..a4ecac9dfeb0 100644 --- a/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml +++ b/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml @@ -50,6 +50,8 @@ <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/> <!-- Disable AOD --> <option name="run-command" value="settings put secure doze_always_on 0"/> + <!-- Disable explore hub mode --> + <option name="run-command" value="settings put secure glanceable_hub_enabled 0"/> <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/> <option name="run-command" value="settings put system show_touches 1"/> <option name="run-command" value="settings put system pointer_location 1"/> diff --git a/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml index ae73dae99d6f..75ffdc69c73b 100644 --- a/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml +++ b/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml @@ -50,6 +50,8 @@ <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/> <!-- Disable AOD --> <option name="run-command" value="settings put secure doze_always_on 0"/> + <!-- Disable explore hub mode --> + <option name="run-command" value="settings put secure glanceable_hub_enabled 0"/> <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/> <option name="run-command" value="settings put system show_touches 1"/> <option name="run-command" value="settings put system pointer_location 1"/> diff --git a/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml index a136936c0838..8003cbaada50 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml +++ b/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml @@ -50,6 +50,8 @@ <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/> <!-- Disable AOD --> <option name="run-command" value="settings put secure doze_always_on 0"/> + <!-- Disable explore hub mode --> + <option name="run-command" value="settings put secure glanceable_hub_enabled 0"/> <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/> <option name="run-command" value="settings put system show_touches 1"/> <option name="run-command" value="settings put system pointer_location 1"/> diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTransitionsTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTransitionsTest.java index 3c79ea7be39f..925ca0f1638d 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTransitionsTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTransitionsTest.java @@ -221,8 +221,8 @@ public class BubbleTransitionsTest extends ShellTestCase { PointF dragPosition = new PointF(10f, 20f); BubbleTransitions.DragData dragData = new BubbleTransitions.DragData( - /* releasedOnLeft= */ false, /* taskScale= */ 0.5f, dragPosition, - pendingWct); + /* releasedOnLeft= */ false, /* taskScale= */ 0.5f, /* cornerRadius= */ 10f, + dragPosition, pendingWct); final BubbleTransitions.BubbleTransition bt = mBubbleTransitions.startConvertToBubble( mBubble, taskInfo, mExpandedViewManager, mTaskViewFactory, mBubblePositioner, @@ -253,6 +253,8 @@ public class BubbleTransitionsTest extends ShellTestCase { verify(startT).setPosition(snapshot, dragPosition.x, dragPosition.y); // Snapshot has the scale of the dragged task verify(startT).setScale(snapshot, dragData.getTaskScale(), dragData.getTaskScale()); + // Snapshot has dragged task corner radius + verify(startT).setCornerRadius(snapshot, dragData.getCornerRadius()); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipBoundsStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipBoundsStateTest.java index 01b76edd9b25..1066276becc7 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipBoundsStateTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipBoundsStateTest.java @@ -19,6 +19,8 @@ package com.android.wm.shell.common.pip; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -128,6 +130,31 @@ public class PipBoundsStateTest extends ShellTestCase { } @Test + public void setLastPipComponentName_notChanged_doesNotCallbackComponentChangedListener() { + mPipBoundsState.setLastPipComponentName(mTestComponentName1); + PipBoundsState.OnPipComponentChangedListener mockListener = + mock(PipBoundsState.OnPipComponentChangedListener.class); + + mPipBoundsState.addOnPipComponentChangedListener(mockListener); + mPipBoundsState.setLastPipComponentName(mTestComponentName1); + + verify(mockListener, never()).onPipComponentChanged(any(), any()); + } + + @Test + public void setLastPipComponentName_changed_callbackComponentChangedListener() { + mPipBoundsState.setLastPipComponentName(mTestComponentName1); + PipBoundsState.OnPipComponentChangedListener mockListener = + mock(PipBoundsState.OnPipComponentChangedListener.class); + + mPipBoundsState.addOnPipComponentChangedListener(mockListener); + mPipBoundsState.setLastPipComponentName(mTestComponentName2); + + verify(mockListener).onPipComponentChanged( + eq(mTestComponentName1), eq(mTestComponentName2)); + } + + @Test public void testSetLastPipComponentName_notChanged_doesNotClearReentryState() { mPipBoundsState.setLastPipComponentName(mTestComponentName1); mPipBoundsState.saveReentryState(DEFAULT_SNAP_FRACTION); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java index 598a101b8bcd..597e4a55ed0e 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java @@ -59,6 +59,7 @@ import com.android.wm.shell.common.DockStateReader; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.compatui.api.CompatUIInfo; +import com.android.wm.shell.compatui.impl.CompatUIRequests; import com.android.wm.shell.desktopmode.DesktopRepository; import com.android.wm.shell.desktopmode.DesktopUserRepositories; import com.android.wm.shell.sysui.ShellController; @@ -738,6 +739,22 @@ public class CompatUIControllerTest extends ShellTestCase { verify(mController, never()).removeLayouts(taskInfo.taskId); } + @Test + @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK) + public void testSendCompatUIRequest_createRestartDialog() { + TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ false); + doReturn(true).when(mMockRestartDialogLayout) + .needsToBeRecreated(any(TaskInfo.class), + any(ShellTaskOrganizer.TaskListener.class)); + doReturn(true).when(mCompatUIConfiguration).isRestartDialogEnabled(); + doReturn(true).when(mCompatUIConfiguration).shouldShowRestartDialogAgain(eq(taskInfo)); + + mController.sendCompatUIRequest(new CompatUIRequests.DisplayCompatShowRestartDialog( + taskInfo, mMockTaskListener)); + verify(mController).createRestartDialogWindowManager(any(), eq(taskInfo), + eq(mMockTaskListener)); + } + private static TaskInfo createTaskInfo(int displayId, int taskId, boolean hasSizeCompat) { return createTaskInfo(displayId, taskId, hasSizeCompat, /* isVisible */ false, /* isFocused */ false, /* isTopActivityTransparent */ false); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/crashhandling/ShellCrashHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/crashhandling/ShellCrashHandlerTest.kt new file mode 100644 index 000000000000..5c77f78a7dfa --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/crashhandling/ShellCrashHandlerTest.kt @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.crashhandling + +import android.app.ActivityManager.RunningTaskInfo +import android.app.PendingIntent +import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.platform.test.annotations.DisableFlags +import android.view.Display.DEFAULT_DISPLAY +import android.window.IWindowContainerToken +import android.window.WindowContainerToken +import android.window.WindowContainerTransaction +import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_PENDING_INTENT +import com.android.modules.utils.testing.ExtendedMockitoRule +import com.android.window.flags.Flags +import com.android.wm.shell.ShellTaskOrganizer +import com.android.wm.shell.ShellTestCase +import com.android.wm.shell.common.HomeIntentProvider +import com.android.wm.shell.common.ShellExecutor +import com.android.wm.shell.shared.desktopmode.DesktopModeStatus +import com.android.wm.shell.sysui.ShellInit +import com.google.common.truth.Truth.assertThat +import kotlin.test.Test +import org.junit.Before +import org.junit.Rule +import org.mockito.ArgumentCaptor +import org.mockito.Mockito +import org.mockito.kotlin.any +import org.mockito.kotlin.mock +import org.mockito.kotlin.spy +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever + +class ShellCrashHandlerTest : ShellTestCase() { + @JvmField + @Rule + val extendedMockitoRule = + ExtendedMockitoRule.Builder(this) + .mockStatic(DesktopModeStatus::class.java) + .mockStatic(PendingIntent::class.java) + .build()!! + + private val testExecutor = mock<ShellExecutor>() + private val context = mock<Context>() + private val shellTaskOrganizer = mock<ShellTaskOrganizer>() + + private lateinit var homeIntentProvider: HomeIntentProvider + private lateinit var crashHandler: ShellCrashHandler + private lateinit var shellInit: ShellInit + + + @Before + fun setup() { + whenever(DesktopModeStatus.canEnterDesktopMode(any())).thenReturn(true) + whenever(PendingIntent.getActivity(any(), any(), any(), any(), any())).thenReturn(mock()) + + shellInit = spy(ShellInit(testExecutor)) + + homeIntentProvider = HomeIntentProvider(context) + crashHandler = ShellCrashHandler(context, shellTaskOrganizer, homeIntentProvider, shellInit) + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun init_freeformTaskExists_sendsHomeIntent() { + val wctCaptor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) + whenever(shellTaskOrganizer.getRunningTasks()).thenReturn(arrayListOf(createTaskInfo(1))) + + shellInit.init() + + verify(shellTaskOrganizer).applyTransaction( + wctCaptor.capture() + ) + wctCaptor.value.assertPendingIntentAt(0, launchHomeIntent(DEFAULT_DISPLAY)) + } + + private fun launchHomeIntent(displayId: Int): Intent { + return Intent(Intent.ACTION_MAIN).apply { + if (displayId != DEFAULT_DISPLAY) { + addCategory(Intent.CATEGORY_SECONDARY_HOME) + } else { + addCategory(Intent.CATEGORY_HOME) + } + } + } + + private fun createTaskInfo(id: Int, windowingMode: Int = WINDOWING_MODE_FREEFORM) = + RunningTaskInfo().apply { + taskId = id + displayId = DEFAULT_DISPLAY + configuration.windowConfiguration.windowingMode = windowingMode + token = WindowContainerToken(Mockito.mock(IWindowContainerToken::class.java)) + baseIntent = Intent().apply { component = ComponentName("package", "component.name") } + } + + private fun WindowContainerTransaction.assertPendingIntentAt(index: Int, intent: Intent) { + val op = hierarchyOps[index] + assertThat(op.type).isEqualTo(HIERARCHY_OP_TYPE_PENDING_INTENT) + assertThat(op.activityIntent?.component).isEqualTo(intent.component) + assertThat(op.activityIntent?.categories).isEqualTo(intent.categories) + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt index 8ad54f5a0bb4..275d7b73a112 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt @@ -28,14 +28,22 @@ import com.android.wm.shell.ShellTestCase import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.DisplayController.OnDisplaysChangedListener import com.android.wm.shell.common.ShellExecutor +import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer import com.android.wm.shell.shared.desktopmode.DesktopModeStatus import com.android.wm.shell.sysui.ShellInit +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.cancel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.spy +import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.whenever @@ -46,15 +54,18 @@ import org.mockito.quality.Strictness * * Usage: atest WMShellUnitTests:DesktopDisplayEventHandlerTest */ +@OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(AndroidTestingRunner::class) class DesktopDisplayEventHandlerTest : ShellTestCase() { @Mock lateinit var testExecutor: ShellExecutor @Mock lateinit var displayController: DisplayController @Mock private lateinit var mockDesktopUserRepositories: DesktopUserRepositories + @Mock private lateinit var mockDesktopRepositoryInitializer: DesktopRepositoryInitializer @Mock private lateinit var mockDesktopRepository: DesktopRepository @Mock private lateinit var mockDesktopTasksController: DesktopTasksController @Mock private lateinit var desktopDisplayModeController: DesktopDisplayModeController + private val testScope = TestScope() private lateinit var mockitoSession: StaticMockitoSession private lateinit var shellInit: ShellInit @@ -77,7 +88,9 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() { DesktopDisplayEventHandler( context, shellInit, + testScope.backgroundScope, displayController, + mockDesktopRepositoryInitializer, mockDesktopUserRepositories, mockDesktopTasksController, desktopDisplayModeController, @@ -89,17 +102,66 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() { @After fun tearDown() { + testScope.cancel() mockitoSession.finishMocking() } @Test - fun testDisplayAdded_supportsDesks_createsDesk() { - whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(true) + fun testDisplayAdded_supportsDesks_desktopRepositoryInitialized_createsDesk() = + testScope.runTest { + val stateFlow = MutableStateFlow(false) + whenever(mockDesktopRepositoryInitializer.isInitialized).thenReturn(stateFlow) + whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(true) - onDisplaysChangedListenerCaptor.lastValue.onDisplayAdded(DEFAULT_DISPLAY) + onDisplaysChangedListenerCaptor.lastValue.onDisplayAdded(DEFAULT_DISPLAY) + stateFlow.emit(true) + runCurrent() - verify(mockDesktopTasksController).createDesk(DEFAULT_DISPLAY) - } + verify(mockDesktopTasksController).createDesk(DEFAULT_DISPLAY) + } + + @Test + fun testDisplayAdded_supportsDesks_desktopRepositoryNotInitialized_doesNotCreateDesk() = + testScope.runTest { + val stateFlow = MutableStateFlow(false) + whenever(mockDesktopRepositoryInitializer.isInitialized).thenReturn(stateFlow) + whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(true) + + onDisplaysChangedListenerCaptor.lastValue.onDisplayAdded(DEFAULT_DISPLAY) + runCurrent() + + verify(mockDesktopTasksController, never()).createDesk(DEFAULT_DISPLAY) + } + + @Test + fun testDisplayAdded_supportsDesks_desktopRepositoryInitializedTwice_createsDeskOnce() = + testScope.runTest { + val stateFlow = MutableStateFlow(false) + whenever(mockDesktopRepositoryInitializer.isInitialized).thenReturn(stateFlow) + whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(true) + + onDisplaysChangedListenerCaptor.lastValue.onDisplayAdded(DEFAULT_DISPLAY) + stateFlow.emit(true) + stateFlow.emit(true) + runCurrent() + + verify(mockDesktopTasksController, times(1)).createDesk(DEFAULT_DISPLAY) + } + + @Test + fun testDisplayAdded_supportsDesks_desktopRepositoryInitialized_deskExists_doesNotCreateDesk() = + testScope.runTest { + val stateFlow = MutableStateFlow(false) + whenever(mockDesktopRepositoryInitializer.isInitialized).thenReturn(stateFlow) + whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(true) + whenever(mockDesktopRepository.getNumberOfDesks(DEFAULT_DISPLAY)).thenReturn(1) + + onDisplaysChangedListenerCaptor.lastValue.onDisplayAdded(DEFAULT_DISPLAY) + stateFlow.emit(true) + runCurrent() + + verify(mockDesktopTasksController, never()).createDesk(DEFAULT_DISPLAY) + } @Test fun testDisplayAdded_cannotEnterDesktopMode_doesNotCreateDesk() { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayModeControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayModeControllerTest.kt index cc37c440f650..450989dd334d 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayModeControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayModeControllerTest.kt @@ -21,9 +21,12 @@ import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED import android.content.ContentResolver +import android.hardware.input.InputManager import android.os.Binder +import android.os.Handler import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.FlagsParameterization import android.provider.Settings import android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS import android.view.Display.DEFAULT_DISPLAY @@ -44,6 +47,7 @@ import com.android.wm.shell.transition.Transitions import com.google.common.truth.Truth.assertThat import com.google.testing.junit.testparameterinjector.TestParameter import com.google.testing.junit.testparameterinjector.TestParameterInjector +import com.google.testing.junit.testparameterinjector.TestParameterValuesProvider import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -64,13 +68,18 @@ import org.mockito.kotlin.whenever */ @SmallTest @RunWith(TestParameterInjector::class) -class DesktopDisplayModeControllerTest : ShellTestCase() { +class DesktopDisplayModeControllerTest( + @TestParameter(valuesProvider = FlagsParameterizationProvider::class) + flags: FlagsParameterization +) : ShellTestCase() { private val transitions = mock<Transitions>() private val rootTaskDisplayAreaOrganizer = mock<RootTaskDisplayAreaOrganizer>() private val mockWindowManager = mock<IWindowManager>() private val shellTaskOrganizer = mock<ShellTaskOrganizer>() private val desktopWallpaperActivityTokenProvider = mock<DesktopWallpaperActivityTokenProvider>() + private val inputManager = mock<InputManager>() + private val mainHandler = mock<Handler>() private lateinit var controller: DesktopDisplayModeController @@ -82,6 +91,10 @@ class DesktopDisplayModeControllerTest : ShellTestCase() { private val defaultTDA = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0) private val wallpaperToken = MockToken().token() + init { + mSetFlagsRule.setFlagsParameterization(flags) + } + @Before fun setUp() { whenever(transitions.startTransition(anyInt(), any(), isNull())).thenReturn(Binder()) @@ -95,27 +108,20 @@ class DesktopDisplayModeControllerTest : ShellTestCase() { mockWindowManager, shellTaskOrganizer, desktopWallpaperActivityTokenProvider, + inputManager, + mainHandler, ) runningTasks.add(freeformTask) runningTasks.add(fullscreenTask) whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenReturn(ArrayList(runningTasks)) whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(wallpaperToken) + setTabletModeStatus(SwitchState.UNKNOWN) } - private fun testDisplayWindowingModeSwitch( - defaultWindowingMode: Int, - extendedDisplayEnabled: Boolean, - expectToSwitch: Boolean, - ) { - defaultTDA.configuration.windowConfiguration.windowingMode = defaultWindowingMode - whenever(mockWindowManager.getWindowingMode(anyInt())).thenReturn(defaultWindowingMode) - val settingsSession = - ExtendedDisplaySettingsSession( - context.contentResolver, - if (extendedDisplayEnabled) 1 else 0, - ) - - settingsSession.use { + private fun testDisplayWindowingModeSwitchOnDisplayConnected(expectToSwitch: Boolean) { + defaultTDA.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN + whenever(mockWindowManager.getWindowingMode(anyInt())).thenReturn(WINDOWING_MODE_FULLSCREEN) + ExtendedDisplaySettingsSession(context.contentResolver, 1).use { connectExternalDisplay() if (expectToSwitch) { // Assumes [connectExternalDisplay] properly triggered the switching transition. @@ -133,7 +139,7 @@ class DesktopDisplayModeControllerTest : ShellTestCase() { assertThat(arg.firstValue.changes[wallpaperToken.asBinder()]?.windowingMode) .isEqualTo(WINDOWING_MODE_FULLSCREEN) assertThat(arg.secondValue.changes[defaultTDA.token.asBinder()]?.windowingMode) - .isEqualTo(defaultWindowingMode) + .isEqualTo(WINDOWING_MODE_FULLSCREEN) assertThat(arg.secondValue.changes[wallpaperToken.asBinder()]?.windowingMode) .isEqualTo(WINDOWING_MODE_FULLSCREEN) } else { @@ -144,25 +150,64 @@ class DesktopDisplayModeControllerTest : ShellTestCase() { @Test @DisableFlags(Flags.FLAG_ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING) - fun displayWindowingModeSwitchOnDisplayConnected_flagDisabled( - @TestParameter param: ModeSwitchTestCase - ) { - testDisplayWindowingModeSwitch( - param.defaultWindowingMode, - param.extendedDisplayEnabled, - // When the flag is disabled, never switch. - expectToSwitch = false, - ) + fun displayWindowingModeSwitchOnDisplayConnected_flagDisabled() { + // When the flag is disabled, never switch. + testDisplayWindowingModeSwitchOnDisplayConnected(/* expectToSwitch= */ false) } @Test @EnableFlags(Flags.FLAG_ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING) - fun displayWindowingModeSwitchOnDisplayConnected(@TestParameter param: ModeSwitchTestCase) { - testDisplayWindowingModeSwitch( - param.defaultWindowingMode, - param.extendedDisplayEnabled, - param.expectToSwitchByDefault, - ) + fun displayWindowingModeSwitchOnDisplayConnected() { + testDisplayWindowingModeSwitchOnDisplayConnected(/* expectToSwitch= */ true) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING) + @DisableFlags(Flags.FLAG_FORM_FACTOR_BASED_DESKTOP_FIRST_SWITCH) + fun testTargetWindowingMode_formfactorDisabled( + @TestParameter param: ExternalDisplayBasedTargetModeTestCase, + @TestParameter tabletModeStatus: SwitchState, + ) { + whenever(mockWindowManager.getWindowingMode(anyInt())) + .thenReturn(param.defaultWindowingMode) + if (param.hasExternalDisplay) { + connectExternalDisplay() + } else { + disconnectExternalDisplay() + } + setTabletModeStatus(tabletModeStatus) + + ExtendedDisplaySettingsSession( + context.contentResolver, + if (param.extendedDisplayEnabled) 1 else 0, + ) + .use { + assertThat(controller.getTargetWindowingModeForDefaultDisplay()) + .isEqualTo(param.expectedWindowingMode) + } + } + + @Test + @EnableFlags( + Flags.FLAG_ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING, + Flags.FLAG_FORM_FACTOR_BASED_DESKTOP_FIRST_SWITCH, + ) + fun testTargetWindowingMode(@TestParameter param: FormFactorBasedTargetModeTestCase) { + if (param.hasExternalDisplay) { + connectExternalDisplay() + } else { + disconnectExternalDisplay() + } + setTabletModeStatus(param.tabletModeStatus) + + ExtendedDisplaySettingsSession( + context.contentResolver, + if (param.extendedDisplayEnabled) 1 else 0, + ) + .use { + assertThat(controller.getTargetWindowingModeForDefaultDisplay()) + .isEqualTo(param.expectedWindowingMode) + } } @Test @@ -217,6 +262,10 @@ class DesktopDisplayModeControllerTest : ShellTestCase() { controller.refreshDisplayWindowingMode() } + private fun setTabletModeStatus(status: SwitchState) { + whenever(inputManager.isInTabletMode()).thenReturn(status.value) + } + private class ExtendedDisplaySettingsSession( private val contentResolver: ContentResolver, private val overrideValue: Int, @@ -233,33 +282,158 @@ class DesktopDisplayModeControllerTest : ShellTestCase() { } } + private class FlagsParameterizationProvider : TestParameterValuesProvider() { + override fun provideValues( + context: TestParameterValuesProvider.Context + ): List<FlagsParameterization> { + return FlagsParameterization.allCombinationsOf( + Flags.FLAG_FORM_FACTOR_BASED_DESKTOP_FIRST_SWITCH + ) + } + } + companion object { const val EXTERNAL_DISPLAY_ID = 100 - enum class ModeSwitchTestCase( + enum class SwitchState(val value: Int) { + UNKNOWN(InputManager.SWITCH_STATE_UNKNOWN), + ON(InputManager.SWITCH_STATE_ON), + OFF(InputManager.SWITCH_STATE_OFF), + } + + enum class ExternalDisplayBasedTargetModeTestCase( val defaultWindowingMode: Int, + val hasExternalDisplay: Boolean, val extendedDisplayEnabled: Boolean, - val expectToSwitchByDefault: Boolean, + val expectedWindowingMode: Int, ) { - FULLSCREEN_DISPLAY( + FREEFORM_EXTERNAL_EXTENDED( + defaultWindowingMode = WINDOWING_MODE_FREEFORM, + hasExternalDisplay = true, + extendedDisplayEnabled = true, + expectedWindowingMode = WINDOWING_MODE_FREEFORM, + ), + FULLSCREEN_EXTERNAL_EXTENDED( defaultWindowingMode = WINDOWING_MODE_FULLSCREEN, + hasExternalDisplay = true, + extendedDisplayEnabled = true, + expectedWindowingMode = WINDOWING_MODE_FREEFORM, + ), + FREEFORM_NO_EXTERNAL_EXTENDED( + defaultWindowingMode = WINDOWING_MODE_FREEFORM, + hasExternalDisplay = false, extendedDisplayEnabled = true, - expectToSwitchByDefault = true, + expectedWindowingMode = WINDOWING_MODE_FREEFORM, + ), + FULLSCREEN_NO_EXTERNAL_EXTENDED( + defaultWindowingMode = WINDOWING_MODE_FULLSCREEN, + hasExternalDisplay = false, + extendedDisplayEnabled = true, + expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, + ), + FREEFORM_EXTERNAL_MIRROR( + defaultWindowingMode = WINDOWING_MODE_FREEFORM, + hasExternalDisplay = true, + extendedDisplayEnabled = false, + expectedWindowingMode = WINDOWING_MODE_FREEFORM, ), - FULLSCREEN_DISPLAY_MIRRORING( + FULLSCREEN_EXTERNAL_MIRROR( defaultWindowingMode = WINDOWING_MODE_FULLSCREEN, + hasExternalDisplay = true, extendedDisplayEnabled = false, - expectToSwitchByDefault = false, + expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, ), - FREEFORM_DISPLAY( + FREEFORM_NO_EXTERNAL_MIRROR( defaultWindowingMode = WINDOWING_MODE_FREEFORM, + hasExternalDisplay = false, + extendedDisplayEnabled = false, + expectedWindowingMode = WINDOWING_MODE_FREEFORM, + ), + FULLSCREEN_NO_EXTERNAL_MIRROR( + defaultWindowingMode = WINDOWING_MODE_FULLSCREEN, + hasExternalDisplay = false, + extendedDisplayEnabled = false, + expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, + ), + } + + enum class FormFactorBasedTargetModeTestCase( + val hasExternalDisplay: Boolean, + val extendedDisplayEnabled: Boolean, + val tabletModeStatus: SwitchState, + val expectedWindowingMode: Int, + ) { + EXTERNAL_EXTENDED_TABLET( + hasExternalDisplay = true, extendedDisplayEnabled = true, - expectToSwitchByDefault = false, + tabletModeStatus = SwitchState.ON, + expectedWindowingMode = WINDOWING_MODE_FREEFORM, ), - FREEFORM_DISPLAY_MIRRORING( - defaultWindowingMode = WINDOWING_MODE_FREEFORM, + NO_EXTERNAL_EXTENDED_TABLET( + hasExternalDisplay = false, + extendedDisplayEnabled = true, + tabletModeStatus = SwitchState.ON, + expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, + ), + EXTERNAL_MIRROR_TABLET( + hasExternalDisplay = true, + extendedDisplayEnabled = false, + tabletModeStatus = SwitchState.ON, + expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, + ), + NO_EXTERNAL_MIRROR_TABLET( + hasExternalDisplay = false, + extendedDisplayEnabled = false, + tabletModeStatus = SwitchState.ON, + expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, + ), + EXTERNAL_EXTENDED_CLAMSHELL( + hasExternalDisplay = true, + extendedDisplayEnabled = true, + tabletModeStatus = SwitchState.OFF, + expectedWindowingMode = WINDOWING_MODE_FREEFORM, + ), + NO_EXTERNAL_EXTENDED_CLAMSHELL( + hasExternalDisplay = false, + extendedDisplayEnabled = true, + tabletModeStatus = SwitchState.OFF, + expectedWindowingMode = WINDOWING_MODE_FREEFORM, + ), + EXTERNAL_MIRROR_CLAMSHELL( + hasExternalDisplay = true, + extendedDisplayEnabled = false, + tabletModeStatus = SwitchState.OFF, + expectedWindowingMode = WINDOWING_MODE_FREEFORM, + ), + NO_EXTERNAL_MIRROR_CLAMSHELL( + hasExternalDisplay = false, + extendedDisplayEnabled = false, + tabletModeStatus = SwitchState.OFF, + expectedWindowingMode = WINDOWING_MODE_FREEFORM, + ), + EXTERNAL_EXTENDED_UNKNOWN( + hasExternalDisplay = true, + extendedDisplayEnabled = true, + tabletModeStatus = SwitchState.UNKNOWN, + expectedWindowingMode = WINDOWING_MODE_FREEFORM, + ), + NO_EXTERNAL_EXTENDED_UNKNOWN( + hasExternalDisplay = false, + extendedDisplayEnabled = true, + tabletModeStatus = SwitchState.UNKNOWN, + expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, + ), + EXTERNAL_MIRROR_UNKNOWN( + hasExternalDisplay = true, + extendedDisplayEnabled = false, + tabletModeStatus = SwitchState.UNKNOWN, + expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, + ), + NO_EXTERNAL_MIRROR_UNKNOWN( + hasExternalDisplay = false, extendedDisplayEnabled = false, - expectToSwitchByDefault = false, + tabletModeStatus = SwitchState.UNKNOWN, + expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, ), } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt index 006c3cae121c..4c18ee1500b7 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt @@ -114,6 +114,8 @@ class DesktopImmersiveControllerTest : ShellTestCase() { transactionSupplier = transactionSupplier, ) desktopRepository = userRepositories.current + desktopRepository.addDesk(DEFAULT_DISPLAY, DEFAULT_DESK_ID) + desktopRepository.setActiveDesk(DEFAULT_DISPLAY, DEFAULT_DESK_ID) } @Test @@ -835,5 +837,6 @@ class DesktopImmersiveControllerTest : ShellTestCase() { companion object { private val STABLE_BOUNDS = Rect(0, 100, 2000, 1900) private val DISPLAY_BOUNDS = Rect(0, 0, 2000, 2000) + private const val DEFAULT_DESK_ID = 0 } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt index e9f92cfd7c56..0c585b3e843a 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt @@ -431,6 +431,38 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { } @Test + @EnableFlags( + Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX, + Flags.FLAG_ENABLE_DESKTOP_OPENING_DEEPLINK_MINIMIZE_ANIMATION_BUGFIX, + ) + fun startAndAnimateLaunchTransition_withMinimizeChange_wrongTaskId_reparentsMinimizeChange() { + val wct = WindowContainerTransaction() + val launchingTask = createTask(WINDOWING_MODE_FREEFORM) + val minimizingTask = createTask(WINDOWING_MODE_FREEFORM) + val launchTaskChange = createChange(launchingTask, mode = TRANSIT_OPEN) + val minimizeChange = createChange(minimizingTask) + val transition = Binder() + whenever(transitions.startTransition(eq(TRANSIT_OPEN), eq(wct), anyOrNull())) + .thenReturn(transition) + + mixedHandler.startLaunchTransition( + transitionType = TRANSIT_OPEN, + wct = wct, + taskId = Int.MAX_VALUE, + minimizingTaskId = minimizingTask.taskId, + ) + mixedHandler.startAnimation( + transition, + createCloseTransitionInfo(TRANSIT_OPEN, listOf(launchTaskChange, minimizeChange)), + SurfaceControl.Transaction(), + SurfaceControl.Transaction(), + ) {} + + verify(rootTaskDisplayAreaOrganizer) + .reparentToDisplayArea(anyInt(), eq(minimizeChange.leash), any()) + } + + @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX) fun startAnimation_pendingTransition_noLaunchChange_returnsFalse() { val wct = WindowContainerTransaction() diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeMoveToDisplayTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeMoveToDisplayTransitionHandlerTest.kt index fbc940663d19..6a99d4770728 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeMoveToDisplayTransitionHandlerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeMoveToDisplayTransitionHandlerTest.kt @@ -16,8 +16,10 @@ package com.android.wm.shell.desktopmode +import android.graphics.Rect import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper +import android.view.SurfaceControl import android.view.WindowManager import android.window.TransitionInfo import androidx.test.filters.SmallTest @@ -30,6 +32,8 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.kotlin.mock +import org.mockito.kotlin.spy +import org.mockito.kotlin.verify @SmallTest @RunWithLooper @@ -55,7 +59,9 @@ class DesktopModeMoveToDisplayTransitionHandlerTest : ShellTestCase() { info = TransitionInfo(WindowManager.TRANSIT_CHANGE, /* flags= */ 0).apply { addChange( - TransitionInfo.Change(mock(), mock()).apply { setDisplayId(1, 1) } + TransitionInfo.Change(mock(), mock()).apply { + setDisplayId(/* start= */ 1, /* end= */ 1) + } ) }, startTransaction = StubTransaction(), @@ -74,7 +80,9 @@ class DesktopModeMoveToDisplayTransitionHandlerTest : ShellTestCase() { info = TransitionInfo(WindowManager.TRANSIT_CHANGE, /* flags= */ 0).apply { addChange( - TransitionInfo.Change(mock(), mock()).apply { setDisplayId(1, 2) } + TransitionInfo.Change(mock(), mock()).apply { + setDisplayId(/* start= */ 1, /* end= */ 2) + } ) }, startTransaction = StubTransaction(), @@ -84,4 +92,77 @@ class DesktopModeMoveToDisplayTransitionHandlerTest : ShellTestCase() { assertTrue("Should animate display change transition", animates) } + + @Test + fun startAnimation_movingActivityEmbedding_shouldSetCorrectBounds() { + val leashLeft = mock<SurfaceControl>() + val leashRight = mock<SurfaceControl>() + val leashContainer = mock<SurfaceControl>() + val startTransaction = spy(StubTransaction()) + + handler.startAnimation( + transition = mock(), + info = + TransitionInfo(WindowManager.TRANSIT_CHANGE, /* flags= */ 0).apply { + addChange( + TransitionInfo.Change(mock(), mock()).apply { + flags = TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY + leash = leashLeft + setDisplayId(/* start= */ 1, /* end= */ 2) + setEndAbsBounds( + Rect( + /* left= */ 100, + /* top= */ 100, + /* right= */ 500, + /* bottom= */ 700, + ) + ) + setEndRelOffset(/* left= */ 0, /* top= */ 0) + } + ) + addChange( + TransitionInfo.Change(mock(), mock()).apply { + flags = TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY + leash = leashRight + setDisplayId(1, 2) + setEndAbsBounds( + Rect( + /* left= */ 500, + /* top= */ 100, + /* right= */ 900, + /* bottom= */ 700, + ) + ) + setEndRelOffset(/* left= */ 400, /* top= */ 0) + } + ) + addChange( + TransitionInfo.Change(mock(), mock()).apply { + flags = TransitionInfo.FLAG_TRANSLUCENT + leash = leashContainer + setDisplayId(/* start= */ 1, /* end= */ 2) + setEndAbsBounds( + Rect( + /* left= */ 100, + /* top= */ 100, + /* right= */ 900, + /* bottom= */ 700, + ) + ) + setEndRelOffset(/* left= */ 100, /* top= */ 100) + } + ) + }, + startTransaction = startTransaction, + finishTransaction = StubTransaction(), + finishCallback = mock(), + ) + + verify(startTransaction).setPosition(leashLeft, 0f, 0f) + verify(startTransaction).setPosition(leashRight, 400f, 0f) + verify(startTransaction).setPosition(leashContainer, 100f, 100f) + verify(startTransaction).setWindowCrop(leashLeft, 400, 600) + verify(startTransaction).setWindowCrop(leashRight, 400, 600) + verify(startTransaction).setWindowCrop(leashContainer, 800, 600) + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt index fe1dc29181b9..b859a00c6df4 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt @@ -24,7 +24,6 @@ import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper -import android.view.Display import android.view.SurfaceControl import androidx.test.filters.SmallTest import com.android.internal.policy.SystemBarUtils @@ -67,7 +66,6 @@ class DesktopModeVisualIndicatorTest : ShellTestCase() { private lateinit var taskInfo: RunningTaskInfo @Mock private lateinit var syncQueue: SyncTransactionQueue @Mock private lateinit var displayController: DisplayController - @Mock private lateinit var display: Display @Mock private lateinit var taskSurface: SurfaceControl @Mock private lateinit var taskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer @Mock private lateinit var displayLayout: DisplayLayout @@ -83,12 +81,20 @@ class DesktopModeVisualIndicatorTest : ShellTestCase() { whenever(displayLayout.width()).thenReturn(DISPLAY_BOUNDS.width()) whenever(displayLayout.height()).thenReturn(DISPLAY_BOUNDS.height()) whenever(displayLayout.stableInsets()).thenReturn(STABLE_INSETS) - whenever(displayController.getDisplay(anyInt())).thenReturn(display) whenever(displayController.getDisplayLayout(anyInt())).thenReturn(displayLayout) whenever(displayController.getDisplay(anyInt())).thenReturn(mContext.display) whenever(bubbleBoundsProvider.getBubbleBarExpandedViewDropTargetBounds(any())) .thenReturn(Rect()) taskInfo = DesktopTestHelpers.createFullscreenTask() + + mContext.orCreateTestableResources.addOverride( + com.android.internal.R.bool.config_isDesktopModeSupported, + true, + ) + mContext.orCreateTestableResources.addOverride( + com.android.internal.R.bool.config_canInternalDisplayHostDesktops, + true, + ) } @Test @@ -260,14 +266,9 @@ class DesktopModeVisualIndicatorTest : ShellTestCase() { ) fun testDefaultIndicatorWithNoDesktop() { mContext.orCreateTestableResources.addOverride( - com.android.internal.R.bool.config_isDesktopModeSupported, + com.android.internal.R.bool.config_canInternalDisplayHostDesktops, false, ) - mContext.orCreateTestableResources.addOverride( - com.android.internal.R.bool.config_isDesktopModeDevOptionSupported, - false, - ) - // Fullscreen to center, no desktop indicator createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FULLSCREEN) var result = visualIndicator.updateIndicatorType(PointF(500f, 500f)) 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 de92d391645a..f84a1a38bdfc 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt @@ -1203,6 +1203,17 @@ class DesktopRepositoryTest(flags: FlagsParameterization) : ShellTestCase() { } @Test + @EnableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND, FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE) + fun removeDesk_removesFromPersistence() = + runTest(StandardTestDispatcher()) { + repo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 2) + + repo.removeDesk(deskId = 2) + + verify(persistentRepository).removeDesktop(DEFAULT_USER_ID, 2) + } + + @Test fun getTaskInFullImmersiveState_byDisplay() { repo.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY) repo.setActiveDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY) 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 54360a8fd908..4ace1b2d7c71 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListenerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListenerTest.kt @@ -119,9 +119,8 @@ class DesktopTaskChangeListenerTest : ShellTestCase() { } @Test - fun onTaskChanging_freeformTask_nonActiveTaskInDesktopRepo_addsTaskToDesktopRepo() { + fun onTaskChanging_freeformTask_addsTaskToDesktopRepo() { val task = createFreeformTask().apply { isVisible = true } - whenever(desktopUserRepositories.current.isActiveTask(task.taskId)).thenReturn(false) desktopTaskChangeListener.onTaskChanging(task) @@ -129,28 +128,6 @@ class DesktopTaskChangeListenerTest : ShellTestCase() { } @Test - fun onTaskChanging_freeformTask_activeVisibleTaskInDesktopRepo_updatesTaskVisibility() { - val task = createFreeformTask().apply { isVisible = true } - whenever(desktopUserRepositories.current.isActiveTask(task.taskId)).thenReturn(true) - - desktopTaskChangeListener.onTaskChanging(task) - - verify(desktopUserRepositories.current) - .updateTask(task.displayId, task.taskId, task.isVisible) - } - - @Test - fun onTaskChanging_freeformTask_activeNonVisibleTask_updatesTaskVisibility() { - val task = createFreeformTask().apply { isVisible = false } - whenever(desktopUserRepositories.current.isActiveTask(task.taskId)).thenReturn(true) - - desktopTaskChangeListener.onTaskChanging(task) - - verify(desktopUserRepositories.current) - .updateTask(task.displayId, task.taskId, task.isVisible) - } - - @Test fun onTaskMovingToFront_fullscreenTask_activeTaskInDesktopRepo_removesTaskFromRepo() { val task = createFullscreenTask().apply { isVisible = true } whenever(desktopUserRepositories.current.isActiveTask(task.taskId)).thenReturn(true) 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 8f499c959759..34c5ebd6d94d 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt @@ -22,6 +22,7 @@ import android.app.ActivityOptions import android.app.KeyguardManager import android.app.PendingIntent import android.app.PictureInPictureParams +import android.app.WindowConfiguration import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM @@ -31,6 +32,7 @@ import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED import android.content.ComponentName import android.content.Context import android.content.Intent +import android.content.Intent.FLAG_ACTIVITY_NEW_TASK import android.content.pm.ActivityInfo import android.content.pm.ActivityInfo.CONFIG_DENSITY import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE @@ -101,6 +103,7 @@ import com.android.wm.shell.TestShellExecutor import com.android.wm.shell.bubbles.BubbleController import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.DisplayLayout +import com.android.wm.shell.common.HomeIntentProvider import com.android.wm.shell.common.MultiInstanceHelper import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.common.SyncTransactionQueue @@ -275,6 +278,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() private lateinit var testScope: CoroutineScope private lateinit var desktopModeCompatPolicy: DesktopModeCompatPolicy private lateinit var spyContext: TestableContext + private lateinit var homeIntentProvider: HomeIntentProvider private val shellExecutor = TestShellExecutor() private val bgExecutor = TestShellExecutor() @@ -294,6 +298,10 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() private val wallpaperToken = MockToken().token() private val homeComponentName = ComponentName(HOME_LAUNCHER_PACKAGE_NAME, /* class */ "") + init { + mSetFlagsRule.setFlagsParameterization(flags) + } + @Before fun setUp() { Dispatchers.setMain(StandardTestDispatcher()) @@ -323,12 +331,14 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() transitions, userRepositories, shellTaskOrganizer, + desksOrganizer, MAX_TASK_LIMIT, mockInteractionJankMonitor, mContext, mockHandler, ) desktopModeCompatPolicy = spy(DesktopModeCompatPolicy(spyContext)) + homeIntentProvider = HomeIntentProvider(context) whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenAnswer { runningTasks } whenever(transitions.startTransition(anyInt(), any(), anyOrNull())).thenAnswer { Binder() } @@ -428,6 +438,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() dragToDesktopTransitionHandler, mMockDesktopImmersiveController, userRepositories, + repositoryInitializer, recentsTransitionHandler, multiInstanceHelper, shellExecutor, @@ -448,6 +459,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() desktopModeCompatPolicy, dragToDisplayTransitionHandler, moveToDisplayTransitionHandler, + homeIntentProvider, ) @After @@ -621,11 +633,13 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) fun isDesktopModeShowing_noTasks_returnsFalse() { assertThat(controller.isDesktopModeShowing(displayId = 0)).isFalse() } @Test + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) fun isDesktopModeShowing_noTasksVisible_returnsFalse() { val task1 = setUpFreeformTask() val task2 = setUpFreeformTask() @@ -636,6 +650,15 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test + @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun isDesktopModeShowing_noActiveDesk_returnsFalse() { + taskRepository.setDeskInactive(deskId = 0) + + assertThat(controller.isDesktopModeShowing(displayId = 0)).isFalse() + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) fun isDesktopModeShowing_tasksActiveAndVisible_returnsTrue() { val task1 = setUpFreeformTask() val task2 = setUpFreeformTask() @@ -650,6 +673,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY, Flags.FLAG_INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC, ) + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) fun isDesktopModeShowing_topTransparentFullscreenTask_returnsTrue() { val topTransparentTask = setUpFullscreenTask(displayId = DEFAULT_DISPLAY) taskRepository.setTopTransparentFullscreenTaskId(DEFAULT_DISPLAY, topTransparentTask.taskId) @@ -659,6 +683,20 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() @Test @EnableFlags( + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY, + Flags.FLAG_INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC, + Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND, + ) + fun isDesktopModeShowing_deskInactive_topTransparentFullscreenTask_returnsTrue() { + val topTransparentTask = setUpFullscreenTask(displayId = DEFAULT_DISPLAY) + taskRepository.setTopTransparentFullscreenTaskId(DEFAULT_DISPLAY, topTransparentTask.taskId) + taskRepository.setDeskInactive(deskId = 0) + + assertThat(controller.isDesktopModeShowing(displayId = DEFAULT_DISPLAY)).isTrue() + } + + @Test + @EnableFlags( Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY, ) @@ -1047,11 +1085,29 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) fun isAnyDeskActive_noTasks_returnsFalse() { assertThat(controller.isAnyDeskActive(DEFAULT_DISPLAY)).isFalse() } @Test + @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun isAnyDeskActive_noActiveDesk_returnsFalse() { + taskRepository.setDeskInactive(deskId = 0) + + assertThat(controller.isAnyDeskActive(DEFAULT_DISPLAY)).isFalse() + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun isAnyDeskActive_withActiveDesk_returnsTrue() { + taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0) + + assertThat(controller.isAnyDeskActive(DEFAULT_DISPLAY)).isTrue() + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) fun isAnyDeskActive_twoTasks_bothVisible_returnsTrue() { setUpHomeTask() @@ -1062,6 +1118,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) fun isInDesktop_twoTasks_oneVisible_returnsTrue() { setUpHomeTask() @@ -1072,6 +1129,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) fun isAnyDeskActive_twoTasksVisibleOnDifferentDisplays_returnsTrue() { taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY) taskRepository.setActiveDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY) @@ -1091,7 +1149,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() controller.addMoveToDeskTaskChanges(wct, task, deskId = 0) val finalBounds = findBoundsChange(wct, task) - assertThat(finalBounds).isEqualTo(Rect()) + assertThat(finalBounds).isNull() } @Test @@ -1102,7 +1160,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() controller.addMoveToDeskTaskChanges(wct, task, deskId = 0) val finalBounds = findBoundsChange(wct, task) - assertThat(finalBounds).isEqualTo(Rect()) + assertThat(finalBounds).isNull() } @Test @@ -1113,7 +1171,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() controller.addMoveToDeskTaskChanges(wct, task, deskId = 0) val finalBounds = findBoundsChange(wct, task) - assertThat(finalBounds).isEqualTo(Rect()) + assertThat(finalBounds).isNull() } @Test @@ -1124,7 +1182,55 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() controller.addMoveToDeskTaskChanges(wct, task, deskId = 0) val finalBounds = findBoundsChange(wct, task) - assertThat(finalBounds).isEqualTo(Rect()) + assertThat(finalBounds).isNull() + } + + @Test + @EnableFlags(Flags.FLAG_INHERIT_TASK_BOUNDS_FOR_TRAMPOLINE_TASK_LAUNCHES) + fun addMoveToDeskTaskChanges_newTaskInstance_inheritsClosingInstanceBounds() { + // Setup existing task. + val existingTask = setUpFreeformTask(active = true) + val testComponent = ComponentName(/* package */ "test.package", /* class */ "test.class") + existingTask.topActivity = testComponent + existingTask.configuration.windowConfiguration.setBounds(Rect(0, 0, 500, 500)) + // Set up new instance of already existing task. + val launchingTask = setUpFullscreenTask() + launchingTask.topActivity = testComponent + launchingTask.baseIntent.addFlags(FLAG_ACTIVITY_NEW_TASK) + + // Move new instance to desktop. By default multi instance is not supported so first + // instance will close. + val wct = WindowContainerTransaction() + controller.addMoveToDeskTaskChanges(wct, launchingTask, deskId = 0) + + // New instance should inherit task bounds of old instance. + assertThat(findBoundsChange(wct, launchingTask)) + .isEqualTo(existingTask.configuration.windowConfiguration.bounds) + } + + @Test + @EnableFlags(Flags.FLAG_INHERIT_TASK_BOUNDS_FOR_TRAMPOLINE_TASK_LAUNCHES) + fun handleRequest_newTaskInstance_inheritsClosingInstanceBounds() { + setUpLandscapeDisplay() + // Setup existing task. + val existingTask = setUpFreeformTask(active = true) + val testComponent = ComponentName(/* package */ "test.package", /* class */ "test.class") + existingTask.topActivity = testComponent + existingTask.configuration.windowConfiguration.setBounds(Rect(0, 0, 500, 500)) + // Set up new instance of already existing task. + val launchingTask = setUpFreeformTask(active = false) + taskRepository.removeTask(launchingTask.displayId, launchingTask.taskId) + launchingTask.topActivity = testComponent + launchingTask.baseIntent.addFlags(FLAG_ACTIVITY_NEW_TASK) + + // Move new instance to desktop. By default multi instance is not supported so first + // instance will close. + val wct = controller.handleRequest(Binder(), createTransition(launchingTask)) + + assertNotNull(wct, "should handle request") + val finalBounds = findBoundsChange(wct, launchingTask) + // New instance should inherit task bounds of old instance. + assertThat(finalBounds).isEqualTo(existingTask.configuration.windowConfiguration.bounds) } @Test @@ -1808,6 +1914,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() val wallpaperToken = MockToken().token() whenever(desktopWallpaperActivityTokenProvider.getToken(SECOND_DISPLAY)) .thenReturn(wallpaperToken) + taskRepository.addDesk(SECOND_DISPLAY, deskId = 2) val task = setUpFreeformTask(displayId = SECOND_DISPLAY, deskId = 2, background = true) controller.moveTaskToDefaultDeskAndActivate( @@ -2363,6 +2470,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) fun moveTaskToFront_postsWctWithReorderOp() { val task1 = setUpFreeformTask() setUpFreeformTask() @@ -2385,9 +2493,34 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test - fun moveTaskToFront_bringsTasksOverLimit_minimizesBackTask() { + @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun moveTaskToFront_reordersToFront() { + val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = 0) + setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = 0) + whenever( + desktopMixedTransitionHandler.startLaunchTransition( + eq(TRANSIT_TO_FRONT), + any(), + eq(task1.taskId), + anyOrNull(), + anyOrNull(), + ) + ) + .thenReturn(Binder()) + + controller.moveTaskToFront(task1, remoteTransition = null) + + verify(desksOrganizer).reorderTaskToFront(any(), eq(0), eq(task1)) + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun moveTaskToFront_bringsTasksOverLimit_multiDesksDisabled_minimizesBackTask() { setUpHomeTask() - val freeformTasks = (1..MAX_TASK_LIMIT + 1).map { _ -> setUpFreeformTask() } + val freeformTasks = + (1..MAX_TASK_LIMIT + 1).map { _ -> + setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = 0) + } whenever( desktopMixedTransitionHandler.startLaunchTransition( eq(TRANSIT_TO_FRONT), @@ -2408,6 +2541,32 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test + @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun moveTaskToFront_bringsTasksOverLimit_multiDesksEnabled_minimizesBackTask() { + val deskId = 0 + setUpHomeTask() + val freeformTasks = + (1..MAX_TASK_LIMIT + 1).map { _ -> + setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId) + } + whenever( + desktopMixedTransitionHandler.startLaunchTransition( + eq(TRANSIT_TO_FRONT), + any(), + eq(freeformTasks[0].taskId), + anyOrNull(), + anyOrNull(), + ) + ) + .thenReturn(Binder()) + + controller.moveTaskToFront(freeformTasks[0], remoteTransition = null) + + val wct = getLatestDesktopMixedTaskWct(type = TRANSIT_TO_FRONT) + verify(desksOrganizer).minimizeTask(wct, deskId, freeformTasks[1]) + } + + @Test fun moveTaskToFront_minimizedTask_marksTaskAsUnminimized() { val transition = Binder() val freeformTask = setUpFreeformTask() @@ -2496,8 +2655,13 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test - fun moveTaskToFront_backgroundTaskBringsTasksOverLimit_minimizesBackTask() { - val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() } + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun moveTaskToFront_backgroundTaskBringsTasksOverLimit_multiDesksDisabled_minimizesBackTask() { + val deskId = 0 + val freeformTasks = + (1..MAX_TASK_LIMIT).map { _ -> + setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId) + } val task = createRecentTaskInfo(1001) whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(null) whenever( @@ -2520,6 +2684,33 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test + @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun moveTaskToFront_backgroundTaskBringsTasksOverLimit_multiDesksEnabled_minimizesBackTask() { + val deskId = 0 + val freeformTasks = + (1..MAX_TASK_LIMIT).map { _ -> + setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId) + } + val task = createRecentTaskInfo(1001) + whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(null) + whenever( + desktopMixedTransitionHandler.startLaunchTransition( + eq(TRANSIT_OPEN), + any(), + eq(task.taskId), + anyOrNull(), + anyOrNull(), + ) + ) + .thenReturn(Binder()) + + controller.moveTaskToFront(task.taskId, unminimizeReason = UnminimizeReason.UNKNOWN) + + val wct = getLatestDesktopMixedTaskWct(type = TRANSIT_OPEN) + verify(desksOrganizer).minimizeTask(wct, deskId, freeformTasks[0]) + } + + @Test fun moveToNextDisplay_noOtherDisplays() { whenever(rootTaskDisplayAreaOrganizer.displayIds).thenReturn(intArrayOf(DEFAULT_DISPLAY)) val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY) @@ -2528,7 +2719,8 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test - fun moveToNextDisplay_moveFromFirstToSecondDisplay() { + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun moveToNextDisplay_moveFromFirstToSecondDisplay_multiDesksDisabled() { // Set up two display ids taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY) whenever(rootTaskDisplayAreaOrganizer.displayIds) @@ -2554,7 +2746,27 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test - fun moveToNextDisplay_moveFromSecondToFirstDisplay() { + @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun moveToNextDisplay_moveFromFirstToSecondDisplay_multiDesksEnabled() { + // Set up two display ids + val targetDeskId = 2 + taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = targetDeskId) + whenever(rootTaskDisplayAreaOrganizer.displayIds) + .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY)) + // Create a mock for the target display area: second display + val secondDisplayArea = DisplayAreaInfo(MockToken().token(), SECOND_DISPLAY, 0) + whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(SECOND_DISPLAY)) + .thenReturn(secondDisplayArea) + + val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY) + controller.moveToNextDisplay(task.taskId) + + verify(desksOrganizer).moveTaskToDesk(any(), eq(targetDeskId), eq(task)) + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun moveToNextDisplay_moveFromSecondToFirstDisplay_multiDesksDisabled() { // Set up two display ids whenever(rootTaskDisplayAreaOrganizer.displayIds) .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY)) @@ -2580,6 +2792,25 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test + @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun moveToNextDisplay_moveFromSecondToFirstDisplay_multiDesksEnabled() { + // Set up two display ids + val targetDeskId = 0 + whenever(rootTaskDisplayAreaOrganizer.displayIds) + .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY)) + // Create a mock for the target display area: default display + val defaultDisplayArea = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0) + whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)) + .thenReturn(defaultDisplayArea) + + taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY) + val task = setUpFreeformTask(displayId = SECOND_DISPLAY) + controller.moveToNextDisplay(task.taskId) + + verify(desksOrganizer).moveTaskToDesk(any(), eq(targetDeskId), eq(task)) + } + + @Test @EnableFlags( FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY, Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER, @@ -2643,6 +2874,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() @EnableFlags(FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT) fun moveToNextDisplay_sizeInDpPreserved() { // Set up two display ids + taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = 2) whenever(rootTaskDisplayAreaOrganizer.displayIds) .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY)) // Create a mock for the target display area: second display @@ -2684,6 +2916,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() @EnableFlags(FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT) fun moveToNextDisplay_shiftWithinDestinationDisplayBounds() { // Set up two display ids + taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = 2) whenever(rootTaskDisplayAreaOrganizer.displayIds) .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY)) // Create a mock for the target display area: second display @@ -2725,6 +2958,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() // Set up two display ids whenever(rootTaskDisplayAreaOrganizer.displayIds) .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY)) + taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = 2) // Create a mock for the target display area: second display val secondDisplayArea = DisplayAreaInfo(MockToken().token(), SECOND_DISPLAY, 0) whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(SECOND_DISPLAY)) @@ -2833,7 +3067,8 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test - fun moveToNextDisplay_toDesktopInOtherDisplay_bringsExistingTasksToFront() { + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun moveToNextDisplay_toDesktopInOtherDisplay_multiDesksDisabled_bringsExistingTasksToFront() { val transition = Binder() val sourceDeskId = 0 val targetDeskId = 2 @@ -2859,6 +3094,34 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test + @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun moveToNextDisplay_toDesktopInOtherDisplay_multiDesksEnabled_bringsExistingTasksToFront() { + val transition = Binder() + val sourceDeskId = 0 + val targetDeskId = 2 + taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = targetDeskId) + taskRepository.setDeskInactive(deskId = targetDeskId) + // Set up two display ids + whenever(rootTaskDisplayAreaOrganizer.displayIds) + .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY)) + // Create a mock for the target display area: second display + val secondDisplayArea = DisplayAreaInfo(MockToken().token(), SECOND_DISPLAY, 0) + whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(SECOND_DISPLAY)) + .thenReturn(secondDisplayArea) + whenever(transitions.startTransition(eq(TRANSIT_CHANGE), any(), anyOrNull())) + .thenReturn(transition) + val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = sourceDeskId) + val task2 = setUpFreeformTask(displayId = SECOND_DISPLAY, deskId = targetDeskId) + + controller.moveToNextDisplay(task1.taskId) + + // Existing desktop task in the target display is moved to front. + val wct = getLatestTransition() + assertNotNull(wct) + verify(desksOrganizer).reorderTaskToFront(wct, targetDeskId, task2) + } + + @Test @EnableFlags( Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY, @@ -2972,7 +3235,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() @Test fun moveToNextDisplay_movingToDesktop_sendsTaskbarRoundingUpdate() { val transition = Binder() - val sourceDeskId = 1 + val sourceDeskId = 0 val targetDeskId = 2 taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = targetDeskId) taskRepository.setDeskInactive(deskId = targetDeskId) @@ -3489,7 +3752,8 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test - fun handleRequest_fullscreenTask_freeformVisible_returnSwitchToFreeformWCT() { + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun handleRequest_fullscreenTask_freeformVisible_multiDesksDisabled_returnSwitchToFreeformWCT() { val homeTask = setUpHomeTask() val freeformTask = setUpFreeformTask() markTaskVisible(freeformTask) @@ -3505,7 +3769,22 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test - fun handleRequest_fullscreenTaskWithTaskOnHome_freeformVisible_returnSwitchToFreeformWCT() { + @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun handleRequest_fullscreenTask_deskActive_multiDesksEnabled_movesToDesk() { + val deskId = 0 + taskRepository.setActiveDesk(DEFAULT_DISPLAY, deskId = deskId) + setUpHomeTask() + val fullscreenTask = createFullscreenTask() + + val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask)) + + assertNotNull(wct, "should handle request") + verify(desksOrganizer).moveTaskToDesk(wct, deskId, fullscreenTask) + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun handleRequest_fullscreenTaskWithTaskOnHome_freeformVisible_multiDesksDisabled_returnSwitchToFreeformWCT() { val homeTask = setUpHomeTask() val freeformTask = setUpFreeformTask() markTaskVisible(freeformTask) @@ -3530,7 +3809,23 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test - fun handleRequest_fullscreenTaskToFreeform_underTaskLimit_dontMinimize() { + @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun handleRequest_fullscreenTaskWithTaskOnHome_activeDesk_multiDesksEnabled_movesToDesk() { + val deskId = 0 + setUpHomeTask() + setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId) + val fullscreenTask = createFullscreenTask() + fullscreenTask.baseIntent.setFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME) + + val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask)) + + assertNotNull(wct, "should handle request") + verify(desksOrganizer).moveTaskToDesk(wct, deskId, fullscreenTask) + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun handleRequest_fullscreenTaskToDesk_underTaskLimit_multiDesksDisabled_dontMinimize() { val freeformTask = setUpFreeformTask() markTaskVisible(freeformTask) val fullscreenTask = createFullscreenTask() @@ -3543,7 +3838,29 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test - fun handleRequest_fullscreenTaskToFreeform_bringsTasksOverLimit_otherTaskIsMinimized() { + @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun handleRequest_fullscreenTaskToDesk_underTaskLimit_multiDesksEnabled_dontMinimize() { + val deskId = 5 + taskRepository.addDesk(displayId = DEFAULT_DISPLAY, deskId = deskId) + taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = deskId) + val freeformTask = + setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId).also { + markTaskVisible(it) + } + + // Launch a fullscreen task while in the desk. + val fullscreenTask = createFullscreenTask() + val transition = Binder() + val wct = controller.handleRequest(transition, createTransition(fullscreenTask)) + + assertNotNull(wct) + verify(desksOrganizer, never()).minimizeTask(eq(wct), eq(deskId), any()) + assertNull(desktopTasksLimiter.getMinimizingTask(transition)) + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun handleRequest_fullscreenTaskToDesk_bringsTasksOverLimit_multiDesksDisabled_otherTaskIsMinimized() { val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() } freeformTasks.forEach { markTaskVisible(it) } val fullscreenTask = createFullscreenTask() @@ -3557,7 +3874,34 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test - fun handleRequest_fullscreenTaskWithTaskOnHome_bringsTasksOverLimit_otherTaskIsMinimized() { + @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun handleRequest_fullscreenTaskToDesk_bringsTasksOverLimit_multiDesksEnabled_otherTaskIsMinimized() { + val deskId = 5 + taskRepository.addDesk(displayId = DEFAULT_DISPLAY, deskId = deskId) + taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = deskId) + val freeformTasks = + (1..MAX_TASK_LIMIT).map { _ -> + setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId).also { + markTaskVisible(it) + } + } + + // Launch a fullscreen task while in the desk. + setUpHomeTask() + val fullscreenTask = createFullscreenTask() + val transition = Binder() + val wct = controller.handleRequest(transition, createTransition(fullscreenTask)) + + assertNotNull(wct) + verify(desksOrganizer).minimizeTask(wct, deskId, freeformTasks[0]) + val minimizingTask = + assertNotNull(desktopTasksLimiter.getMinimizingTask(transition)?.taskId) + assertThat(minimizingTask).isEqualTo(freeformTasks[0].taskId) + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun handleRequest_fullscreenTaskWithTaskOnHome_bringsTasksOverLimit_multiDesksDisabled_otherTaskIsMinimized() { val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() } freeformTasks.forEach { markTaskVisible(it) } val fullscreenTask = createFullscreenTask() @@ -3578,7 +3922,31 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test - fun handleRequest_fullscreenTaskWithTaskOnHome_beyondLimit_existingAndNewTasksAreMinimized() { + @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun handleRequest_fullscreenTaskWithTaskOnHome_bringsTasksOverLimit_multiDesksEnabled_otherTaskIsMinimized() { + val deskId = 5 + taskRepository.addDesk(displayId = DEFAULT_DISPLAY, deskId = deskId) + taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = deskId) + val freeformTasks = + (1..MAX_TASK_LIMIT).map { _ -> + setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId) + } + freeformTasks.forEach { markTaskVisible(it) } + val fullscreenTask = createFullscreenTask() + fullscreenTask.baseIntent.setFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME) + + val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask)) + + assertNotNull(wct) + // The launching task is moved to the desk. + verify(desksOrganizer).moveTaskToDesk(wct, deskId, fullscreenTask) + // The bottom-most task is minimized. + verify(desksOrganizer).minimizeTask(wct, deskId, freeformTasks[0]) + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun handleRequest_fullscreenTaskWithTaskOnHome_beyondLimit_multiDesksDisabled_existingAndNewTasksAreMinimized() { val minimizedTask = setUpFreeformTask() taskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = minimizedTask.taskId) val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() } @@ -3604,7 +3972,90 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test + @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun handleRequest_fullscreenTaskWithTaskOnHome_beyondLimit_multiDesksEnabled_existingAndNewTasksAreMinimized() { + // A desk with a minimized tasks, and non-minimized tasks already at the task limit. + val deskId = 5 + taskRepository.addDesk(displayId = DEFAULT_DISPLAY, deskId = deskId) + taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = deskId) + val minimizedTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId) + taskRepository.minimizeTaskInDesk( + displayId = DEFAULT_DISPLAY, + deskId = deskId, + taskId = minimizedTask.taskId, + ) + val freeformTasks = + (1..MAX_TASK_LIMIT).map { _ -> + setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId).also { + markTaskVisible(it) + } + } + + // Launch a fullscreen task that brings Home to front with it. + setUpHomeTask() + val fullscreenTask = createFullscreenTask() + fullscreenTask.baseIntent.setFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME) + val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask)) + + assertNotNull(wct) + verify(desksOrganizer).minimizeTask(wct, deskId, freeformTasks[0]) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun handleRequest_fullscreenTaskWithTaskOnHome_taskAddedToDesk() { + // A desk with a minimized tasks, and non-minimized tasks already at the task limit. + val deskId = 5 + taskRepository.addDesk(displayId = DEFAULT_DISPLAY, deskId = deskId) + taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = deskId) + + // Launch a fullscreen task that brings Home to front with it. + setUpHomeTask() + val fullscreenTask = createFullscreenTask() + fullscreenTask.baseIntent.setFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME) + val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask)) + + assertNotNull(wct) + verify(desksOrganizer).moveTaskToDesk(wct, deskId, fullscreenTask) + } + + @Test + @EnableFlags( + Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, + Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY, + Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER, + ) + fun handleRequest_fullscreenTaskWithTaskOnHome_activatesDesk() { + val deskId = 5 + taskRepository.addDesk(displayId = DEFAULT_DISPLAY, deskId = deskId) + taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = deskId) + + // Launch a fullscreen task that brings Home to front with it. + val homeTask = setUpHomeTask() + val fullscreenTask = createFullscreenTask() + fullscreenTask.baseIntent.setFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME) + val transition = Binder() + val wct = controller.handleRequest(transition, createTransition(fullscreenTask)) + + assertNotNull(wct) + wct.assertReorder(homeTask, toTop = true) + wct.assertReorder(wallpaperToken, toTop = true) + verify(desksOrganizer).activateDesk(wct, deskId) + verify(desksTransitionsObserver) + .addPendingTransition( + DeskTransition.ActiveDeskWithTask( + token = transition, + displayId = DEFAULT_DISPLAY, + deskId = deskId, + enterTaskId = fullscreenTask.taskId, + ) + ) + } + + @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) fun handleRequest_fullscreenTask_noTasks_enforceDesktop_freeformDisplay_returnFreeformWCT() { whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null) whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true) @@ -3627,7 +4078,28 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test - fun handleRequest_fullscreenTask_noTasks_enforceDesktop_fullscreenDisplay_returnNull() { + @EnableFlags( + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, + Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND, + ) + fun handleRequest_fullscreenTask_noInDesk_enforceDesktop_freeformDisplay_movesToDesk() { + val deskId = 0 + taskRepository.setDeskInactive(deskId) + whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null) + whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true) + val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!! + tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM + + val fullscreenTask = createFullscreenTask() + val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask)) + + assertNotNull(wct, "should handle request") + verify(desksOrganizer).moveTaskToDesk(wct, deskId, fullscreenTask) + } + + @Test + fun handleRequest_fullscreenTask_notInDesk_enforceDesktop_fullscreenDisplay_returnNull() { + taskRepository.setDeskInactive(deskId = 0) whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true) val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!! tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN @@ -3639,6 +4111,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) fun handleRequest_fullscreenTask_freeformNotVisible_returnNull() { val freeformTask = setUpFreeformTask() markTaskHidden(freeformTask) @@ -3647,12 +4120,23 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) fun handleRequest_fullscreenTask_noOtherTasks_returnNull() { val fullscreenTask = createFullscreenTask() assertThat(controller.handleRequest(Binder(), createTransition(fullscreenTask))).isNull() } @Test + @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun handleRequest_fullscreenTask_notInDesk_returnNull() { + taskRepository.setDeskInactive(deskId = 0) + val fullscreenTask = createFullscreenTask() + + assertThat(controller.handleRequest(Binder(), createTransition(fullscreenTask))).isNull() + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) fun handleRequest_fullscreenTask_freeformTaskOnOtherDisplay_returnNull() { val fullscreenTaskDefaultDisplay = createFullscreenTask(displayId = DEFAULT_DISPLAY) createFreeformTask(displayId = SECOND_DISPLAY) @@ -3663,8 +4147,27 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test - fun handleRequest_freeformTask_freeformVisible_aboveTaskLimit_minimize() { - val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() } + @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun handleRequest_fullscreenTask_deskInOtherDisplayActive_returnNull() { + taskRepository.setDeskInactive(deskId = 0) + val fullscreenTaskDefaultDisplay = createFullscreenTask(displayId = DEFAULT_DISPLAY) + taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = 2) + taskRepository.setActiveDesk(displayId = SECOND_DISPLAY, deskId = 2) + + val result = + controller.handleRequest(Binder(), createTransition(fullscreenTaskDefaultDisplay)) + + assertThat(result).isNull() + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun handleRequest_freeformTask_freeformVisible_aboveTaskLimit_multiDesksDisabled_minimize() { + val deskId = 0 + val freeformTasks = + (1..MAX_TASK_LIMIT).map { _ -> + setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId) + } freeformTasks.forEach { markTaskVisible(it) } val newFreeformTask = createFreeformTask() @@ -3677,6 +4180,24 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() @Test @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun handleRequest_freeformTask_freeformVisible_aboveTaskLimit_multiDesksEnabled_minimize() { + val deskId = 0 + val freeformTasks = + (1..MAX_TASK_LIMIT).map { _ -> + setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId) + } + freeformTasks.forEach { markTaskVisible(it) } + val newFreeformTask = createFreeformTask() + + val wct = + controller.handleRequest(Binder(), createTransition(newFreeformTask, TRANSIT_OPEN)) + + assertNotNull(wct) + verify(desksOrganizer).minimizeTask(wct, deskId, freeformTasks[0]) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) fun handleRequest_freeformTaskFromInactiveDesk_tracksDeskDeactivation() { val deskId = 0 val freeformTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId) @@ -3691,7 +4212,8 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() @Test fun handleRequest_freeformTask_relaunchActiveTask_taskBecomesUndefined() { - val freeformTask = setUpFreeformTask() + taskRepository.setDeskInactive(deskId = 0) + val freeformTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = 0) markTaskHidden(freeformTask) val wct = controller.handleRequest(Binder(), createTransition(freeformTask)) @@ -3721,9 +4243,10 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true) val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!! tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN - - val freeformTask = setUpFreeformTask() + taskRepository.setDeskInactive(deskId = 0) + val freeformTask = setUpFreeformTask(DEFAULT_DISPLAY, deskId = 0) markTaskHidden(freeformTask) + val wct = controller.handleRequest(Binder(), createTransition(freeformTask)) assertNotNull(wct, "should handle request") @@ -3732,7 +4255,10 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test - @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + @DisableFlags( + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, + Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND, + ) fun handleRequest_freeformTask_desktopWallpaperDisabled_freeformNotVisible_reorderedToTop() { val freeformTask1 = setUpFreeformTask() val freeformTask2 = createFreeformTask() @@ -3751,7 +4277,8 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun handleRequest_freeformTask_desktopWallpaperEnabled_freeformNotVisible_reorderedToTop() { + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun handleRequest_freeformTask_desktopWallpaperEnabled_freeformNotVisible_multiDesksDisabled_reorderedToTop() { whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null) val freeformTask1 = setUpFreeformTask() val freeformTask2 = createFreeformTask() @@ -3774,34 +4301,47 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test - @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun handleRequest_freeformTask_desktopWallpaperDisabled_noOtherTasks_reorderedToTop() { - val task = createFreeformTask() - val result = controller.handleRequest(Binder(), createTransition(task)) + @EnableFlags( + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, + Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND, + ) + fun handleRequest_freeformTask_desktopWallpaperEnabled_notInDesk_reorderedToTop() { + whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null) + val deskId = 0 + taskRepository.setDeskInactive(deskId) + val freeformTask1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId) + val freeformTask2 = createFreeformTask() - assertNotNull(result, "Should handle request") - assertThat(result.hierarchyOps?.size).isEqualTo(1) - result.assertReorderAt(0, task, toTop = true) + val wct = + controller.handleRequest( + Binder(), + createTransition(freeformTask2, type = TRANSIT_TO_FRONT), + ) + + assertNotNull(wct, "Should handle request") + verify(desksOrganizer).reorderTaskToFront(wct, deskId, freeformTask1) + wct.assertReorder(freeformTask2, toTop = true) } @Test - @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun handleRequest_freeformTask_desktopWallpaperEnabled_noOtherTasks_reorderedToTop() { - whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null) + @DisableFlags( + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, + Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND, + ) + fun handleRequest_freeformTask_desktopWallpaperDisabled_noOtherTasks_reorderedToTop() { val task = createFreeformTask() - val result = controller.handleRequest(Binder(), createTransition(task)) assertNotNull(result, "Should handle request") - assertThat(result.hierarchyOps?.size).isEqualTo(2) - // Add desktop wallpaper activity - result.assertPendingIntentAt(0, desktopWallpaperIntent) - // Bring new task to front - result.assertReorderAt(1, task, toTop = true) + assertThat(result.hierarchyOps?.size).isEqualTo(1) + result.assertReorderAt(0, task, toTop = true) } @Test - @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) + @DisableFlags( + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, + Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND, + ) fun handleRequest_freeformTask_dskWallpaperDisabled_freeformOnOtherDisplayOnly_reorderedToTop() { val taskDefaultDisplay = createFreeformTask(displayId = DEFAULT_DISPLAY) // Second display task @@ -3817,10 +4357,13 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) fun handleRequest_freeformTask_dskWallpaperEnabled_freeformOnOtherDisplayOnly_reorderedToTop() { + taskRepository.setDeskInactive(deskId = 0) whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null) val taskDefaultDisplay = createFreeformTask(displayId = DEFAULT_DISPLAY) // Second display task - createFreeformTask(displayId = SECOND_DISPLAY) + taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = 2) + taskRepository.setActiveDesk(displayId = SECOND_DISPLAY, deskId = 2) + setUpFreeformTask(displayId = SECOND_DISPLAY, deskId = 2) val result = controller.handleRequest(Binder(), createTransition(taskDefaultDisplay)) @@ -3956,7 +4499,8 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) - fun handleRequest_topActivityTransparentWithoutDisplay_returnSwitchToFreeformWCT() { + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun handleRequest_topActivityTransparentWithoutDisplay_multiDesksDisabled_returnSwitchToFreeformWCT() { val freeformTask = setUpFreeformTask() markTaskVisible(freeformTask) @@ -3973,6 +4517,29 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test + @EnableFlags( + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY, + Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND, + ) + fun handleRequest_topActivityTransparentWithoutDisplay_multiDesksEnabled_returnSwitchToFreeformWCT() { + val deskId = 0 + val freeformTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId) + markTaskVisible(freeformTask) + + val task = + setUpFullscreenTask().apply { + isActivityStackTransparent = true + isTopActivityNoDisplay = true + numActivities = 1 + } + + val wct = controller.handleRequest(Binder(), createTransition(task)) + + assertNotNull(wct) + verify(desksOrganizer).moveTaskToDesk(wct, deskId, task) + } + + @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) @DisableFlags(Flags.FLAG_ENABLE_MODALS_FULLSCREEN_WITH_PERMISSION) fun handleRequest_topActivityTransparentWithDisplay_returnSwitchToFullscreenWCT() { @@ -4031,7 +4598,8 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY, Flags.FLAG_INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC, ) - fun handleRequest_onlyTopTransparentFullscreenTask_returnSwitchToFreeformWCT() { + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun handleRequest_onlyTopTransparentFullscreenTask_multiDesksDisabled_returnSwitchToFreeformWCT() { val topTransparentTask = setUpFullscreenTask(displayId = DEFAULT_DISPLAY) taskRepository.setTopTransparentFullscreenTaskId(DEFAULT_DISPLAY, topTransparentTask.taskId) @@ -4043,8 +4611,28 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test + @EnableFlags( + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY, + Flags.FLAG_INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC, + Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND, + ) + fun handleRequest_onlyTopTransparentFullscreenTask_multiDesksEnabled_movesToDesktop() { + val deskId = 0 + val topTransparentTask = setUpFullscreenTask(displayId = DEFAULT_DISPLAY) + taskRepository.setTopTransparentFullscreenTaskId(DEFAULT_DISPLAY, topTransparentTask.taskId) + taskRepository.setDeskInactive(deskId = deskId) + + val task = setUpFullscreenTask(displayId = DEFAULT_DISPLAY) + + val wct = controller.handleRequest(Binder(), createTransition(task)) + assertNotNull(wct) + verify(desksOrganizer).moveTaskToDesk(wct, deskId, task) + } + + @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) fun handleRequest_desktopNotShowing_topTransparentFullscreenTask_returnNull() { + taskRepository.setDeskInactive(deskId = 0) val task = setUpFullscreenTask(displayId = DEFAULT_DISPLAY) assertThat(controller.handleRequest(Binder(), createTransition(task))).isNull() @@ -4098,7 +4686,8 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) - fun handleRequest_systemUIActivityWithoutDisplay_returnSwitchToFreeformWCT() { + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun handleRequest_systemUIActivityWithoutDisplay_multiDesksDisabled_returnSwitchToFreeformWCT() { val freeformTask = setUpFreeformTask() markTaskVisible(freeformTask) @@ -4117,6 +4706,32 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() .isEqualTo(WINDOWING_MODE_FREEFORM) } + @Test + @EnableFlags( + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY, + Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND, + ) + fun handleRequest_systemUIActivityWithoutDisplay_multiDesksEnabled_movesTaskToDesk() { + val deskId = 0 + val freeformTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId) + markTaskVisible(freeformTask) + + // Set task as systemUI package + val systemUIPackageName = + context.resources.getString(com.android.internal.R.string.config_systemUi) + val baseComponent = ComponentName(systemUIPackageName, /* cls= */ "") + val task = + setUpFullscreenTask(displayId = DEFAULT_DISPLAY).apply { + baseActivity = baseComponent + isTopActivityNoDisplay = true + } + + val wct = controller.handleRequest(Binder(), createTransition(task)) + + assertNotNull(wct) + verify(desksOrganizer).moveTaskToDesk(wct, deskId, task) + } + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) fun handleRequest_defaultHomePackageWithDisplay_returnSwitchToFullscreenWCT() { val freeformTask = setUpFreeformTask() @@ -4159,6 +4774,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() @Test fun handleRequest_systemUIActivityWithDisplay_returnSwitchToFullscreenWCT_enforcedDesktop() { + taskRepository.setDeskInactive(deskId = 0) whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true) val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!! tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM @@ -4792,6 +5408,55 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION, Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND, ) + fun activateDesk_hasNonRunningTask_startsTask() { + val deskId = 0 + val nonRunningTask = + setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = 0, background = true) + + val transition = Binder() + val deskChange = mock(TransitionInfo.Change::class.java) + whenever(transitions.startTransition(eq(TRANSIT_TO_FRONT), any(), anyOrNull())) + .thenReturn(transition) + whenever(desksOrganizer.isDeskActiveAtEnd(deskChange, deskId)).thenReturn(true) + // Make desk inactive by activating another desk. + taskRepository.addDesk(DEFAULT_DISPLAY, deskId = 1) + taskRepository.setActiveDesk(DEFAULT_DISPLAY, deskId = 1) + + controller.activateDesk(deskId, RemoteTransition(TestRemoteTransition())) + + val wct = getLatestWct(TRANSIT_TO_FRONT, OneShotRemoteHandler::class.java) + assertNotNull(wct) + wct.assertLaunchTask(nonRunningTask.taskId, WINDOWING_MODE_FREEFORM) + } + + @Test + @EnableFlags( + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION, + Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND, + ) + fun activateDesk_hasRunningTask_reordersTask() { + val deskId = 0 + val runningTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = 0) + + val transition = Binder() + val deskChange = mock(TransitionInfo.Change::class.java) + whenever(transitions.startTransition(eq(TRANSIT_TO_FRONT), any(), anyOrNull())) + .thenReturn(transition) + whenever(desksOrganizer.isDeskActiveAtEnd(deskChange, deskId)).thenReturn(true) + // Make desk inactive by activating another desk. + taskRepository.addDesk(DEFAULT_DISPLAY, deskId = 1) + taskRepository.setActiveDesk(DEFAULT_DISPLAY, deskId = 1) + + controller.activateDesk(deskId, RemoteTransition(TestRemoteTransition())) + + verify(desksOrganizer).reorderTaskToFront(any(), eq(deskId), eq(runningTask)) + } + + @Test + @EnableFlags( + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION, + Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND, + ) fun moveTaskToDesk_multipleDesks_addsPendingTransition() { val transition = Binder() whenever(enterDesktopTransitionHandler.moveToDesktop(any(), any())).thenReturn(transition) @@ -5578,7 +6243,8 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES) - fun openInstance_fromFreeformAddsNewWindow() { + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun openInstance_fromFreeformAddsNewWindow_multiDesksDisabled() { setUpLandscapeDisplay() val task = setUpFreeformTask() val taskToRequest = setUpFreeformTask() @@ -5592,7 +6258,9 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() ) ) .thenReturn(Binder()) + runOpenInstance(task, taskToRequest.taskId) + verify(desktopMixedTransitionHandler) .startLaunchTransition(anyInt(), any(), anyInt(), anyOrNull(), anyOrNull()) val wct = getLatestDesktopMixedTaskWct(type = TRANSIT_TO_FRONT) @@ -5601,10 +6269,42 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test + @EnableFlags( + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES, + Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND, + ) + fun openInstance_fromFreeformAddsNewWindow_multiDesksEnabled() { + setUpLandscapeDisplay() + val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = 0) + val taskToRequest = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = 0) + whenever( + desktopMixedTransitionHandler.startLaunchTransition( + eq(TRANSIT_TO_FRONT), + any(), + eq(taskToRequest.taskId), + anyOrNull(), + anyOrNull(), + ) + ) + .thenReturn(Binder()) + + runOpenInstance(task, taskToRequest.taskId) + + verify(desktopMixedTransitionHandler) + .startLaunchTransition(anyInt(), any(), anyInt(), anyOrNull(), anyOrNull()) + verify(desksOrganizer).reorderTaskToFront(any(), eq(0), eq(taskToRequest)) + } + + @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES) - fun openInstance_fromFreeform_minimizesIfNeeded() { + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun openInstance_fromFreeform_multiDesksDisabled_minimizesIfNeeded() { setUpLandscapeDisplay() - val freeformTasks = (1..MAX_TASK_LIMIT + 1).map { _ -> setUpFreeformTask() } + val deskId = 0 + val freeformTasks = + (1..MAX_TASK_LIMIT + 1).map { _ -> + setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId) + } val oldestTask = freeformTasks.first() val newestTask = freeformTasks.last() @@ -5630,6 +6330,40 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test + @EnableFlags( + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES, + Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND, + ) + fun openInstance_fromFreeform_multiDesksEnabled_minimizesIfNeeded() { + setUpLandscapeDisplay() + val deskId = 0 + val freeformTasks = + (1..MAX_TASK_LIMIT + 1).map { _ -> + setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId) + } + val oldestTask = freeformTasks.first() + val newestTask = freeformTasks.last() + + val transition = Binder() + val wctCaptor = argumentCaptor<WindowContainerTransaction>() + whenever( + desktopMixedTransitionHandler.startLaunchTransition( + anyInt(), + wctCaptor.capture(), + anyInt(), + anyOrNull(), + anyOrNull(), + ) + ) + .thenReturn(transition) + + runOpenInstance(newestTask, freeformTasks[1].taskId) + + val wct = wctCaptor.firstValue + verify(desksOrganizer).minimizeTask(wct, deskId, oldestTask) + } + + @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES) fun openInstance_fromFreeform_exitsImmersiveIfNeeded() { setUpLandscapeDisplay() @@ -6447,6 +7181,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() @Test @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) fun shouldPlayDesktopAnimation_notShowingDesktop_doesNotPlay() { + taskRepository.setDeskInactive(deskId = 0) val triggerTask = setUpFullscreenTask(displayId = DEFAULT_DISPLAY) taskRepository.setTaskInFullImmersiveState( displayId = triggerTask.displayId, @@ -6523,6 +7258,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) fun shouldPlayDesktopAnimation_fullscreenStaysFullscreen_doesNotPlay() { val triggerTask = setUpFullscreenTask(displayId = DEFAULT_DISPLAY) + taskRepository.setDeskInactive(deskId = 0) assertThat(controller.isDesktopModeShowing(triggerTask.displayId)).isFalse() assertThat( @@ -6558,6 +7294,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) fun shouldPlayDesktopAnimation_freeformExitsDesktop_doesNotPlay() { val triggerTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY, active = false) + taskRepository.setDeskInactive(deskId = 0) assertThat(controller.isDesktopModeShowing(triggerTask.displayId)).isFalse() assertThat( @@ -6587,7 +7324,8 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER, ) fun startLaunchTransition_desktopNotShowing_movesWallpaperToFront() { - val launchingTask = createFreeformTask() + taskRepository.setDeskInactive(deskId = 0) + val launchingTask = createFreeformTask(displayId = DEFAULT_DISPLAY) val wct = WindowContainerTransaction() wct.reorder(launchingTask.token, /* onTop= */ true) whenever( @@ -6601,7 +7339,13 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() ) .thenReturn(Binder()) - controller.startLaunchTransition(TRANSIT_OPEN, wct, launchingTaskId = null) + controller.startLaunchTransition( + transitionType = TRANSIT_OPEN, + wct = wct, + launchingTaskId = null, + deskId = 0, + displayId = DEFAULT_DISPLAY, + ) val latestWct = getLatestDesktopMixedTaskWct(type = TRANSIT_OPEN) val launchingTaskReorderIndex = latestWct.indexOfReorder(launchingTask, toTop = true) @@ -6625,6 +7369,8 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() transitionType = TRANSIT_OPEN, wct = WindowContainerTransaction(), launchingTaskId = null, + deskId = 0, + displayId = DEFAULT_DISPLAY, ) verify(desktopModeEnterExitTransitionListener).onEnterDesktopModeTransitionStarted(any()) @@ -6634,6 +7380,51 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() @EnableFlags( Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER, + Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND, + ) + fun startLaunchTransition_launchingTaskFromInactiveDesk_otherDeskActive_activatesDesk() { + val activeDeskId = 4 + taskRepository.addDesk(displayId = DEFAULT_DISPLAY, deskId = activeDeskId) + taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = activeDeskId) + val inactiveDesk = 5 + taskRepository.addDesk(displayId = DEFAULT_DISPLAY, deskId = inactiveDesk) + val launchingTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = inactiveDesk) + val transition = Binder() + whenever( + desktopMixedTransitionHandler.startLaunchTransition( + eq(TRANSIT_OPEN), + any(), + eq(launchingTask.taskId), + anyOrNull(), + anyOrNull(), + ) + ) + .thenReturn(transition) + + val wct = WindowContainerTransaction() + controller.startLaunchTransition( + transitionType = TRANSIT_OPEN, + wct = wct, + launchingTaskId = launchingTask.taskId, + deskId = inactiveDesk, + displayId = DEFAULT_DISPLAY, + ) + + verify(desksOrganizer).activateDesk(any(), eq(inactiveDesk)) + verify(desksTransitionsObserver) + .addPendingTransition( + DeskTransition.ActivateDesk( + token = transition, + displayId = DEFAULT_DISPLAY, + deskId = inactiveDesk, + ) + ) + } + + @Test + @EnableFlags( + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY, + Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER, ) fun startLaunchTransition_desktopShowing_doesNotReorderWallpaper() { val wct = WindowContainerTransaction() @@ -6648,8 +7439,14 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() ) .thenReturn(Binder()) - setUpFreeformTask() - controller.startLaunchTransition(TRANSIT_OPEN, wct, launchingTaskId = null) + setUpFreeformTask(deskId = 0, displayId = DEFAULT_DISPLAY) + controller.startLaunchTransition( + transitionType = TRANSIT_OPEN, + wct = wct, + launchingTaskId = null, + deskId = 0, + displayId = DEFAULT_DISPLAY, + ) val latestWct = getLatestDesktopMixedTaskWct(type = TRANSIT_OPEN) assertNull(latestWct.hierarchyOps.find { op -> op.container == wallpaperToken.asBinder() }) @@ -6997,7 +7794,15 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } private fun findBoundsChange(wct: WindowContainerTransaction, task: RunningTaskInfo): Rect? = - wct.changes[task.token.asBinder()]?.configuration?.windowConfiguration?.bounds + wct.changes.entries + .find { (token, change) -> + token == task.token.asBinder() && + (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 + } + ?.value + ?.configuration + ?.windowConfiguration + ?.bounds private fun verifyWCTNotExecuted() { verify(transitions, never()).startTransition(anyInt(), any(), isNull()) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt index 76103640c029..eeecb00b5b08 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt @@ -39,12 +39,14 @@ import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn import com.android.dx.mockito.inline.extended.StaticMockitoSession import com.android.internal.jank.InteractionJankMonitor import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION +import com.android.window.flags.Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.ShellTestCase import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UnminimizeReason import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask +import com.android.wm.shell.desktopmode.multidesks.DesksOrganizer import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer import com.android.wm.shell.shared.desktopmode.DesktopModeStatus @@ -67,10 +69,13 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock -import org.mockito.Mockito.any import org.mockito.Mockito.mock import org.mockito.Mockito.spy +import org.mockito.Mockito.verify import org.mockito.Mockito.`when` +import org.mockito.kotlin.any +import org.mockito.kotlin.eq +import org.mockito.kotlin.never import org.mockito.quality.Strictness /** @@ -84,6 +89,7 @@ import org.mockito.quality.Strictness class DesktopTasksLimiterTest : ShellTestCase() { @Mock lateinit var shellTaskOrganizer: ShellTaskOrganizer + @Mock lateinit var desksOrganizer: DesksOrganizer @Mock lateinit var transitions: Transitions @Mock lateinit var interactionJankMonitor: InteractionJankMonitor @Mock lateinit var handler: Handler @@ -128,6 +134,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { transitions, userRepositories, shellTaskOrganizer, + desksOrganizer, MAX_TASK_LIMIT, interactionJankMonitor, mContext, @@ -148,6 +155,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { transitions, userRepositories, shellTaskOrganizer, + desksOrganizer, 0, interactionJankMonitor, mContext, @@ -163,6 +171,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { transitions, userRepositories, shellTaskOrganizer, + desksOrganizer, -5, interactionJankMonitor, mContext, @@ -178,6 +187,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { transitions, userRepositories, shellTaskOrganizer, + desksOrganizer, maxTasksLimit = null, interactionJankMonitor, mContext, @@ -394,7 +404,8 @@ class DesktopTasksLimiterTest : ShellTestCase() { } @Test - fun addAndGetMinimizeTaskChanges_tasksWithinLimit_noTaskMinimized() { + @DisableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun addAndGetMinimizeTaskChanges_tasksWithinLimit_multiDesksDisabled_noTaskMinimized() { desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0) desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0) (1..<MAX_TASK_LIMIT).forEach { _ -> setUpFreeformTask() } @@ -402,7 +413,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { val wct = WindowContainerTransaction() val minimizedTaskId = desktopTasksLimiter.addAndGetMinimizeTaskChanges( - displayId = DEFAULT_DISPLAY, + deskId = 0, wct = wct, newFrontTaskId = setUpFreeformTask().taskId, ) @@ -412,7 +423,27 @@ class DesktopTasksLimiterTest : ShellTestCase() { } @Test - fun addAndGetMinimizeTaskChanges_tasksAboveLimit_backTaskMinimized() { + @EnableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun addAndGetMinimizeTaskChanges_tasksWithinLimit_multiDesksEnabled_noTaskMinimized() { + desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0) + desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0) + (1..<MAX_TASK_LIMIT).forEach { _ -> setUpFreeformTask() } + + val wct = WindowContainerTransaction() + val minimizedTaskId = + desktopTasksLimiter.addAndGetMinimizeTaskChanges( + deskId = 0, + wct = wct, + newFrontTaskId = setUpFreeformTask().taskId, + ) + + assertThat(minimizedTaskId).isNull() + verify(desksOrganizer, never()).minimizeTask(eq(wct), eq(0), any()) + } + + @Test + @DisableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun addAndGetMinimizeTaskChanges_tasksAboveLimit_multiDesksDisabled_backTaskMinimized() { desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0) desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0) // The following list will be ordered bottom -> top, as the last task is moved to top last. @@ -421,7 +452,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { val wct = WindowContainerTransaction() val minimizedTaskId = desktopTasksLimiter.addAndGetMinimizeTaskChanges( - displayId = DEFAULT_DISPLAY, + deskId = DEFAULT_DISPLAY, wct = wct, newFrontTaskId = setUpFreeformTask().taskId, ) @@ -433,7 +464,28 @@ class DesktopTasksLimiterTest : ShellTestCase() { } @Test - fun addAndGetMinimizeTaskChanges_nonMinimizedTasksWithinLimit_noTaskMinimized() { + @EnableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun addAndGetMinimizeTaskChanges_tasksAboveLimit_multiDesksEnabled_backTaskMinimized() { + desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0) + desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0) + // The following list will be ordered bottom -> top, as the last task is moved to top last. + val tasks = (1..MAX_TASK_LIMIT).map { setUpFreeformTask() } + + val wct = WindowContainerTransaction() + val minimizedTaskId = + desktopTasksLimiter.addAndGetMinimizeTaskChanges( + deskId = DEFAULT_DISPLAY, + wct = wct, + newFrontTaskId = setUpFreeformTask().taskId, + ) + + assertThat(minimizedTaskId).isEqualTo(tasks.first().taskId) + verify(desksOrganizer).minimizeTask(wct, deskId = 0, tasks.first()) + } + + @Test + @DisableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun addAndGetMinimizeTaskChanges_nonMinimizedTasksWithinLimit_multiDesksDisabled_noTaskMinimized() { desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0) desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0) val tasks = (1..MAX_TASK_LIMIT).map { setUpFreeformTask() } @@ -442,7 +494,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { val wct = WindowContainerTransaction() val minimizedTaskId = desktopTasksLimiter.addAndGetMinimizeTaskChanges( - displayId = 0, + deskId = 0, wct = wct, newFrontTaskId = setUpFreeformTask().taskId, ) @@ -452,6 +504,26 @@ class DesktopTasksLimiterTest : ShellTestCase() { } @Test + @EnableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun addAndGetMinimizeTaskChanges_nonMinimizedTasksWithinLimit_multiDesksEnabled_noTaskMinimized() { + desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0) + desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0) + val tasks = (1..MAX_TASK_LIMIT).map { setUpFreeformTask() } + desktopTaskRepo.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = tasks[0].taskId) + + val wct = WindowContainerTransaction() + val minimizedTaskId = + desktopTasksLimiter.addAndGetMinimizeTaskChanges( + deskId = 0, + wct = wct, + newFrontTaskId = setUpFreeformTask().taskId, + ) + + assertThat(minimizedTaskId).isNull() + verify(desksOrganizer, never()).minimizeTask(eq(wct), eq(0), any()) + } + + @Test fun getTaskToMinimize_tasksWithinLimit_returnsNull() { desktopTaskRepo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 0) desktopTaskRepo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 0) @@ -485,6 +557,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { transitions, userRepositories, shellTaskOrganizer, + desksOrganizer, MAX_TASK_LIMIT2, interactionJankMonitor, mContext, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt index fd8842b6d99b..a7dc706eb6c9 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt @@ -48,13 +48,11 @@ import com.android.wm.shell.back.BackAnimationController import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_TASK_DRAG import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider -import com.android.wm.shell.desktopmode.multidesks.DesksTransitionObserver import com.android.wm.shell.shared.desktopmode.DesktopModeStatus import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.Transitions import com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP import com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP -import com.android.wm.shell.util.StubTransaction import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertWithMessage import org.junit.Before @@ -95,7 +93,6 @@ class DesktopTasksTransitionObserverTest { private val backAnimationController = mock<BackAnimationController>() private val desktopWallpaperActivityTokenProvider = mock<DesktopWallpaperActivityTokenProvider>() - private val desksTransitionObserver = mock<DesksTransitionObserver>() private val wallpaperToken = MockToken().token() private lateinit var transitionObserver: DesktopTasksTransitionObserver @@ -119,7 +116,6 @@ class DesktopTasksTransitionObserverTest { mixedHandler, backAnimationController, desktopWallpaperActivityTokenProvider, - desksTransitionObserver, shellInit, ) } @@ -403,21 +399,6 @@ class DesktopTasksTransitionObserverTest { verify(taskRepository).setTaskInPip(task.displayId, task.taskId, enterPip = false) } - @Test - fun onTransitionReady_forwardsToDesksTransitionObserver() { - val transition = Binder() - val info = TransitionInfo(TRANSIT_CLOSE, /* flags= */ 0) - - transitionObserver.onTransitionReady( - transition = transition, - info = info, - StubTransaction(), - StubTransaction(), - ) - - verify(desksTransitionObserver).onTransitionReady(transition, info) - } - private fun createBackNavigationTransition( task: RunningTaskInfo?, type: Int = TRANSIT_TO_BACK, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt index c00083b0607f..c40a04c47b9b 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt @@ -41,6 +41,7 @@ object DesktopTestHelpers { .setActivityType(ACTIVITY_TYPE_STANDARD) .setWindowingMode(WINDOWING_MODE_FREEFORM) .setLastActiveTime(100) + .setUserId(DEFAULT_USER_ID) .apply { bounds?.let { setBounds(it) } } .build() @@ -50,6 +51,7 @@ object DesktopTestHelpers { .setToken(MockToken().token()) .setActivityType(ACTIVITY_TYPE_STANDARD) .setWindowingMode(WINDOWING_MODE_FULLSCREEN) + .setUserId(DEFAULT_USER_ID) .setLastActiveTime(100) /** Create a task that has windowing mode set to [WINDOWING_MODE_FULLSCREEN] */ @@ -63,6 +65,7 @@ object DesktopTestHelpers { .setToken(MockToken().token()) .setActivityType(ACTIVITY_TYPE_STANDARD) .setWindowingMode(WINDOWING_MODE_MULTI_WINDOW) + .setUserId(DEFAULT_USER_ID) .setLastActiveTime(100) .build() @@ -72,6 +75,7 @@ object DesktopTestHelpers { .setToken(MockToken().token()) .setActivityType(ACTIVITY_TYPE_HOME) .setWindowingMode(WINDOWING_MODE_FULLSCREEN) + .setUserId(DEFAULT_USER_ID) .setLastActiveTime(100) .build() @@ -91,4 +95,6 @@ object DesktopTestHelpers { createSystemModalTask().apply { baseActivity = ComponentName("com.test.dummypackage", "TestClass") } + + const val DEFAULT_USER_ID = 10 } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt index 0871d38ceb46..6e7adf368155 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt @@ -26,6 +26,7 @@ import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE import com.android.internal.jank.InteractionJankMonitor import com.android.window.flags.Flags +import com.android.window.flags.Flags.FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.ShellTestCase import com.android.wm.shell.TestRunningTaskInfoBuilder @@ -34,6 +35,7 @@ import com.android.wm.shell.bubbles.BubbleTransitions import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP +import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.CancelState import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.Companion.DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT @@ -56,6 +58,7 @@ import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.eq import org.mockito.Mock import org.mockito.MockitoSession +import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.argThat import org.mockito.kotlin.mock import org.mockito.kotlin.never @@ -118,6 +121,14 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { .strictness(Strictness.LENIENT) .mockStatic(SystemProperties::class.java) .startMocking() + whenever( + transitions.startTransition( + eq(TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP), + /* wct= */ any(), + eq(defaultHandler), + ) + ) + .thenReturn(mock<IBinder>()) } @After @@ -679,17 +690,11 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { val startTransition = startDrag(defaultHandler, task) val endTransition = mock<IBinder>() defaultHandler.onTaskResizeAnimationListener = mock() - defaultHandler.mergeAnimation( + mergeAnimation( transition = endTransition, - info = - createTransitionInfo( - type = TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP, - draggedTask = task, - ), - startT = mock<SurfaceControl.Transaction>(), - finishT = mock<SurfaceControl.Transaction>(), + type = TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP, + task = task, mergeTarget = startTransition, - finishCallback = mock<Transitions.TransitionFinishCallback>(), ) defaultHandler.onTransitionConsumed(endTransition, aborted = true, mock()) @@ -701,6 +706,123 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { } @Test + @EnableFlags(FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX) + fun mergeOtherTransition_cancelAndEndNotYetRequested_doesntInterruptsStartDrag() { + val finishCallback = mock<Transitions.TransitionFinishCallback>() + val task = createTask() + defaultHandler.onTaskResizeAnimationListener = mock() + val startTransition = startDrag(defaultHandler, task, finishCallback = finishCallback) + + mergeInterruptingTransition(mergeTarget = startTransition) + + verify(finishCallback, never()).onTransitionFinished(anyOrNull()) + verify(dragAnimator, never()).cancelAnimator() + } + + @Test + @EnableFlags(FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX) + fun mergeOtherTransition_endDragAlreadyMerged_doesNotInterruptStartDrag() { + val startDragFinishCallback = mock<Transitions.TransitionFinishCallback>() + val task = createTask() + val startTransition = + startDrag(defaultHandler, task, finishCallback = startDragFinishCallback) + defaultHandler.onTaskResizeAnimationListener = mock() + mergeAnimation( + type = TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP, + task = task, + mergeTarget = startTransition, + ) + + mergeInterruptingTransition(mergeTarget = startTransition) + + verify(startDragFinishCallback, never()).onTransitionFinished(anyOrNull()) + } + + @Test + @EnableFlags(FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX) + fun startEndAnimation_otherTransitionInterruptedStartAfterEndRequest_finishImmediately() { + val task1 = createTask() + val startTransition = startDrag(defaultHandler, task1) + val endTransition = + defaultHandler.finishDragToDesktopTransition(WindowContainerTransaction()) + val startTransaction = mock<SurfaceControl.Transaction>() + val endDragFinishCallback = mock<Transitions.TransitionFinishCallback>() + defaultHandler.onTaskResizeAnimationListener = mock() + mergeInterruptingTransition(mergeTarget = startTransition) + + val didAnimate = + defaultHandler.startAnimation( + transition = requireNotNull(endTransition), + info = + createTransitionInfo( + type = TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP, + draggedTask = task1, + ), + startTransaction = startTransaction, + finishTransaction = mock(), + finishCallback = endDragFinishCallback, + ) + + assertThat(didAnimate).isTrue() + verify(startTransaction).apply() + verify(endDragFinishCallback).onTransitionFinished(anyOrNull()) + } + + @Test + @EnableFlags(FLAG_ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX) + fun startDrag_otherTransitionInterruptedStartAfterEndRequested_animatesDragWhenReady() { + val task1 = createTask() + val startTransition = startDrag(defaultHandler, task1) + verify(dragAnimator).startAnimation() + val endTransition = + defaultHandler.finishDragToDesktopTransition(WindowContainerTransaction()) + defaultHandler.onTaskResizeAnimationListener = mock() + mergeInterruptingTransition(mergeTarget = startTransition) + defaultHandler.startAnimation( + transition = requireNotNull(endTransition), + info = + createTransitionInfo( + type = TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP, + draggedTask = task1, + ), + startTransaction = mock(), + finishTransaction = mock(), + finishCallback = mock(), + ) + + startDrag(defaultHandler, createTask()) + + verify(dragAnimator, times(2)).startAnimation() + } + + private fun mergeInterruptingTransition(mergeTarget: IBinder) { + defaultHandler.mergeAnimation( + transition = mock<IBinder>(), + info = createTransitionInfo(type = TRANSIT_OPEN, draggedTask = createTask()), + startT = mock(), + finishT = mock(), + mergeTarget = mergeTarget, + finishCallback = mock(), + ) + } + + private fun mergeAnimation( + transition: IBinder = mock(), + type: Int, + mergeTarget: IBinder, + task: RunningTaskInfo, + ) { + defaultHandler.mergeAnimation( + transition = transition, + info = createTransitionInfo(type = type, draggedTask = task), + startT = mock(), + finishT = mock(), + mergeTarget = mergeTarget, + finishCallback = mock(), + ) + } + + @Test fun getAnimationFraction_returnsFraction() { val fraction = SpringDragToDesktopTransitionHandler.getAnimationFraction( @@ -785,6 +907,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { finishTransaction: SurfaceControl.Transaction = mock(), homeChange: TransitionInfo.Change? = createHomeChange(), transitionRootLeash: SurfaceControl = mock(), + finishCallback: Transitions.TransitionFinishCallback = mock(), ): IBinder { whenever(dragAnimator.position).thenReturn(PointF()) // Simulate transition is started and is ready to animate. @@ -800,7 +923,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { ), startTransaction = startTransaction, finishTransaction = finishTransaction, - finishCallback = {}, + finishCallback = finishCallback, ) return transition } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizerTest.kt index 96b85ad2729e..9af504797182 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizerTest.kt @@ -15,20 +15,25 @@ */ package com.android.wm.shell.desktopmode.multidesks +import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED import android.testing.AndroidTestingRunner import android.view.Display import android.view.SurfaceControl import android.view.WindowManager.TRANSIT_TO_FRONT import android.window.TransitionInfo +import android.window.WindowContainerToken import android.window.WindowContainerTransaction import android.window.WindowContainerTransaction.Change import android.window.WindowContainerTransaction.HierarchyOp +import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT import androidx.test.filters.SmallTest import com.android.wm.shell.ShellTaskOrganizer +import com.android.wm.shell.ShellTaskOrganizer.TaskListener import com.android.wm.shell.ShellTestCase import com.android.wm.shell.TestShellExecutor +import com.android.wm.shell.common.LaunchAdjacentController import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask import com.android.wm.shell.desktopmode.multidesks.RootTaskDesksOrganizer.DeskMinimizationRoot import com.android.wm.shell.desktopmode.multidesks.RootTaskDesksOrganizer.DeskRoot @@ -36,14 +41,17 @@ import com.android.wm.shell.sysui.ShellCommandHandler import com.android.wm.shell.sysui.ShellInit import com.google.common.truth.Truth.assertThat import kotlin.test.assertNotNull +import kotlinx.coroutines.test.runTest import org.junit.Assert.assertEquals import org.junit.Assert.assertThrows import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.Mockito import org.mockito.Mockito.verify import org.mockito.kotlin.argThat import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever /** * Tests for [RootTaskDesksOrganizer]. @@ -58,57 +66,32 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { private val testShellInit = ShellInit(testExecutor) private val mockShellCommandHandler = mock<ShellCommandHandler>() private val mockShellTaskOrganizer = mock<ShellTaskOrganizer>() + private val launchAdjacentController = LaunchAdjacentController(mock()) private lateinit var organizer: RootTaskDesksOrganizer @Before fun setUp() { organizer = - RootTaskDesksOrganizer(testShellInit, mockShellCommandHandler, mockShellTaskOrganizer) - } - - @Test - fun testCreateDesk_callsBack() { - val callback = FakeOnCreateCallback() - organizer.createDesk(Display.DEFAULT_DISPLAY, callback) - - val freeformRoot = createFreeformTask().apply { parentTaskId = -1 } - organizer.onTaskAppeared(freeformRoot, SurfaceControl()) - - assertThat(callback.created).isTrue() - assertEquals(freeformRoot.taskId, callback.deskId) + RootTaskDesksOrganizer( + testShellInit, + mockShellCommandHandler, + mockShellTaskOrganizer, + launchAdjacentController, + ) } - @Test - fun testCreateDesk_createsMinimizationRoot() { - val callback = FakeOnCreateCallback() - organizer.createDesk(Display.DEFAULT_DISPLAY, callback) - val freeformRoot = createFreeformTask().apply { parentTaskId = -1 } - organizer.onTaskAppeared(freeformRoot, SurfaceControl()) - - val minimizationRootTask = createFreeformTask().apply { parentTaskId = -1 } - organizer.onTaskAppeared(minimizationRootTask, SurfaceControl()) - - val minimizationRoot = organizer.deskMinimizationRootsByDeskId[freeformRoot.taskId] - assertNotNull(minimizationRoot) - assertThat(minimizationRoot.deskId).isEqualTo(freeformRoot.taskId) - assertThat(minimizationRoot.rootId).isEqualTo(minimizationRootTask.taskId) - } + @Test fun testCreateDesk_createsDeskAndMinimizationRoots() = runTest { createDesk() } @Test - fun testCreateMinimizationRoot_marksHidden() { - organizer.createDesk(Display.DEFAULT_DISPLAY, FakeOnCreateCallback()) - val freeformRoot = createFreeformTask().apply { parentTaskId = -1 } - organizer.onTaskAppeared(freeformRoot, SurfaceControl()) - - val minimizationRootTask = createFreeformTask().apply { parentTaskId = -1 } - organizer.onTaskAppeared(minimizationRootTask, SurfaceControl()) + fun testCreateMinimizationRoot_marksHidden() = runTest { + val desk = createDesk() verify(mockShellTaskOrganizer) .applyTransaction( argThat { wct -> wct.changes.any { change -> - change.key == minimizationRootTask.token.asBinder() && + change.key == desk.minimizationRoot.token.asBinder() && (change.value.changeMask and Change.CHANGE_HIDDEN != 0) && change.value.hidden } @@ -117,7 +100,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { } @Test - fun testOnTaskAppeared_withoutRequest_throws() { + fun testOnTaskAppeared_withoutRequest_throws() = runTest { val freeformRoot = createFreeformTask().apply { parentTaskId = -1 } assertThrows(Exception::class.java) { @@ -126,41 +109,25 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { } @Test - fun testOnTaskAppeared_withRequestOnlyInAnotherDisplay_throws() { - organizer.createDesk(displayId = 2, FakeOnCreateCallback()) - val freeformRoot = createFreeformTask(Display.DEFAULT_DISPLAY).apply { parentTaskId = -1 } - - assertThrows(Exception::class.java) { - organizer.onTaskAppeared(freeformRoot, SurfaceControl()) - } - } - - @Test - fun testOnTaskAppeared_duplicateRoot_throws() { - organizer.createDesk(Display.DEFAULT_DISPLAY, FakeOnCreateCallback()) - val freeformRoot = createFreeformTask().apply { parentTaskId = -1 } - organizer.onTaskAppeared(freeformRoot, SurfaceControl()) + fun testOnTaskAppeared_duplicateRoot_throws() = runTest { + val desk = createDesk() assertThrows(Exception::class.java) { - organizer.onTaskAppeared(freeformRoot, SurfaceControl()) + organizer.onTaskAppeared(desk.deskRoot.taskInfo, SurfaceControl()) } } @Test - fun testOnTaskAppeared_duplicateMinimizedRoot_throws() { - organizer.createDesk(Display.DEFAULT_DISPLAY, FakeOnCreateCallback()) - val freeformRoot = createFreeformTask().apply { parentTaskId = -1 } - val minimizationRootTask = createFreeformTask().apply { parentTaskId = -1 } - organizer.onTaskAppeared(freeformRoot, SurfaceControl()) - organizer.onTaskAppeared(minimizationRootTask, SurfaceControl()) + fun testOnTaskAppeared_duplicateMinimizedRoot_throws() = runTest { + val desk = createDesk() assertThrows(Exception::class.java) { - organizer.onTaskAppeared(minimizationRootTask, SurfaceControl()) + organizer.onTaskAppeared(desk.minimizationRoot.taskInfo, SurfaceControl()) } } @Test - fun testOnTaskVanished_removesRoot() { + fun testOnTaskVanished_removesRoot() = runTest { val desk = createDesk() organizer.onTaskVanished(desk.deskRoot.taskInfo) @@ -169,7 +136,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { } @Test - fun testOnTaskVanished_removesMinimizedRoot() { + fun testOnTaskVanished_removesMinimizedRoot() = runTest { val desk = createDesk() organizer.onTaskVanished(desk.deskRoot.taskInfo) @@ -179,7 +146,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { } @Test - fun testDesktopWindowAppearsInDesk() { + fun testDesktopWindowAppearsInDesk() = runTest { val desk = createDesk() val child = createFreeformTask().apply { parentTaskId = desk.deskRoot.deskId } @@ -189,7 +156,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { } @Test - fun testDesktopWindowAppearsInDeskMinimizationRoot() { + fun testDesktopWindowAppearsInDeskMinimizationRoot() = runTest { val desk = createDesk() val child = createFreeformTask().apply { parentTaskId = desk.minimizationRoot.rootId } @@ -199,7 +166,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { } @Test - fun testDesktopWindowMovesToMinimizationRoot() { + fun testDesktopWindowMovesToMinimizationRoot() = runTest { val desk = createDesk() val child = createFreeformTask().apply { parentTaskId = desk.deskRoot.deskId } organizer.onTaskAppeared(child, SurfaceControl()) @@ -212,7 +179,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { } @Test - fun testDesktopWindowDisappearsFromDesk() { + fun testDesktopWindowDisappearsFromDesk() = runTest { val desk = createDesk() val child = createFreeformTask().apply { parentTaskId = desk.deskRoot.deskId } @@ -223,7 +190,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { } @Test - fun testDesktopWindowDisappearsFromDeskMinimizationRoot() { + fun testDesktopWindowDisappearsFromDeskMinimizationRoot() = runTest { val desk = createDesk() val child = createFreeformTask().apply { parentTaskId = desk.minimizationRoot.rootId } @@ -234,7 +201,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { } @Test - fun testRemoveDesk_removesDeskRoot() { + fun testRemoveDesk_removesDeskRoot() = runTest { val desk = createDesk() val wct = WindowContainerTransaction() @@ -250,7 +217,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { } @Test - fun testRemoveDesk_removesMinimizationRoot() { + fun testRemoveDesk_removesMinimizationRoot() = runTest { val desk = createDesk() val wct = WindowContainerTransaction() @@ -266,7 +233,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { } @Test - fun testActivateDesk() { + fun testActivateDesk() = runTest { val desk = createDesk() val wct = WindowContainerTransaction() @@ -290,7 +257,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { } @Test - fun testActivateDesk_didNotExist_throws() { + fun testActivateDesk_didNotExist_throws() = runTest { val freeformRoot = createFreeformTask().apply { parentTaskId = -1 } val wct = WindowContainerTransaction() @@ -298,7 +265,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { } @Test - fun testMoveTaskToDesk() { + fun testMoveTaskToDesk() = runTest { val desk = createDesk() val desktopTask = createFreeformTask().apply { parentTaskId = -1 } @@ -324,7 +291,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { } @Test - fun testMoveTaskToDesk_didNotExist_throws() { + fun testMoveTaskToDesk_didNotExist_throws() = runTest { val freeformRoot = createFreeformTask().apply { parentTaskId = -1 } val desktopTask = createFreeformTask().apply { parentTaskId = -1 } @@ -335,7 +302,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { } @Test - fun testGetDeskAtEnd() { + fun testGetDeskAtEnd() = runTest { val desk = createDesk() val task = createFreeformTask().apply { parentTaskId = desk.deskRoot.deskId } @@ -348,7 +315,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { } @Test - fun testGetDeskAtEnd_inMinimizationRoot() { + fun testGetDeskAtEnd_inMinimizationRoot() = runTest { val desk = createDesk() val task = createFreeformTask().apply { parentTaskId = desk.minimizationRoot.rootId } @@ -361,27 +328,24 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { } @Test - fun testIsDeskActiveAtEnd() { - organizer.createDesk(Display.DEFAULT_DISPLAY, FakeOnCreateCallback()) - val freeformRoot = createFreeformTask().apply { parentTaskId = -1 } - freeformRoot.isVisibleRequested = true - organizer.onTaskAppeared(freeformRoot, SurfaceControl()) + fun testIsDeskActiveAtEnd() = runTest { + val desk = createDesk() val isActive = organizer.isDeskActiveAtEnd( change = - TransitionInfo.Change(freeformRoot.token, SurfaceControl()).apply { - taskInfo = freeformRoot + TransitionInfo.Change(desk.deskRoot.token, SurfaceControl()).apply { + taskInfo = desk.deskRoot.taskInfo mode = TRANSIT_TO_FRONT }, - deskId = freeformRoot.taskId, + deskId = desk.deskRoot.deskId, ) assertThat(isActive).isTrue() } @Test - fun deactivateDesk_clearsLaunchRoot() { + fun deactivateDesk_clearsLaunchRoot() = runTest { val wct = WindowContainerTransaction() val desk = createDesk() organizer.activateDesk(wct, desk.deskRoot.deskId) @@ -400,7 +364,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { } @Test - fun isDeskChange_forDeskId() { + fun isDeskChange_forDeskId() = runTest { val desk = createDesk() assertThat( @@ -415,7 +379,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { } @Test - fun isDeskChange_forDeskId_inMinimizationRoot() { + fun isDeskChange_forDeskId_inMinimizationRoot() = runTest { val desk = createDesk() assertThat( @@ -433,7 +397,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { } @Test - fun isDeskChange_anyDesk() { + fun isDeskChange_anyDesk() = runTest { val desk = createDesk() assertThat( @@ -447,7 +411,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { } @Test - fun isDeskChange_anyDesk_inMinimizationRoot() { + fun isDeskChange_anyDesk_inMinimizationRoot() = runTest { val desk = createDesk() assertThat( @@ -464,7 +428,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { } @Test - fun minimizeTask() { + fun minimizeTask() = runTest { val desk = createDesk() val task = createFreeformTask().apply { parentTaskId = desk.deskRoot.deskId } val wct = WindowContainerTransaction() @@ -473,18 +437,11 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { organizer.minimizeTask(wct, deskId = desk.deskRoot.deskId, task) - assertThat( - wct.hierarchyOps.any { hop -> - hop.isReparent && - hop.container == task.token.asBinder() && - hop.newParent == desk.minimizationRoot.token.asBinder() - } - ) - .isTrue() + assertThat(wct.hasMinimizationHops(desk, task.token)).isTrue() } @Test - fun minimizeTask_alreadyMinimized_noOp() { + fun minimizeTask_alreadyMinimized_noOp() = runTest { val desk = createDesk() val task = createFreeformTask().apply { parentTaskId = desk.minimizationRoot.rootId } val wct = WindowContainerTransaction() @@ -496,7 +453,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { } @Test - fun minimizeTask_inDifferentDesk_noOp() { + fun minimizeTask_inDifferentDesk_noOp() = runTest { val desk = createDesk() val otherDesk = createDesk() val task = createFreeformTask().apply { parentTaskId = otherDesk.deskRoot.deskId } @@ -508,30 +465,251 @@ class RootTaskDesksOrganizerTest : ShellTestCase() { assertThat(wct.isEmpty).isTrue() } + @Test + fun unminimizeTask() = runTest { + val desk = createDesk() + val task = createFreeformTask().apply { parentTaskId = desk.deskRoot.deskId } + val wct = WindowContainerTransaction() + organizer.moveTaskToDesk(wct, desk.deskRoot.deskId, task) + organizer.onTaskAppeared(task, SurfaceControl()) + organizer.minimizeTask(wct, deskId = desk.deskRoot.deskId, task) + task.parentTaskId = desk.minimizationRoot.rootId + organizer.onTaskInfoChanged(task) + + wct.clear() + organizer.unminimizeTask(wct, deskId = desk.deskRoot.deskId, task) + + assertThat(wct.hasUnminimizationHops(desk, task.token)).isTrue() + } + + @Test + fun unminimizeTask_alreadyUnminimized_noOp() = runTest { + val desk = createDesk() + val task = createFreeformTask().apply { parentTaskId = desk.deskRoot.deskId } + val wct = WindowContainerTransaction() + organizer.moveTaskToDesk(wct, desk.deskRoot.deskId, task) + organizer.onTaskAppeared(task, SurfaceControl()) + + wct.clear() + organizer.unminimizeTask(wct, deskId = desk.deskRoot.deskId, task) + + assertThat(wct.hasUnminimizationHops(desk, task.token)).isFalse() + } + + @Test + fun unminimizeTask_notInDesk_noOp() = runTest { + val desk = createDesk() + val task = createFreeformTask() + val wct = WindowContainerTransaction() + + organizer.unminimizeTask(wct, deskId = desk.deskRoot.deskId, task) + + assertThat(wct.hasUnminimizationHops(desk, task.token)).isFalse() + } + + @Test + fun reorderTaskToFront() = runTest { + val desk = createDesk() + val task = createFreeformTask().apply { parentTaskId = desk.deskRoot.deskId } + val wct = WindowContainerTransaction() + organizer.onTaskAppeared(task, SurfaceControl()) + + organizer.reorderTaskToFront(wct, desk.deskRoot.deskId, task) + + assertThat( + wct.hierarchyOps.singleOrNull { hop -> + hop.container == task.token.asBinder() && + hop.type == HIERARCHY_OP_TYPE_REORDER && + hop.toTop && + hop.includingParents() + } + ) + .isNotNull() + } + + @Test + fun reorderTaskToFront_notInDesk_noOp() = runTest { + val desk = createDesk() + val task = createFreeformTask() + val wct = WindowContainerTransaction() + + organizer.reorderTaskToFront(wct, desk.deskRoot.deskId, task) + + assertThat( + wct.hierarchyOps.singleOrNull { hop -> + hop.container == task.token.asBinder() && + hop.type == HIERARCHY_OP_TYPE_REORDER && + hop.toTop && + hop.includingParents() + } + ) + .isNull() + } + + @Test + fun reorderTaskToFront_minimized_unminimizesAndReorders() = runTest { + val desk = createDesk() + val task = createFreeformTask().apply { parentTaskId = desk.deskRoot.deskId } + val wct = WindowContainerTransaction() + organizer.onTaskAppeared(task, SurfaceControl()) + task.parentTaskId = desk.minimizationRoot.rootId + organizer.onTaskInfoChanged(task) + + organizer.reorderTaskToFront(wct, desk.deskRoot.deskId, task) + + assertThat(wct.hasUnminimizationHops(desk, task.token)).isTrue() + assertThat( + wct.hierarchyOps.singleOrNull { hop -> + hop.container == task.token.asBinder() && + hop.type == HIERARCHY_OP_TYPE_REORDER && + hop.toTop && + hop.includingParents() + } + ) + .isNotNull() + } + + @Test + fun onTaskAppeared_visibleDesk_onlyDesk_disablesLaunchAdjacent() = runTest { + launchAdjacentController.launchAdjacentEnabled = true + + createDesk(visible = true) + + assertThat(launchAdjacentController.launchAdjacentEnabled).isFalse() + } + + @Test + fun onTaskAppeared_invisibleDesk_onlyDesk_enablesLaunchAdjacent() = runTest { + launchAdjacentController.launchAdjacentEnabled = false + + createDesk(visible = false) + + assertThat(launchAdjacentController.launchAdjacentEnabled).isTrue() + } + + @Test + fun onTaskAppeared_invisibleDesk_otherVisibleDesk_disablesLaunchAdjacent() = runTest { + launchAdjacentController.launchAdjacentEnabled = true + + createDesk(visible = true) + createDesk(visible = false) + + assertThat(launchAdjacentController.launchAdjacentEnabled).isFalse() + } + + @Test + fun onTaskInfoChanged_deskBecomesVisible_onlyDesk_disablesLaunchAdjacent() = runTest { + launchAdjacentController.launchAdjacentEnabled = true + + val desk = createDesk(visible = false) + desk.deskRoot.taskInfo.isVisible = true + organizer.onTaskInfoChanged(desk.deskRoot.taskInfo) + + assertThat(launchAdjacentController.launchAdjacentEnabled).isFalse() + } + + @Test + fun onTaskInfoChanged_deskBecomesInvisible_onlyDesk_enablesLaunchAdjacent() = runTest { + launchAdjacentController.launchAdjacentEnabled = false + + val desk = createDesk(visible = true) + desk.deskRoot.taskInfo.isVisible = false + organizer.onTaskInfoChanged(desk.deskRoot.taskInfo) + + assertThat(launchAdjacentController.launchAdjacentEnabled).isTrue() + } + + @Test + fun onTaskInfoChanged_deskBecomesInvisible_otherVisibleDesk_disablesLaunchAdjacent() = runTest { + launchAdjacentController.launchAdjacentEnabled = true + + createDesk(visible = true) + val desk = createDesk(visible = true) + desk.deskRoot.taskInfo.isVisible = false + organizer.onTaskInfoChanged(desk.deskRoot.taskInfo) + + assertThat(launchAdjacentController.launchAdjacentEnabled).isFalse() + } + + @Test + fun onTaskVanished_visibleDeskDisappears_onlyDesk_enablesLaunchAdjacent() = runTest { + launchAdjacentController.launchAdjacentEnabled = false + + val desk = createDesk(visible = true) + organizer.onTaskVanished(desk.deskRoot.taskInfo) + + assertThat(launchAdjacentController.launchAdjacentEnabled).isTrue() + } + + @Test + fun onTaskVanished_visibleDeskDisappears_otherDeskVisible_disablesLaunchAdjacent() = runTest { + launchAdjacentController.launchAdjacentEnabled = true + + createDesk(visible = true) + val desk = createDesk(visible = true) + organizer.onTaskVanished(desk.deskRoot.taskInfo) + + assertThat(launchAdjacentController.launchAdjacentEnabled).isFalse() + } + private data class DeskRoots( val deskRoot: DeskRoot, val minimizationRoot: DeskMinimizationRoot, ) - private fun createDesk(): DeskRoots { - organizer.createDesk(Display.DEFAULT_DISPLAY, FakeOnCreateCallback()) - val freeformRoot = createFreeformTask().apply { parentTaskId = -1 } - organizer.onTaskAppeared(freeformRoot, SurfaceControl()) - val minimizationRoot = createFreeformTask().apply { parentTaskId = -1 } - organizer.onTaskAppeared(minimizationRoot, SurfaceControl()) - return DeskRoots( - organizer.deskRootsByDeskId[freeformRoot.taskId], - checkNotNull(organizer.deskMinimizationRootsByDeskId[freeformRoot.taskId]), - ) - } - - private class FakeOnCreateCallback : DesksOrganizer.OnCreateCallback { - var deskId: Int? = null - val created: Boolean - get() = deskId != null - - override fun onCreated(deskId: Int) { - this.deskId = deskId - } + private suspend fun createDesk(visible: Boolean = true): DeskRoots { + val freeformRootTask = + createFreeformTask().apply { + parentTaskId = -1 + isVisible = visible + isVisibleRequested = visible + } + val minimizationRootTask = createFreeformTask().apply { parentTaskId = -1 } + Mockito.reset(mockShellTaskOrganizer) + whenever( + mockShellTaskOrganizer.createRootTask( + Display.DEFAULT_DISPLAY, + WINDOWING_MODE_FREEFORM, + organizer, + true, + ) + ) + .thenAnswer { invocation -> + val listener = (invocation.arguments[2] as TaskListener) + listener.onTaskAppeared(freeformRootTask, SurfaceControl()) + } + .thenAnswer { invocation -> + val listener = (invocation.arguments[2] as TaskListener) + listener.onTaskAppeared(minimizationRootTask, SurfaceControl()) + } + val deskId = organizer.createDesk(Display.DEFAULT_DISPLAY) + assertEquals(freeformRootTask.taskId, deskId) + val deskRoot = assertNotNull(organizer.deskRootsByDeskId.get(freeformRootTask.taskId)) + val minimizationRoot = + assertNotNull(organizer.deskMinimizationRootsByDeskId[freeformRootTask.taskId]) + assertThat(minimizationRoot.deskId).isEqualTo(freeformRootTask.taskId) + assertThat(minimizationRoot.rootId).isEqualTo(minimizationRootTask.taskId) + return DeskRoots(deskRoot, minimizationRoot) } + + private fun WindowContainerTransaction.hasMinimizationHops( + desk: DeskRoots, + task: WindowContainerToken, + ): Boolean = + hierarchyOps.any { hop -> + hop.isReparent && + hop.container == task.asBinder() && + hop.newParent == desk.minimizationRoot.token.asBinder() + } + + private fun WindowContainerTransaction.hasUnminimizationHops( + desk: DeskRoots, + task: WindowContainerToken, + ): Boolean = + hierarchyOps.any { hop -> + hop.isReparent && + hop.container == task.asBinder() && + hop.newParent == desk.deskRoot.token.asBinder() && + hop.toTop + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerTest.kt index dd9e6ca0deae..4440d4e801fe 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerTest.kt @@ -17,7 +17,6 @@ package com.android.wm.shell.desktopmode.persistence import android.os.UserManager -import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.testing.AndroidTestingRunner import android.view.Display.DEFAULT_DISPLAY @@ -82,10 +81,27 @@ class DesktopRepositoryInitializerTest : ShellTestCase() { } @Test - @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE, FLAG_ENABLE_DESKTOP_WINDOWING_HSUM) - /** TODO: b/362720497 - add multi-desk version when implemented. */ - @DisableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) - fun initWithPersistence_multipleUsers_addedCorrectly_multiDesksDisabled() = + fun init_updatesFlow() = + runTest(StandardTestDispatcher()) { + whenever(persistentRepository.getUserDesktopRepositoryMap()) + .thenReturn(mapOf(USER_ID_1 to desktopRepositoryState1)) + whenever(persistentRepository.getDesktopRepositoryState(USER_ID_1)) + .thenReturn(desktopRepositoryState1) + whenever(persistentRepository.readDesktop(USER_ID_1, DESKTOP_ID_1)).thenReturn(desktop1) + whenever(persistentRepository.readDesktop(USER_ID_1, DESKTOP_ID_2)).thenReturn(desktop2) + + repositoryInitializer.initialize(desktopUserRepositories) + + assertThat(repositoryInitializer.isInitialized.value).isTrue() + } + + @Test + @EnableFlags( + FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE, + FLAG_ENABLE_DESKTOP_WINDOWING_HSUM, + FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND, + ) + fun initWithPersistence_multipleUsers_addedCorrectly() = runTest(StandardTestDispatcher()) { whenever(persistentRepository.getUserDesktopRepositoryMap()) .thenReturn( @@ -104,50 +120,74 @@ class DesktopRepositoryInitializerTest : ShellTestCase() { repositoryInitializer.initialize(desktopUserRepositories) - // Desktop Repository currently returns all tasks across desktops for a specific user - // since the repository currently doesn't handle desktops. This test logic should be - // updated - // once the repository handles multiple desktops. assertThat( - desktopUserRepositories.getProfile(USER_ID_1).getActiveTasks(DEFAULT_DISPLAY) + desktopUserRepositories + .getProfile(USER_ID_1) + .getActiveTaskIdsInDesk(DESKTOP_ID_1) ) - .containsExactly(1, 3, 4, 5) + .containsExactly(1, 3) .inOrder() assertThat( desktopUserRepositories .getProfile(USER_ID_1) - .getExpandedTasksOrdered(DEFAULT_DISPLAY) + .getActiveTaskIdsInDesk(DESKTOP_ID_2) ) - .containsExactly(5, 1) + .containsExactly(4, 5) .inOrder() assertThat( - desktopUserRepositories.getProfile(USER_ID_1).getMinimizedTasks(DEFAULT_DISPLAY) + desktopUserRepositories + .getProfile(USER_ID_2) + .getActiveTaskIdsInDesk(DESKTOP_ID_3) ) - .containsExactly(3, 4) + .containsExactly(7, 8) + .inOrder() + assertThat( + desktopUserRepositories + .getProfile(USER_ID_1) + .getExpandedTasksIdsInDeskOrdered(DESKTOP_ID_1) + ) + .containsExactly(1) .inOrder() - assertThat( - desktopUserRepositories.getProfile(USER_ID_2).getActiveTasks(DEFAULT_DISPLAY) + desktopUserRepositories + .getProfile(USER_ID_1) + .getExpandedTasksIdsInDeskOrdered(DESKTOP_ID_2) ) - .containsExactly(7, 8) + .containsExactly(5) .inOrder() assertThat( desktopUserRepositories .getProfile(USER_ID_2) - .getExpandedTasksOrdered(DEFAULT_DISPLAY) + .getExpandedTasksIdsInDeskOrdered(DESKTOP_ID_3) + ) + .containsExactly(7) + .inOrder() + assertThat( + desktopUserRepositories + .getProfile(USER_ID_1) + .getMinimizedTaskIdsInDesk(DESKTOP_ID_1) + ) + .containsExactly(3) + .inOrder() + assertThat( + desktopUserRepositories + .getProfile(USER_ID_1) + .getMinimizedTaskIdsInDesk(DESKTOP_ID_2) ) - .contains(7) + .containsExactly(4) + .inOrder() assertThat( - desktopUserRepositories.getProfile(USER_ID_2).getMinimizedTasks(DEFAULT_DISPLAY) + desktopUserRepositories + .getProfile(USER_ID_2) + .getMinimizedTaskIdsInDesk(DESKTOP_ID_3) ) .containsExactly(8) + .inOrder() } @Test - @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE) - /** TODO: b/362720497 - add multi-desk version when implemented. */ - @DisableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) - fun initWithPersistence_singleUser_addedCorrectly_multiDesksDisabled() = + @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE, FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun initWithPersistence_singleUser_addedCorrectly() = runTest(StandardTestDispatcher()) { whenever(persistentRepository.getUserDesktopRepositoryMap()) .thenReturn(mapOf(USER_ID_1 to desktopRepositoryState1)) @@ -161,23 +201,44 @@ class DesktopRepositoryInitializerTest : ShellTestCase() { assertThat( desktopUserRepositories .getProfile(USER_ID_1) - .getActiveTaskIdsInDesk(deskId = DEFAULT_DISPLAY) + .getActiveTaskIdsInDesk(DESKTOP_ID_1) + ) + .containsExactly(1, 3) + .inOrder() + assertThat( + desktopUserRepositories + .getProfile(USER_ID_1) + .getActiveTaskIdsInDesk(DESKTOP_ID_2) + ) + .containsExactly(4, 5) + .inOrder() + assertThat( + desktopUserRepositories + .getProfile(USER_ID_1) + .getExpandedTasksIdsInDeskOrdered(DESKTOP_ID_1) + ) + .containsExactly(1) + .inOrder() + assertThat( + desktopUserRepositories + .getProfile(USER_ID_1) + .getExpandedTasksIdsInDeskOrdered(DESKTOP_ID_2) ) - .containsExactly(1, 3, 4, 5) + .containsExactly(5) .inOrder() assertThat( desktopUserRepositories .getProfile(USER_ID_1) - .getExpandedTasksIdsInDeskOrdered(deskId = DEFAULT_DISPLAY) + .getMinimizedTaskIdsInDesk(DESKTOP_ID_1) ) - .containsExactly(5, 1) + .containsExactly(3) .inOrder() assertThat( desktopUserRepositories .getProfile(USER_ID_1) - .getMinimizedTaskIdsInDesk(deskId = DEFAULT_DISPLAY) + .getMinimizedTaskIdsInDesk(DESKTOP_ID_2) ) - .containsExactly(3, 4) + .containsExactly(4) .inOrder() } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java index 9509aaf20c9b..52c5ad1f8e49 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java @@ -41,6 +41,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.dx.mockito.inline.extended.StaticMockitoSession; +import com.android.window.flags.Flags; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestRunningTaskInfoBuilder; @@ -198,6 +199,7 @@ public final class FreeformTaskListenerTests extends ShellTestCase { } @Test + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) public void visibilityTaskChanged_visible_setLaunchAdjacentDisabled() { ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FREEFORM).build(); @@ -209,6 +211,7 @@ public final class FreeformTaskListenerTests extends ShellTestCase { } @Test + @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) public void visibilityTaskChanged_notVisible_setLaunchAdjacentEnabled() { ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FREEFORM).build(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java index bc918450a3cf..714e5f486285 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java @@ -44,10 +44,12 @@ import androidx.test.filters.SmallTest; import com.android.window.flags.Flags; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.desktopmode.DesktopImmersiveController; +import com.android.wm.shell.desktopmode.multidesks.DesksTransitionObserver; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.FocusTransitionObserver; import com.android.wm.shell.transition.TransitionInfoBuilder; import com.android.wm.shell.transition.Transitions; +import com.android.wm.shell.util.StubTransaction; import com.android.wm.shell.windowdecor.WindowDecorViewModel; import org.junit.Before; @@ -68,6 +70,7 @@ public class FreeformTaskTransitionObserverTest extends ShellTestCase { @Mock private WindowDecorViewModel mWindowDecorViewModel; @Mock private TaskChangeListener mTaskChangeListener; @Mock private FocusTransitionObserver mFocusTransitionObserver; + @Mock private DesksTransitionObserver mDesksTransitionObserver; private FreeformTaskTransitionObserver mTransitionObserver; @@ -88,7 +91,8 @@ public class FreeformTaskTransitionObserverTest extends ShellTestCase { Optional.of(mDesktopImmersiveController), mWindowDecorViewModel, Optional.of(mTaskChangeListener), - mFocusTransitionObserver); + mFocusTransitionObserver, + Optional.of(mDesksTransitionObserver)); final ArgumentCaptor<Runnable> initRunnableCaptor = ArgumentCaptor.forClass(Runnable.class); verify(mShellInit).addInitCallback(initRunnableCaptor.capture(), same(mTransitionObserver)); @@ -357,6 +361,18 @@ public class FreeformTaskTransitionObserverTest extends ShellTestCase { verify(mDesktopImmersiveController).onTransitionFinished(transition, /* aborted= */ false); } + @Test + public void onTransitionReady_forwardsToDesksTransitionObserver() { + final IBinder transition = mock(IBinder.class); + final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_CLOSE, /* flags= */ 0) + .build(); + + mTransitionObserver.onTransitionReady(transition, info, new StubTransaction(), + new StubTransaction()); + + verify(mDesksTransitionObserver).onTransitionReady(transition, info); + } + private static TransitionInfo.Change createChange(int mode, int taskId, int windowingMode) { final ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo(); taskInfo.taskId = taskId; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java index 275e4882a79d..42f65dd71f16 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java @@ -17,6 +17,7 @@ package com.android.wm.shell.pip2.phone; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; @@ -86,6 +87,7 @@ public class PipSchedulerTest { @Mock private SurfaceControl.Transaction mMockTransaction; @Mock private PipAlphaAnimator mMockAlphaAnimator; @Mock private SplitScreenController mMockSplitScreenController; + @Mock private SurfaceControl mMockLeash; @Captor private ArgumentCaptor<Runnable> mRunnableArgumentCaptor; @Captor private ArgumentCaptor<WindowContainerTransaction> mWctArgumentCaptor; @@ -315,6 +317,30 @@ public class PipSchedulerTest { verify(mMockAlphaAnimator, never()).start(); } + @Test + public void onPipTransitionStateChanged_exiting_endAnimation() { + mPipScheduler.setOverlayFadeoutAnimator(mMockAlphaAnimator); + when(mMockAlphaAnimator.isStarted()).thenReturn(true); + mPipScheduler.onPipTransitionStateChanged(PipTransitionState.ENTERED_PIP, + PipTransitionState.EXITING_PIP, null); + + verify(mMockAlphaAnimator, times(1)).end(); + assertNull("mOverlayFadeoutAnimator should be reset to null", + mPipScheduler.getOverlayFadeoutAnimator()); + } + + @Test + public void onPipTransitionStateChanged_scheduledBoundsChange_endAnimation() { + mPipScheduler.setOverlayFadeoutAnimator(mMockAlphaAnimator); + when(mMockAlphaAnimator.isStarted()).thenReturn(true); + mPipScheduler.onPipTransitionStateChanged(PipTransitionState.ENTERED_PIP, + PipTransitionState.SCHEDULED_BOUNDS_CHANGE, null); + + verify(mMockAlphaAnimator, times(1)).end(); + assertNull("mOverlayFadeoutAnimator should be reset to null", + mPipScheduler.getOverlayFadeoutAnimator()); + } + private void setNullPipTaskToken() { when(mMockPipTransitionState.getPipTaskToken()).thenReturn(null); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTaskListenerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTaskListenerTest.java index 333569a7206e..5029371c3419 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTaskListenerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTaskListenerTest.java @@ -24,6 +24,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.mockito.kotlin.MatchersKt.eq; import static org.mockito.kotlin.VerificationKt.clearInvocations; @@ -35,7 +36,9 @@ import android.app.ActivityManager; import android.app.PendingIntent; import android.app.PictureInPictureParams; import android.app.RemoteAction; +import android.content.ComponentName; import android.content.Context; +import android.content.res.Resources; import android.graphics.Rect; import android.graphics.drawable.Icon; import android.os.Bundle; @@ -48,8 +51,10 @@ import androidx.test.filters.SmallTest; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.pip.PhoneSizeSpecSource; import com.android.wm.shell.common.pip.PipBoundsAlgorithm; import com.android.wm.shell.common.pip.PipBoundsState; +import com.android.wm.shell.common.pip.PipDisplayLayoutState; import com.android.wm.shell.pip2.animation.PipResizeAnimator; import org.junit.Before; @@ -107,6 +112,16 @@ public class PipTaskListenerTest { } @Test + public void constructor_addOnPipComponentChangedListener() { + mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer, + mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState, + mMockPipBoundsAlgorithm, mMockShellExecutor); + + verify(mMockPipBoundsState).addOnPipComponentChangedListener( + any(PipBoundsState.OnPipComponentChangedListener.class)); + } + + @Test public void setPictureInPictureParams_updatePictureInPictureParams() { mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer, mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState, @@ -359,6 +374,26 @@ public class PipTaskListenerTest { verify(mMockPipResizeAnimator, times(0)).start(); } + @Test + public void onPipComponentChanged_clearPictureInPictureParams() { + when(mMockContext.getResources()).thenReturn(mock(Resources.class)); + PipBoundsState pipBoundsState = new PipBoundsState(mMockContext, + mock(PhoneSizeSpecSource.class), mock(PipDisplayLayoutState.class)); + pipBoundsState.setLastPipComponentName(new ComponentName("org.test", "test1")); + + mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer, + mMockPipTransitionState, mMockPipScheduler, pipBoundsState, + mMockPipBoundsAlgorithm, mMockShellExecutor); + Rational aspectRatio = new Rational(4, 3); + String action1 = "action1"; + mPipTaskListener.setPictureInPictureParams(getPictureInPictureParams( + aspectRatio, action1)); + + pipBoundsState.setLastPipComponentName(new ComponentName("org.test", "test2")); + + assertTrue(mPipTaskListener.getPictureInPictureParams().empty()); + } + private PictureInPictureParams getPictureInPictureParams(Rational aspectRatio, String... actions) { final PictureInPictureParams.Builder builder = new PictureInPictureParams.Builder(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedTaskInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedTaskInfoTest.kt index 6ecebd76a951..75f6bda4d750 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedTaskInfoTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedTaskInfoTest.kt @@ -203,7 +203,7 @@ class GroupedTaskInfoTest : ShellTestCase() { assertThat(taskInfoFromParcel.taskInfoList).hasSize(3) // Only compare task ids val taskIdComparator = Correspondence.transforming<TaskInfo, Int>( - { it?.taskId }, "has taskId of" + { it.taskId }, "has taskId of" ) assertThat(taskInfoFromParcel.taskInfoList).comparingElementsUsing(taskIdComparator) .containsExactly(1, 2, 3).inOrder() diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/DragZoneFactoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/DragZoneFactoryTest.kt index fd22a84dee5d..2b39262d9f00 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/DragZoneFactoryTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/DragZoneFactoryTest.kt @@ -53,7 +53,8 @@ class DragZoneFactoryTest { tabletPortrait.copy(windowBounds = Rect(0, 0, 800, 900), isSmallTablet = true) private val foldableLandscape = foldablePortrait.copy(windowBounds = Rect(0, 0, 900, 800), isLandscape = true) - private val splitScreenModeChecker = SplitScreenModeChecker { SplitScreenMode.NONE } + private var splitScreenMode = SplitScreenMode.NONE + private val splitScreenModeChecker = SplitScreenModeChecker { splitScreenMode } private var isDesktopWindowModeSupported = true private val desktopWindowModeChecker = DesktopWindowModeChecker { isDesktopWindowModeSupported } @@ -283,7 +284,7 @@ class DragZoneFactoryTest { } @Test - fun dragZonesForBubble_tablet_desktopModeDisabled() { + fun dragZonesForBubble_desktopModeDisabled() { isDesktopWindowModeSupported = false dragZoneFactory = DragZoneFactory( @@ -298,7 +299,7 @@ class DragZoneFactoryTest { } @Test - fun dragZonesForExpandedView_tablet_desktopModeDisabled() { + fun dragZonesForExpandedView_desktopModeDisabled() { isDesktopWindowModeSupported = false dragZoneFactory = DragZoneFactory( @@ -314,6 +315,38 @@ class DragZoneFactoryTest { assertThat(dragZones.filterIsInstance<DragZone.DesktopWindow>()).isEmpty() } + @Test + fun dragZonesForBubble_splitScreenModeUnsupported() { + splitScreenMode = SplitScreenMode.UNSUPPORTED + dragZoneFactory = + DragZoneFactory( + context, + foldableLandscape, + splitScreenModeChecker, + desktopWindowModeChecker + ) + val dragZones = + dragZoneFactory.createSortedDragZones(DraggedObject.Bubble(BubbleBarLocation.LEFT)) + assertThat(dragZones.filterIsInstance<DragZone.Split>()).isEmpty() + } + + @Test + fun dragZonesForExpandedView_splitScreenModeUnsupported() { + splitScreenMode = SplitScreenMode.UNSUPPORTED + dragZoneFactory = + DragZoneFactory( + context, + foldableLandscape, + splitScreenModeChecker, + desktopWindowModeChecker + ) + val dragZones = + dragZoneFactory.createSortedDragZones( + DraggedObject.ExpandedView(BubbleBarLocation.LEFT) + ) + assertThat(dragZones.filterIsInstance<DragZone.Split>()).isEmpty() + } + private inline fun <reified T> verifyInstance(): DragZoneVerifier = { dragZone -> assertThat(dragZone).isInstanceOf(T::class.java) } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatusTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatusTest.kt index fb62ba75e056..edf91fe62e7d 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatusTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatusTest.kt @@ -234,14 +234,25 @@ class DesktopModeStatusTest : ShellTestCase() { assertThat(DesktopModeStatus.isDeviceEligibleForDesktopMode(mockContext)).isFalse() } + @DisableFlags(Flags.FLAG_ENABLE_PROJECTED_DISPLAY_DESKTOP_MODE) @Test fun isDeviceEligibleForDesktopMode_configDEModeOnAndIntDispHostsDesktopOff_returnsFalse() { doReturn(true).whenever(mockResources).getBoolean(eq(R.bool.config_isDesktopModeSupported)) - doReturn(false).whenever(mockResources).getBoolean(eq(R.bool.config_canInternalDisplayHostDesktops)) + doReturn(false).whenever(mockResources) + .getBoolean(eq(R.bool.config_canInternalDisplayHostDesktops)) assertThat(DesktopModeStatus.isDeviceEligibleForDesktopMode(mockContext)).isFalse() } + @EnableFlags(Flags.FLAG_ENABLE_PROJECTED_DISPLAY_DESKTOP_MODE) + @Test + fun isPDDeviceEligibleForDesktopMode_configDEModeOnAndIntDispHostsDesktopOff_returnsTrue() { + doReturn(true).whenever(mockResources).getBoolean(eq(R.bool.config_isDesktopModeSupported)) + doReturn(false).whenever(mockResources).getBoolean(eq(R.bool.config_canInternalDisplayHostDesktops)) + + assertThat(DesktopModeStatus.isDeviceEligibleForDesktopMode(mockContext)).isTrue() + } + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE) @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION) @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenuTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenuTest.kt index 257bbb5603a7..b07b6c1a3a87 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenuTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenuTest.kt @@ -22,6 +22,7 @@ import android.platform.test.annotations.EnableFlags import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.SurfaceControl +import android.window.TaskSnapshot import androidx.test.filters.SmallTest import com.android.window.flags.Flags import com.android.wm.shell.MockToken @@ -33,6 +34,7 @@ import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer import com.google.common.truth.Truth.assertThat import org.junit.After +import org.junit.Assert.fail import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -84,7 +86,29 @@ class DesktopHeaderManageWindowsMenuTest : ShellTestCase() { assertThat(menu.menuViewContainer).isInstanceOf(AdditionalSystemViewContainer::class.java) } - private fun createMenu(task: RunningTaskInfo) = DesktopHeaderManageWindowsMenu( + @Test + @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) + fun testShow_nullSnapshotDoesNotCauseNPE() { + val task = createFreeformTask() + val snapshotList = listOf(Pair(/* index = */ 1, /* snapshot = */ null)) + // Set as immersive so that menu is created as system view container (simpler of the + // options) + userRepositories.getProfile(DEFAULT_USER_ID).setTaskInFullImmersiveState( + displayId = task.displayId, + taskId = task.taskId, + immersive = true + ) + try { + menu = createMenu(task, snapshotList) + } catch (e: NullPointerException) { + fail("Null snapshot should not have thrown null pointer exception") + } + } + + private fun createMenu( + task: RunningTaskInfo, + snapshotList: List<Pair<Int, TaskSnapshot?>> = emptyList() + ) = DesktopHeaderManageWindowsMenu( callerTaskInfo = task, x = 0, y = 0, @@ -94,7 +118,7 @@ class DesktopHeaderManageWindowsMenuTest : ShellTestCase() { desktopUserRepositories = userRepositories, surfaceControlBuilderSupplier = { SurfaceControl.Builder() }, surfaceControlTransactionSupplier = { SurfaceControl.Transaction() }, - snapshotList = emptyList(), + snapshotList = snapshotList, onIconClickListener = {}, onOutsideClickListener = {}, ) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt index e89a122595d5..d69509faf4ec 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt @@ -115,7 +115,8 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest .spyStatic(DragPositioningCallbackUtility::class.java) .startMocking() - doReturn(true).`when` { DesktopModeStatus.canInternalDisplayHostDesktops(Mockito.any()) } + doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupportedOnDisplay(Mockito.any(), + Mockito.any()) } doReturn(true).`when` { DesktopModeStatus.canEnterDesktopMode(Mockito.any()) } doReturn(false).`when` { DesktopModeStatus.overridesShowAppHandle(Mockito.any()) } @@ -394,7 +395,7 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest whenever(DesktopModeStatus.enforceDeviceRestrictions()).thenReturn(true) val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN) - doReturn(true).`when` { DesktopModeStatus.canInternalDisplayHostDesktops(any()) } + doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupportedOnDisplay(any(), any()) } setUpMockDecorationsForTasks(task) onTaskOpening(task) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt index 81dfaed56b6f..a1f40fdefee9 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt @@ -79,6 +79,7 @@ import com.android.wm.shell.transition.Transitions import com.android.wm.shell.util.StubTransaction import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel.DesktopModeKeyguardChangeListener import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel.DesktopModeOnInsetsChangedListener +import com.android.wm.shell.windowdecor.common.AppHandleAndHeaderVisibilityHelper import com.android.wm.shell.windowdecor.common.WindowDecorTaskResourceLoader import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier @@ -174,6 +175,7 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() { internal lateinit var desktopModeOnKeyguardChangedListener: DesktopModeKeyguardChangeListener protected lateinit var desktopModeWindowDecorViewModel: DesktopModeWindowDecorViewModel protected lateinit var desktopModeCompatPolicy: DesktopModeCompatPolicy + protected lateinit var appHandleAndHeaderVisibilityHelper: AppHandleAndHeaderVisibilityHelper fun setUpCommon() { spyContext = spy(mContext) @@ -185,9 +187,13 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() { whenever(mockDesktopUserRepositories.current).thenReturn(mockDesktopRepository) whenever(mockDisplayController.getDisplayContext(any())).thenReturn(spyContext) whenever(mockDisplayController.getDisplay(any())).thenReturn(display) + whenever(display.type).thenReturn(Display.TYPE_INTERNAL) whenever(mockDesktopUserRepositories.getProfile(anyInt())) .thenReturn(mockDesktopRepository) desktopModeCompatPolicy = DesktopModeCompatPolicy(spyContext) + appHandleAndHeaderVisibilityHelper = + AppHandleAndHeaderVisibilityHelper(spyContext, mockDisplayController, + desktopModeCompatPolicy) desktopModeWindowDecorViewModel = DesktopModeWindowDecorViewModel( spyContext, testShellExecutor, @@ -222,6 +228,7 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() { Optional.of(mockTasksLimiter), mockAppHandleEducationController, mockAppToWebEducationController, + appHandleAndHeaderVisibilityHelper, mockCaptionHandleRepository, Optional.of(mockActivityOrientationChangeHandler), mockTaskPositionerFactory, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java index c4f70ac2297f..1371b38a579f 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java @@ -167,6 +167,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { private static final boolean DEFAULT_IS_STATUSBAR_VISIBLE = true; private static final boolean DEFAULT_IS_KEYGUARD_VISIBLE_AND_OCCLUDED = false; private static final boolean DEFAULT_IS_IN_FULL_IMMERSIVE_MODE = false; + private static final boolean DEFAULT_IS_DRAGGING = false; private static final boolean DEFAULT_HAS_GLOBAL_FOCUS = true; private static final boolean DEFAULT_SHOULD_IGNORE_CORNER_RADIUS = false; private static final boolean DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS = false; @@ -415,6 +416,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { DEFAULT_IS_STATUSBAR_VISIBLE, DEFAULT_IS_KEYGUARD_VISIBLE_AND_OCCLUDED, DEFAULT_IS_IN_FULL_IMMERSIVE_MODE, + DEFAULT_IS_DRAGGING, new InsetsState(), DEFAULT_HAS_GLOBAL_FOCUS, mExclusionRegion, @@ -616,6 +618,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { DEFAULT_IS_STATUSBAR_VISIBLE, DEFAULT_IS_KEYGUARD_VISIBLE_AND_OCCLUDED, DEFAULT_IS_IN_FULL_IMMERSIVE_MODE, + DEFAULT_IS_DRAGGING, new InsetsState(), DEFAULT_HAS_GLOBAL_FOCUS, mExclusionRegion, @@ -650,6 +653,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { DEFAULT_IS_STATUSBAR_VISIBLE, DEFAULT_IS_KEYGUARD_VISIBLE_AND_OCCLUDED, DEFAULT_IS_IN_FULL_IMMERSIVE_MODE, + DEFAULT_IS_DRAGGING, new InsetsState(), DEFAULT_HAS_GLOBAL_FOCUS, mExclusionRegion, @@ -728,6 +732,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { DEFAULT_IS_STATUSBAR_VISIBLE, DEFAULT_IS_KEYGUARD_VISIBLE_AND_OCCLUDED, /* inFullImmersiveMode */ true, + DEFAULT_IS_DRAGGING, insetsState, DEFAULT_HAS_GLOBAL_FOCUS, mExclusionRegion, @@ -755,6 +760,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { DEFAULT_IS_STATUSBAR_VISIBLE, DEFAULT_IS_KEYGUARD_VISIBLE_AND_OCCLUDED, /* inFullImmersiveMode */ true, + DEFAULT_IS_DRAGGING, new InsetsState(), DEFAULT_HAS_GLOBAL_FOCUS, mExclusionRegion, @@ -781,6 +787,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { /* isStatusBarVisible */ false, DEFAULT_IS_KEYGUARD_VISIBLE_AND_OCCLUDED, DEFAULT_IS_IN_FULL_IMMERSIVE_MODE, + DEFAULT_IS_DRAGGING, new InsetsState(), DEFAULT_HAS_GLOBAL_FOCUS, mExclusionRegion, @@ -807,6 +814,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { /* isStatusBarVisible */ true, /* isKeyguardVisibleAndOccluded */ false, DEFAULT_IS_IN_FULL_IMMERSIVE_MODE, + DEFAULT_IS_DRAGGING, new InsetsState(), DEFAULT_HAS_GLOBAL_FOCUS, mExclusionRegion, @@ -832,6 +840,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { /* isStatusBarVisible */ false, DEFAULT_IS_KEYGUARD_VISIBLE_AND_OCCLUDED, DEFAULT_IS_IN_FULL_IMMERSIVE_MODE, + DEFAULT_IS_DRAGGING, new InsetsState(), DEFAULT_HAS_GLOBAL_FOCUS, mExclusionRegion, @@ -857,6 +866,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { DEFAULT_IS_STATUSBAR_VISIBLE, /* isKeyguardVisibleAndOccluded */ true, DEFAULT_IS_IN_FULL_IMMERSIVE_MODE, + DEFAULT_IS_DRAGGING, new InsetsState(), DEFAULT_HAS_GLOBAL_FOCUS, mExclusionRegion, @@ -883,6 +893,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { /* isStatusBarVisible */ true, DEFAULT_IS_KEYGUARD_VISIBLE_AND_OCCLUDED, /* inFullImmersiveMode */ true, + DEFAULT_IS_DRAGGING, new InsetsState(), DEFAULT_HAS_GLOBAL_FOCUS, mExclusionRegion, @@ -901,6 +912,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { /* isStatusBarVisible */ false, DEFAULT_IS_KEYGUARD_VISIBLE_AND_OCCLUDED, /* inFullImmersiveMode */ true, + DEFAULT_IS_DRAGGING, new InsetsState(), DEFAULT_HAS_GLOBAL_FOCUS, mExclusionRegion, @@ -911,6 +923,33 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { } @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_IMMERSIVE_DRAG_BUGFIX) + public void updateRelayoutParams_header_fullyImmersive_captionVisDuringDrag() { + final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); + taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + final RelayoutParams relayoutParams = new RelayoutParams(); + + DesktopModeWindowDecoration.updateRelayoutParams( + relayoutParams, + mTestableContext, + taskInfo, + mMockSplitScreenController, + DEFAULT_APPLY_START_TRANSACTION_ON_DRAW, + DEFAULT_SHOULD_SET_TASK_POSITIONING_AND_CROP, + /* isStatusBarVisible */ false, + DEFAULT_IS_KEYGUARD_VISIBLE_AND_OCCLUDED, + /* inFullImmersiveMode */ true, + /* isDragging */ true, + new InsetsState(), + DEFAULT_HAS_GLOBAL_FOCUS, + mExclusionRegion, + DEFAULT_SHOULD_IGNORE_CORNER_RADIUS, + DEFAULT_SHOULD_EXCLUDE_CAPTION_FROM_APP_BOUNDS); + + assertThat(relayoutParams.mIsCaptionVisible).isTrue(); + } + + @Test @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) public void updateRelayoutParams_header_fullyImmersive_overKeyguard_captionNotVisible() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); @@ -927,6 +966,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { DEFAULT_IS_STATUSBAR_VISIBLE, /* isKeyguardVisibleAndOccluded */ true, /* inFullImmersiveMode */ true, + DEFAULT_IS_DRAGGING, new InsetsState(), DEFAULT_HAS_GLOBAL_FOCUS, mExclusionRegion, @@ -1588,6 +1628,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { DEFAULT_IS_STATUSBAR_VISIBLE, DEFAULT_IS_KEYGUARD_VISIBLE_AND_OCCLUDED, DEFAULT_IS_IN_FULL_IMMERSIVE_MODE, + DEFAULT_IS_DRAGGING, new InsetsState(), DEFAULT_HAS_GLOBAL_FOCUS, mExclusionRegion, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java index a2927fa3527b..9a2e2fad50be 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java @@ -60,6 +60,7 @@ import android.graphics.Point; import android.graphics.Rect; import android.graphics.Region; import android.os.Handler; +import android.os.LocaleList; import android.testing.AndroidTestingRunner; import android.util.DisplayMetrics; import android.view.AttachedSurfaceControl; @@ -97,6 +98,7 @@ import org.mockito.Mockito; import java.util.ArrayList; import java.util.List; +import java.util.Locale; import java.util.function.Supplier; /** @@ -475,6 +477,50 @@ public class WindowDecorationTests extends ShellTestCase { } @Test + public void testReinflateViewsOnLocaleListChange() { + final Display defaultDisplay = mock(Display.class); + doReturn(defaultDisplay).when(mMockDisplayController) + .getDisplay(Display.DEFAULT_DISPLAY); + + final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder() + .setVisible(true) + .setDisplayId(Display.DEFAULT_DISPLAY) + .build(); + taskInfo.configuration.setLocales(new LocaleList(Locale.FRANCE, Locale.US)); + final TestWindowDecoration windowDecor = spy(createWindowDecoration(taskInfo)); + windowDecor.relayout(taskInfo, true /* hasGlobalFocus */, Region.obtain()); + clearInvocations(windowDecor); + + final ActivityManager.RunningTaskInfo taskInfo2 = new TestRunningTaskInfoBuilder() + .setVisible(true) + .setDisplayId(Display.DEFAULT_DISPLAY) + .build(); + taskInfo2.configuration.setLocales(new LocaleList(Locale.US, Locale.FRANCE)); + windowDecor.relayout(taskInfo2, true /* hasGlobalFocus */, Region.obtain()); + // WindowDecoration#releaseViews should be called since the locale list has changed. + verify(windowDecor, times(1)).releaseViews(any()); + } + + @Test + public void testViewNotReinflatedWhenLocaleListNotChanged() { + final Display defaultDisplay = mock(Display.class); + doReturn(defaultDisplay).when(mMockDisplayController) + .getDisplay(Display.DEFAULT_DISPLAY); + + final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder() + .setVisible(true) + .setDisplayId(Display.DEFAULT_DISPLAY) + .build(); + taskInfo.configuration.setLocales(new LocaleList(Locale.FRANCE, Locale.US)); + final TestWindowDecoration windowDecor = spy(createWindowDecoration(taskInfo)); + windowDecor.relayout(taskInfo, true /* hasGlobalFocus */, Region.obtain()); + clearInvocations(windowDecor); + windowDecor.relayout(taskInfo, true /* hasGlobalFocus */, Region.obtain()); + // WindowDecoration#releaseViews should not be called since nothing has changed. + verify(windowDecor, never()).releaseViews(any()); + } + + @Test public void testLayoutResultCalculation_fullWidthCaption() { final Display defaultDisplay = mock(Display.class); doReturn(defaultDisplay).when(mMockDisplayController) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoaderTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoaderTest.kt index c61e0eb3b5af..c8ccac35d4c4 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoaderTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoaderTest.kt @@ -23,6 +23,7 @@ import android.content.pm.ActivityInfo import android.content.pm.ApplicationInfo import android.content.pm.PackageManager import android.graphics.drawable.Drawable +import android.os.LocaleList import android.os.UserHandle import android.testing.AndroidTestingRunner import android.testing.TestableContext @@ -39,6 +40,7 @@ import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.sysui.UserChangeListener import com.android.wm.shell.windowdecor.common.WindowDecorTaskResourceLoader.AppResources import com.google.common.truth.Truth.assertThat +import java.util.Locale import org.junit.Assert.assertThrows import org.junit.Before import org.junit.Test @@ -116,8 +118,10 @@ class WindowDecorTaskResourceLoaderTest : ShellTestCase() { @Test fun testGetName_cached_returnsFromCache() { val task = createTaskInfo(context.userId) + task.configuration.setLocales(LocaleList(Locale.US)) loader.onWindowDecorCreated(task) loader.taskToResourceCache[task.taskId] = AppResources("App Name", mock(), mock()) + loader.localeListOnCache[task.taskId] = LocaleList(Locale.US) loader.getName(task) @@ -130,6 +134,19 @@ class WindowDecorTaskResourceLoaderTest : ShellTestCase() { } @Test + fun testGetName_cached_localesChanged_loadsResourceAndCaches() { + val task = createTaskInfo(context.userId) + loader.onWindowDecorCreated(task) + loader.taskToResourceCache[task.taskId] = AppResources("App Name", mock(), mock()) + loader.localeListOnCache[task.taskId] = LocaleList(Locale.US, Locale.FRANCE) + task.configuration.setLocales(LocaleList(Locale.FRANCE, Locale.US)) + doReturn("App Name but in French").whenever(mockPackageManager).getApplicationLabel(any()) + + assertThat(loader.getName(task)).isEqualTo("App Name but in French") + assertThat(loader.taskToResourceCache[task.taskId]?.appName).isEqualTo("App Name but in French") + } + + @Test fun testGetHeaderIcon_notCached_loadsResourceAndCaches() { val task = createTaskInfo(context.userId) loader.onWindowDecorCreated(task) diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp index a18c5f5f92f6..8ecd6ba9b253 100644 --- a/libs/androidfw/ResourceTypes.cpp +++ b/libs/androidfw/ResourceTypes.cpp @@ -6520,41 +6520,79 @@ base::expected<StringPiece16, NullOrIOError> StringPoolRef::string16() const { } bool ResTable::getResourceFlags(uint32_t resID, uint32_t* outFlags) const { - if (mError != NO_ERROR) { - return false; - } + if (mError != NO_ERROR) { + return false; + } - const ssize_t p = getResourcePackageIndex(resID); - const int t = Res_GETTYPE(resID); - const int e = Res_GETENTRY(resID); + const ssize_t p = getResourcePackageIndex(resID); + const int t = Res_GETTYPE(resID); + const int e = Res_GETENTRY(resID); - if (p < 0) { - if (Res_GETPACKAGE(resID)+1 == 0) { - ALOGW("No package identifier when getting flags for resource number 0x%08x", resID); - } else { - ALOGW("No known package when getting flags for resource number 0x%08x", resID); - } - return false; - } - if (t < 0) { - ALOGW("No type identifier when getting flags for resource number 0x%08x", resID); - return false; + if (p < 0) { + if (Res_GETPACKAGE(resID)+1 == 0) { + ALOGW("No package identifier when getting flags for resource number 0x%08x", resID); + } else { + ALOGW("No known package when getting flags for resource number 0x%08x", resID); } + return false; + } + if (t < 0) { + ALOGW("No type identifier when getting flags for resource number 0x%08x", resID); + return false; + } - const PackageGroup* const grp = mPackageGroups[p]; - if (grp == NULL) { - ALOGW("Bad identifier when getting flags for resource number 0x%08x", resID); - return false; - } + const PackageGroup* const grp = mPackageGroups[p]; + if (grp == NULL) { + ALOGW("Bad identifier when getting flags for resource number 0x%08x", resID); + return false; + } - Entry entry; - status_t err = getEntry(grp, t, e, NULL, &entry); - if (err != NO_ERROR) { - return false; + Entry entry; + status_t err = getEntry(grp, t, e, NULL, &entry); + if (err != NO_ERROR) { + return false; + } + + *outFlags = entry.specFlags; + return true; +} + +bool ResTable::getResourceEntryFlags(uint32_t resID, uint32_t* outFlags) const { + if (mError != NO_ERROR) { + return false; + } + + const ssize_t p = getResourcePackageIndex(resID); + const int t = Res_GETTYPE(resID); + const int e = Res_GETENTRY(resID); + + if (p < 0) { + if (Res_GETPACKAGE(resID)+1 == 0) { + ALOGW("No package identifier when getting flags for resource number 0x%08x", resID); + } else { + ALOGW("No known package when getting flags for resource number 0x%08x", resID); } + return false; + } + if (t < 0) { + ALOGW("No type identifier when getting flags for resource number 0x%08x", resID); + return false; + } - *outFlags = entry.specFlags; - return true; + const PackageGroup* const grp = mPackageGroups[p]; + if (grp == NULL) { + ALOGW("Bad identifier when getting flags for resource number 0x%08x", resID); + return false; + } + + Entry entry; + status_t err = getEntry(grp, t, e, NULL, &entry); + if (err != NO_ERROR) { + return false; + } + + *outFlags = entry.entry->flags(); + return true; } bool ResTable::isPackageDynamic(uint8_t packageID) const { diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h index 30594dcfa939..63b28da075cd 100644 --- a/libs/androidfw/include/androidfw/ResourceTypes.h +++ b/libs/androidfw/include/androidfw/ResourceTypes.h @@ -1265,6 +1265,9 @@ struct ResTable_config // Varies in length from 3 to 8 chars. Zero-filled value. char localeNumberingSystem[8]; + // Mark all padding explicitly so it's clear how much we can expand it. + char endPadding[3]; + void copyFromDeviceNoSwap(const ResTable_config& o) { const auto o_size = dtohl(o.size); if (o_size >= sizeof(ResTable_config)) [[likely]] { @@ -1422,6 +1425,13 @@ struct ResTable_config void swapHtoD_slow(); }; +// Fix the struct size for backward compatibility +static_assert(sizeof(ResTable_config) == 64); + +// Make sure there's no unaccounted padding in the structure. +static_assert(offsetof(ResTable_config, endPadding) + + sizeof(ResTable_config::endPadding) == sizeof(ResTable_config)); + /** * A specification of the resources defined by a particular type. * @@ -1583,6 +1593,8 @@ union ResTable_entry // If set, this is a compact entry with data type and value directly // encoded in the this entry, see ResTable_entry::compact FLAG_COMPACT = 0x0008, + // If set, this entry relies on read write android feature flags + FLAG_USES_FEATURE_FLAGS = 0x0010, }; struct Full { @@ -1612,6 +1624,7 @@ union ResTable_entry uint16_t flags() const { return dtohs(full.flags); }; bool is_compact() const { return flags() & FLAG_COMPACT; } bool is_complex() const { return flags() & FLAG_COMPLEX; } + bool uses_feature_flags() const { return flags() & FLAG_USES_FEATURE_FLAGS; } size_t size() const { return is_compact() ? sizeof(ResTable_entry) : dtohs(this->full.size); @@ -2029,6 +2042,8 @@ public: bool getResourceFlags(uint32_t resID, uint32_t* outFlags) const; + bool getResourceEntryFlags(uint32_t resID, uint32_t* outFlags) const; + /** * Returns whether or not the package for the given resource has been dynamically assigned. * If the resource can't be found, returns 'false'. diff --git a/libs/hostgraphics/HostBufferQueue.cpp b/libs/hostgraphics/HostBufferQueue.cpp index 7e14b88a47fa..ef5406250251 100644 --- a/libs/hostgraphics/HostBufferQueue.cpp +++ b/libs/hostgraphics/HostBufferQueue.cpp @@ -29,6 +29,7 @@ public: } virtual status_t detachBuffer(int slot) { + mBuffer.clear(); return OK; } diff --git a/libs/hwui/jni/Graphics.cpp b/libs/hwui/jni/Graphics.cpp index a210ddf54b2e..7d227f793817 100644 --- a/libs/hwui/jni/Graphics.cpp +++ b/libs/hwui/jni/Graphics.cpp @@ -722,10 +722,15 @@ void RecyclingClippingPixelAllocator::copyIfNecessary() { auto canvas = SkCanvas(recycledPixels->getSkBitmap()); SkRect destination = SkRect::Make(recycledPixels->info().bounds()); - destination.intersect(SkRect::Make(mSkiaBitmap->info().bounds())); - canvas.drawImageRect(mSkiaBitmap->asImage(), *mDesiredSubset, destination, - SkSamplingOptions(SkFilterMode::kLinear), nullptr, - SkCanvas::kFast_SrcRectConstraint); + if (destination.intersect(SkRect::Make(mSkiaBitmap->info().bounds()))) { + canvas.drawImageRect(mSkiaBitmap->asImage(), *mDesiredSubset, destination, + SkSamplingOptions(SkFilterMode::kLinear), nullptr, + SkCanvas::kFast_SrcRectConstraint); + } else { + // The canvas would have discarded the draw operation automatically, but + // this case should have been detected before getting to this point. + ALOGE("Copy destination does not intersect image bounds"); + } } else { void* dst = recycledPixels->pixels(); const size_t dstRowBytes = mRecycledBitmap->rowBytes(); diff --git a/location/java/android/location/flags/location.aconfig b/location/java/android/location/flags/location.aconfig index 83b1778fd611..f8eb41826c6f 100644 --- a/location/java/android/location/flags/location.aconfig +++ b/location/java/android/location/flags/location.aconfig @@ -179,3 +179,10 @@ flag { } } +flag { + name: "gnss_location_provider_overlay_2025_devices" + namespace: "location" + description: "Flag for GNSS location provider overlay for 2025 devices" + bug: "398254728" + is_fixed_read_only: true +} diff --git a/media/java/Android.bp b/media/java/Android.bp index 6878f9d61f6d..28b9d3bbc167 100644 --- a/media/java/Android.bp +++ b/media/java/Android.bp @@ -15,6 +15,7 @@ filegroup { ], exclude_srcs: [ ":framework-media-tv-tunerresourcemanager-sources-aidl", + ":framework-media-quality-sources-aidl", ], visibility: [ "//frameworks/base", diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index d082c7384fd1..32af7c6fca68 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -4857,7 +4857,7 @@ public class AudioManager { focusReceiver = addClientIdToFocusReceiverLocked(clientFakeId); } - return handleExternalAudioPolicyWaitIfNeeded(clientFakeId, focusReceiver); + return handleExternalAudioPolicyWaitIfNeeded(clientFakeId, focusReceiver, afr); } /** @@ -5070,7 +5070,7 @@ public class AudioManager { focusReceiver = addClientIdToFocusReceiverLocked(clientId); } - return handleExternalAudioPolicyWaitIfNeeded(clientId, focusReceiver); + return handleExternalAudioPolicyWaitIfNeeded(clientId, focusReceiver, afr); } @GuardedBy("mFocusRequestsLock") @@ -5086,11 +5086,20 @@ public class AudioManager { } private @FocusRequestResult int handleExternalAudioPolicyWaitIfNeeded(String clientId, - BlockingFocusResultReceiver focusReceiver) { + BlockingFocusResultReceiver focusReceiver, @NonNull AudioFocusRequest afr) { focusReceiver.waitForResult(EXT_FOCUS_POLICY_TIMEOUT_MS); - if (DEBUG && !focusReceiver.receivedResult()) { - Log.e(TAG, "handleExternalAudioPolicyWaitIfNeeded" - + " response from ext policy timed out, denying request"); + if (!focusReceiver.receivedResult()) { + if (DEBUG) { + Log.e(TAG, "handleExternalAudioPolicyWaitIfNeeded" + + " response from ext policy timed out, denying request"); + } + try { + // To prevent from orphan focus holder, cleanup + abandonAudioFocus(afr.getOnAudioFocusChangeListener()); + } catch (Exception e) { + Log.e(TAG, "handleExternalAudioPolicyWaitIfNeeded failed to abandon audio" + +" focus after time out, error: " + e.getMessage()); + } } synchronized (mFocusRequestsLock) { diff --git a/media/java/android/media/IMediaRouter2.aidl b/media/java/android/media/IMediaRouter2.aidl index 85bc8efe2750..e9590d50d719 100644 --- a/media/java/android/media/IMediaRouter2.aidl +++ b/media/java/android/media/IMediaRouter2.aidl @@ -18,6 +18,7 @@ package android.media; import android.media.MediaRoute2Info; import android.media.RoutingSessionInfo; +import android.media.SuggestedDeviceInfo; import android.os.Bundle; import android.os.UserHandle; @@ -37,4 +38,6 @@ oneway interface IMediaRouter2 { */ void requestCreateSessionByManager(long uniqueRequestId, in RoutingSessionInfo oldSession, in MediaRoute2Info route); + void notifyDeviceSuggestionsUpdated(String suggestingPackageName, + in List<SuggestedDeviceInfo> suggestions); } diff --git a/media/java/android/media/IMediaRouter2Manager.aidl b/media/java/android/media/IMediaRouter2Manager.aidl index 21908b2ca2e0..1c399d6958bb 100644 --- a/media/java/android/media/IMediaRouter2Manager.aidl +++ b/media/java/android/media/IMediaRouter2Manager.aidl @@ -21,6 +21,7 @@ import android.media.MediaRoute2Info; import android.media.RouteDiscoveryPreference; import android.media.RouteListingPreference; import android.media.RoutingSessionInfo; +import android.media.SuggestedDeviceInfo; /** * {@hide} @@ -33,6 +34,8 @@ oneway interface IMediaRouter2Manager { in RouteDiscoveryPreference discoveryPreference); void notifyRouteListingPreferenceChange(String packageName, in @nullable RouteListingPreference routeListingPreference); + void notifyDeviceSuggestionsUpdated(String packageName, String suggestingPackageName, + in @nullable List<SuggestedDeviceInfo> suggestedDeviceInfo); void notifyRoutesUpdated(in List<MediaRoute2Info> routes); void notifyRequestFailed(int requestId, int reason); void invalidateInstance(); diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl index 961962f6a010..60881f4bfc30 100644 --- a/media/java/android/media/IMediaRouterService.aidl +++ b/media/java/android/media/IMediaRouterService.aidl @@ -25,6 +25,7 @@ import android.media.MediaRouterClientState; import android.media.RouteDiscoveryPreference; import android.media.RouteListingPreference; import android.media.RoutingSessionInfo; +import android.media.SuggestedDeviceInfo; import android.os.Bundle; import android.os.UserHandle; /** @@ -72,6 +73,10 @@ interface IMediaRouterService { in MediaRoute2Info route); void setSessionVolumeWithRouter2(IMediaRouter2 router, String sessionId, int volume); void releaseSessionWithRouter2(IMediaRouter2 router, String sessionId); + void setDeviceSuggestionsWithRouter2(IMediaRouter2 router, + in @nullable List<SuggestedDeviceInfo> suggestedDeviceInfo); + @nullable Map<String, List<SuggestedDeviceInfo>> getDeviceSuggestionsWithRouter2( + IMediaRouter2 router); // Methods for MediaRouter2Manager List<RoutingSessionInfo> getRemoteSessions(IMediaRouter2Manager manager); @@ -98,4 +103,8 @@ interface IMediaRouterService { String sessionId, int volume); void releaseSessionWithManager(IMediaRouter2Manager manager, int requestId, String sessionId); boolean showMediaOutputSwitcherWithProxyRouter(IMediaRouter2Manager manager); + void setDeviceSuggestionsWithManager(IMediaRouter2Manager manager, + in @nullable List<SuggestedDeviceInfo> suggestedDeviceInfo); + @nullable Map<String, List<SuggestedDeviceInfo>> getDeviceSuggestionsWithManager( + IMediaRouter2Manager manager); } diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java index 4e86eacea404..f3b21bfdaa3c 100644 --- a/media/java/android/media/MediaCodecInfo.java +++ b/media/java/android/media/MediaCodecInfo.java @@ -3836,6 +3836,151 @@ public final class MediaCodecInfo { maxBlocks, maxBlocksPerSecond, blockSize, blockSize, 1 /* widthAlignment */, 1 /* heightAlignment */); + } else if (GetFlag(() -> android.media.codec.Flags.apvSupport()) + && mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_APV)) { + maxBlocksPerSecond = 11880; + maxBps = 7000000; + + // Sample rate, and Bit rate for APV Codec, + // corresponding to the definitions in + // "10.1.4. Levels and bands" + // found at https://www.ietf.org/archive/id/draft-lim-apv-03.html + for (CodecProfileLevel profileLevel: profileLevels) { + long SR = 0; // luma sample rate + int BR = 0; // bit rate bps + switch (profileLevel.level) { + case CodecProfileLevel.APVLevel1Band0: + SR = 3041280; BR = 7000000; break; + case CodecProfileLevel.APVLevel1Band1: + SR = 3041280; BR = 11000000; break; + case CodecProfileLevel.APVLevel1Band2: + SR = 3041280; BR = 14000000; break; + case CodecProfileLevel.APVLevel1Band3: + SR = 3041280; BR = 21000000; break; + case CodecProfileLevel.APVLevel11Band0: + SR = 6082560; BR = 14000000; break; + case CodecProfileLevel.APVLevel11Band1: + SR = 6082560; BR = 21000000; break; + case CodecProfileLevel.APVLevel11Band2: + SR = 6082560; BR = 28000000; break; + case CodecProfileLevel.APVLevel11Band3: + SR = 6082560; BR = 42000000; break; + case CodecProfileLevel.APVLevel2Band0: + SR = 15667200; BR = 36000000; break; + case CodecProfileLevel.APVLevel2Band1: + SR = 15667200; BR = 53000000; break; + case CodecProfileLevel.APVLevel2Band2: + SR = 15667200; BR = 71000000; break; + case CodecProfileLevel.APVLevel2Band3: + SR = 15667200; BR = 106000000; break; + case CodecProfileLevel.APVLevel21Band0: + SR = 31334400; BR = 71000000; break; + case CodecProfileLevel.APVLevel21Band1: + SR = 31334400; BR = 106000000; break; + case CodecProfileLevel.APVLevel21Band2: + SR = 31334400; BR = 141000000; break; + case CodecProfileLevel.APVLevel21Band3: + SR = 31334400; BR = 212000000; break; + case CodecProfileLevel.APVLevel3Band0: + SR = 66846720; BR = 101000000; break; + case CodecProfileLevel.APVLevel3Band1: + SR = 66846720; BR = 151000000; break; + case CodecProfileLevel.APVLevel3Band2: + SR = 66846720; BR = 201000000; break; + case CodecProfileLevel.APVLevel3Band3: + SR = 66846720; BR = 301000000; break; + case CodecProfileLevel.APVLevel31Band0: + SR = 133693440; BR = 201000000; break; + case CodecProfileLevel.APVLevel31Band1: + SR = 133693440; BR = 301000000; break; + case CodecProfileLevel.APVLevel31Band2: + SR = 133693440; BR = 401000000; break; + case CodecProfileLevel.APVLevel31Band3: + SR = 133693440; BR = 602000000; break; + case CodecProfileLevel.APVLevel4Band0: + SR = 265420800; BR = 401000000; break; + case CodecProfileLevel.APVLevel4Band1: + SR = 265420800; BR = 602000000; break; + case CodecProfileLevel.APVLevel4Band2: + SR = 265420800; BR = 780000000; break; + case CodecProfileLevel.APVLevel4Band3: + SR = 265420800; BR = 1170000000; break; + case CodecProfileLevel.APVLevel41Band0: + SR = 530841600; BR = 780000000; break; + case CodecProfileLevel.APVLevel41Band1: + SR = 530841600; BR = 1170000000; break; + case CodecProfileLevel.APVLevel41Band2: + SR = 530841600; BR = 1560000000; break; + case CodecProfileLevel.APVLevel41Band3: + // Current API allows bitrates only up to Max Integer + // Hence we are limiting internal limits to Integer.MAX_VALUE + // even when actual Level/Band limits are higher + SR = 530841600; BR = Integer.MAX_VALUE; break; + case CodecProfileLevel.APVLevel5Band0: + SR = 1061683200; BR = 1560000000; break; + case CodecProfileLevel.APVLevel5Band1: + SR = 1061683200; BR = Integer.MAX_VALUE; break; + case CodecProfileLevel.APVLevel5Band2: + SR = 1061683200; BR = Integer.MAX_VALUE; break; + case CodecProfileLevel.APVLevel5Band3: + SR = 1061683200; BR = Integer.MAX_VALUE; break; + case CodecProfileLevel.APVLevel51Band0: + case CodecProfileLevel.APVLevel51Band1: + case CodecProfileLevel.APVLevel51Band2: + case CodecProfileLevel.APVLevel51Band3: + SR = 2123366400; BR = Integer.MAX_VALUE; break; + case CodecProfileLevel.APVLevel6Band0: + case CodecProfileLevel.APVLevel6Band1: + case CodecProfileLevel.APVLevel6Band2: + case CodecProfileLevel.APVLevel6Band3: + SR = 4777574400L; BR = Integer.MAX_VALUE; break; + case CodecProfileLevel.APVLevel61Band0: + case CodecProfileLevel.APVLevel61Band1: + case CodecProfileLevel.APVLevel61Band2: + case CodecProfileLevel.APVLevel61Band3: + SR = 8493465600L; BR = Integer.MAX_VALUE; break; + case CodecProfileLevel.APVLevel7Band0: + case CodecProfileLevel.APVLevel7Band1: + case CodecProfileLevel.APVLevel7Band2: + case CodecProfileLevel.APVLevel7Band3: + SR = 16986931200L; BR = Integer.MAX_VALUE; break; + case CodecProfileLevel.APVLevel71Band0: + case CodecProfileLevel.APVLevel71Band1: + case CodecProfileLevel.APVLevel71Band2: + case CodecProfileLevel.APVLevel71Band3: + SR = 33973862400L; BR = Integer.MAX_VALUE; break; + default: + Log.w(TAG, "Unrecognized level " + + profileLevel.level + " for " + mime); + errors |= ERROR_UNRECOGNIZED; + } + switch (profileLevel.profile) { + case CodecProfileLevel.APVProfile422_10: + case CodecProfileLevel.APVProfile422_10HDR10: + case CodecProfileLevel.APVProfile422_10HDR10Plus: + break; + default: + Log.w(TAG, "Unrecognized profile " + + profileLevel.profile + " for " + mime); + errors |= ERROR_UNRECOGNIZED; + } + errors &= ~ERROR_NONE_SUPPORTED; + maxBlocksPerSecond = Math.max(SR, maxBlocksPerSecond); + maxBps = Math.max(BR, maxBps); + } + + final int blockSize = 16; + maxBlocks = Integer.MAX_VALUE; + maxBlocksPerSecond = Utils.divUp(maxBlocksPerSecond, blockSize * blockSize); + maxBlocks = (int) Math.min((long) maxBlocks, maxBlocksPerSecond); + // Max frame size in APV is 2^24 + int maxLengthInBlocks = Utils.divUp((int) Math.pow(2, 24), blockSize); + maxLengthInBlocks = Math.min(maxLengthInBlocks, maxBlocks); + applyMacroBlockLimits( + maxLengthInBlocks, maxLengthInBlocks, + maxBlocks, maxBlocksPerSecond, + blockSize, blockSize, + 2 /* widthAlignment */, 1 /* heightAlignment */); } else { Log.w(TAG, "Unsupported mime " + mime); // using minimal bitrate here. should be overriden by diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java index 3af36a404c30..db305effac3f 100644 --- a/media/java/android/media/MediaRouter2.java +++ b/media/java/android/media/MediaRouter2.java @@ -22,6 +22,7 @@ import static com.android.media.flags.Flags.FLAG_ENABLE_GET_TRANSFERABLE_ROUTES; import static com.android.media.flags.Flags.FLAG_ENABLE_PRIVILEGED_ROUTING_FOR_MEDIA_ROUTING_CONTROL; import static com.android.media.flags.Flags.FLAG_ENABLE_RLP_CALLBACKS_IN_MEDIA_ROUTER2; import static com.android.media.flags.Flags.FLAG_ENABLE_SCREEN_OFF_SCANNING; +import static com.android.media.flags.Flags.FLAG_ENABLE_SUGGESTED_DEVICE_API; import android.Manifest; import android.annotation.CallbackExecutor; @@ -159,6 +160,8 @@ public final class MediaRouter2 { new CopyOnWriteArrayList<>(); private final CopyOnWriteArrayList<ControllerCallbackRecord> mControllerCallbackRecords = new CopyOnWriteArrayList<>(); + private final CopyOnWriteArrayList<DeviceSuggestionsCallbackRecord> + mDeviceSuggestionsCallbackRecords = new CopyOnWriteArrayList<>(); private final CopyOnWriteArrayList<ControllerCreationRequest> mControllerCreationRequests = new CopyOnWriteArrayList<>(); @@ -198,6 +201,10 @@ public final class MediaRouter2 { @Nullable private RouteListingPreference mRouteListingPreference; + @GuardedBy("mLock") + @Nullable + private Map<String, List<SuggestedDeviceInfo>> mSuggestedDeviceInfo = new HashMap<>(); + /** * Stores an auxiliary copy of {@link #mFilteredRoutes} at the time of the last route callback * dispatch. This is only used to determine what callback a route should be assigned to (added, @@ -760,6 +767,27 @@ public final class MediaRouter2 { } /** + * Registers the given callback to be invoked when the {@link SuggestedDeviceInfo} of the target + * router changes. + * + * <p>Calls using a previously registered callback will overwrite the previous executor. + * + * @hide + */ + public void registerDeviceSuggestionsCallback( + @NonNull @CallbackExecutor Executor executor, + @NonNull DeviceSuggestionsCallback deviceSuggestionsCallback) { + Objects.requireNonNull(executor, "executor must not be null"); + Objects.requireNonNull(deviceSuggestionsCallback, "callback must not be null"); + + DeviceSuggestionsCallbackRecord record = + new DeviceSuggestionsCallbackRecord(executor, deviceSuggestionsCallback); + + mDeviceSuggestionsCallbackRecords.remove(record); + mDeviceSuggestionsCallbackRecords.add(record); + } + + /** * Unregisters the given callback to not receive {@link RouteListingPreference} change events. * * @see #registerRouteListingPreferenceUpdatedCallback(Executor, Consumer) @@ -779,6 +807,21 @@ public final class MediaRouter2 { } /** + * Unregisters the given callback to not receive {@link SuggestedDeviceInfo} change events. + * + * @see #registerDeviceSuggestionsCallback(Executor, DeviceSuggestionsCallback) + * @hide + */ + public void unregisterDeviceSuggestionsCallback(@NonNull DeviceSuggestionsCallback callback) { + Objects.requireNonNull(callback, "callback must not be null"); + + if (!mDeviceSuggestionsCallbackRecords.remove( + new DeviceSuggestionsCallbackRecord(/* executor */ null, callback))) { + Log.w(TAG, "unregisterDeviceSuggestionsCallback: Ignoring an unknown" + " callback"); + } + } + + /** * Shows the system output switcher dialog. * * <p>Should only be called when the context of MediaRouter2 is in the foreground and visible on @@ -832,6 +875,36 @@ public final class MediaRouter2 { } /** + * Sets the suggested devices. + * + * <p>Use this method to inform the system UI that this device is suggested in the Output + * Switcher and media controls. + * + * <p>You should pass null to this method to clear a previously set suggestion without setting a + * new one. + * + * @param suggestedDeviceInfo The {@link SuggestedDeviceInfo} the router suggests should be + * provided to the user. + * @hide + */ + @FlaggedApi(FLAG_ENABLE_SUGGESTED_DEVICE_API) + public void setDeviceSuggestions(@Nullable List<SuggestedDeviceInfo> suggestedDeviceInfo) { + mImpl.setDeviceSuggestions(suggestedDeviceInfo); + } + + /** + * Gets the current suggested devices. + * + * @return the suggested devices, keyed by the package name providing each suggestion list. + * @hide + */ + @FlaggedApi(FLAG_ENABLE_SUGGESTED_DEVICE_API) + @Nullable + public Map<String, List<SuggestedDeviceInfo>> getDeviceSuggestions() { + return mImpl.getDeviceSuggestions(); + } + + /** * Returns the current {@link RouteListingPreference} of the target router. * * <p>If this instance was created using {@code #getInstance(Context, String)}, then it returns @@ -1518,6 +1591,17 @@ public final class MediaRouter2 { } } + private void notifyDeviceSuggestionsUpdated( + @NonNull String suggestingPackageName, + @Nullable List<SuggestedDeviceInfo> deviceSuggestions) { + for (DeviceSuggestionsCallbackRecord record : mDeviceSuggestionsCallbackRecords) { + record.mExecutor.execute( + () -> + record.mDeviceSuggestionsCallback.onSuggestionUpdated( + suggestingPackageName, deviceSuggestions)); + } + } + private void notifyTransfer(RoutingController oldController, RoutingController newController) { for (TransferCallbackRecord record : mTransferCallbackRecords) { record.mExecutor.execute( @@ -1568,6 +1652,25 @@ public final class MediaRouter2 { .build(); } + /** + * Callback for receiving events about device suggestions + * + * @hide + */ + public interface DeviceSuggestionsCallback { + + /** + * Called when suggestions are updated. Whenever you register a callback, this will be + * invoked with the current suggestions. + * + * @param suggestingPackageName the package that provided the suggestions. + * @param suggestedDeviceInfo the suggestions provided by the package. + */ + void onSuggestionUpdated( + @NonNull String suggestingPackageName, + @Nullable List<SuggestedDeviceInfo> suggestedDeviceInfo); + } + /** Callback for receiving events about media route discovery. */ public abstract static class RouteCallback { /** @@ -2326,6 +2429,35 @@ public final class MediaRouter2 { } } + private static final class DeviceSuggestionsCallbackRecord { + public final Executor mExecutor; + public final DeviceSuggestionsCallback mDeviceSuggestionsCallback; + + /* package */ DeviceSuggestionsCallbackRecord( + @NonNull Executor executor, + @NonNull DeviceSuggestionsCallback deviceSuggestionsCallback) { + mExecutor = executor; + mDeviceSuggestionsCallback = deviceSuggestionsCallback; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof DeviceSuggestionsCallbackRecord)) { + return false; + } + return mDeviceSuggestionsCallback + == ((DeviceSuggestionsCallbackRecord) obj).mDeviceSuggestionsCallback; + } + + @Override + public int hashCode() { + return mDeviceSuggestionsCallback.hashCode(); + } + } + static final class TransferCallbackRecord { public final Executor mExecutor; public final TransferCallback mTransferCallback; @@ -2446,6 +2578,17 @@ public final class MediaRouter2 { } @Override + public void notifyDeviceSuggestionsUpdated( + String suggestingPackageName, List<SuggestedDeviceInfo> suggestions) { + mHandler.sendMessage( + obtainMessage( + MediaRouter2::notifyDeviceSuggestionsUpdated, + MediaRouter2.this, + suggestingPackageName, + suggestions)); + } + + @Override public void requestCreateSessionByManager( long managerRequestId, RoutingSessionInfo oldSession, MediaRoute2Info route) { mHandler.sendMessage( @@ -2487,6 +2630,11 @@ public final class MediaRouter2 { void setRouteListingPreference(@Nullable RouteListingPreference preference); + void setDeviceSuggestions(@Nullable List<SuggestedDeviceInfo> suggestedDeviceInfo); + + @Nullable + Map<String, List<SuggestedDeviceInfo>> getDeviceSuggestions(); + boolean showSystemOutputSwitcher(); List<MediaRoute2Info> getAllRoutes(); @@ -2687,6 +2835,29 @@ public final class MediaRouter2 { } @Override + public void setDeviceSuggestions(@Nullable List<SuggestedDeviceInfo> suggestedDeviceInfo) { + synchronized (mLock) { + try { + mMediaRouterService.setDeviceSuggestionsWithManager( + mClient, suggestedDeviceInfo); + } catch (RemoteException ex) { + ex.rethrowFromSystemServer(); + } + } + } + + @Override + public Map<String, List<SuggestedDeviceInfo>> getDeviceSuggestions() { + synchronized (mLock) { + try { + return mMediaRouterService.getDeviceSuggestionsWithManager(mClient); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + } + + @Override public boolean showSystemOutputSwitcher() { try { return mMediaRouterService.showMediaOutputSwitcherWithProxyRouter(mClient); @@ -3296,6 +3467,23 @@ public final class MediaRouter2 { notifyRouteListingPreferenceUpdated(routeListingPreference); } + private void onDeviceSuggestionsChangeHandler( + @NonNull String packageName, + @NonNull String suggestingPackageName, + @Nullable List<SuggestedDeviceInfo> suggestedDeviceInfo) { + if (!TextUtils.equals(getClientPackageName(), packageName)) { + return; + } + synchronized (mLock) { + if (Objects.equals( + mSuggestedDeviceInfo.get(suggestingPackageName), suggestedDeviceInfo)) { + return; + } + mSuggestedDeviceInfo.put(suggestingPackageName, suggestedDeviceInfo); + } + notifyDeviceSuggestionsUpdated(suggestingPackageName, suggestedDeviceInfo); + } + private void onRequestFailedOnHandler(int requestId, int reason) { MediaRouter2Manager.TransferRequest matchingRequest = null; for (MediaRouter2Manager.TransferRequest request : mTransferRequests) { @@ -3390,6 +3578,20 @@ public final class MediaRouter2 { } @Override + public void notifyDeviceSuggestionsUpdated( + String packageName, + String suggestingPackageName, + @Nullable List<SuggestedDeviceInfo> deviceSuggestions) { + mHandler.sendMessage( + obtainMessage( + ProxyMediaRouter2Impl::onDeviceSuggestionsChangeHandler, + ProxyMediaRouter2Impl.this, + packageName, + suggestingPackageName, + deviceSuggestions)); + } + + @Override public void notifyRoutesUpdated(List<MediaRoute2Info> routes) { mHandler.sendMessage( obtainMessage( @@ -3553,6 +3755,30 @@ public final class MediaRouter2 { } @Override + public void setDeviceSuggestions(@Nullable List<SuggestedDeviceInfo> deviceSuggestions) { + synchronized (mLock) { + try { + registerRouterStubIfNeededLocked(); + mMediaRouterService.setDeviceSuggestionsWithRouter2(mStub, deviceSuggestions); + } catch (RemoteException ex) { + ex.rethrowFromSystemServer(); + } + } + } + + @Override + @Nullable + public Map<String, List<SuggestedDeviceInfo>> getDeviceSuggestions() { + synchronized (mLock) { + try { + return mMediaRouterService.getDeviceSuggestionsWithRouter2(mStub); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + } + + @Override public boolean showSystemOutputSwitcher() { synchronized (mLock) { try { diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java index 3f18eef2f9aa..bf88709eec33 100644 --- a/media/java/android/media/MediaRouter2Manager.java +++ b/media/java/android/media/MediaRouter2Manager.java @@ -1138,6 +1138,14 @@ public final class MediaRouter2Manager { } @Override + public void notifyDeviceSuggestionsUpdated( + String packageName, + String suggestingPackageName, + @Nullable List<SuggestedDeviceInfo> suggestedDeviceInfo) { + // MediaRouter2Manager doesn't support device suggestions + } + + @Override public void notifyRoutesUpdated(List<MediaRoute2Info> routes) { mHandler.sendMessage( obtainMessage( diff --git a/media/java/android/media/SuggestedDeviceInfo.aidl b/media/java/android/media/SuggestedDeviceInfo.aidl new file mode 100644 index 000000000000..eab642572ed2 --- /dev/null +++ b/media/java/android/media/SuggestedDeviceInfo.aidl @@ -0,0 +1,19 @@ +/* + * Copyright 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media; + +parcelable SuggestedDeviceInfo; diff --git a/media/java/android/media/SuggestedDeviceInfo.java b/media/java/android/media/SuggestedDeviceInfo.java new file mode 100644 index 000000000000..2aa139fcca17 --- /dev/null +++ b/media/java/android/media/SuggestedDeviceInfo.java @@ -0,0 +1,235 @@ +/* + * Copyright 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media; + +import static com.android.media.flags.Flags.FLAG_ENABLE_SUGGESTED_DEVICE_API; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; + +import java.util.Objects; + +/** + * Allows applications to suggest routes to the system UI (for example, in the System UI Output + * Switcher). + * + * @see MediaRouter2#setSuggestedDevice + * @hide + */ +@FlaggedApi(FLAG_ENABLE_SUGGESTED_DEVICE_API) +public final class SuggestedDeviceInfo implements Parcelable { + @NonNull + public static final Creator<SuggestedDeviceInfo> CREATOR = + new Creator<>() { + @Override + public SuggestedDeviceInfo createFromParcel(Parcel in) { + return new SuggestedDeviceInfo(in); + } + + @Override + public SuggestedDeviceInfo[] newArray(int size) { + return new SuggestedDeviceInfo[size]; + } + }; + + @NonNull private final String mDeviceDisplayName; + + @NonNull private final String mRouteId; + + private final int mType; + + @NonNull private final Bundle mExtras; + + private SuggestedDeviceInfo(Builder builder) { + mDeviceDisplayName = builder.mDeviceDisplayName; + mRouteId = builder.mRouteId; + mType = builder.mType; + mExtras = builder.mExtras; + } + + private SuggestedDeviceInfo(Parcel in) { + mDeviceDisplayName = in.readString(); + mRouteId = in.readString(); + mType = in.readInt(); + mExtras = in.readBundle(); + } + + /** + * Returns the name to be displayed to the user. + * + * @return The device display name. + */ + @NonNull + public String getDeviceDisplayName() { + return mDeviceDisplayName; + } + + /** + * Returns the route ID associated with the suggestion. + * + * @return The route ID. + */ + @NonNull + public String getRouteId() { + return mRouteId; + } + + /** + * Returns the device type associated with the suggestion. + * + * @return The device type. + */ + public int getType() { + return mType; + } + + /** + * Returns the extras associated with the suggestion. + * + * @return The extras. + */ + @Nullable + public Bundle getExtras() { + return mExtras; + } + + // SuggestedDeviceInfo Parcelable implementation. + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString(mDeviceDisplayName); + dest.writeString(mRouteId); + dest.writeInt(mType); + dest.writeBundle(mExtras); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof SuggestedDeviceInfo)) { + return false; + } + return Objects.equals(mDeviceDisplayName, ((SuggestedDeviceInfo) obj).mDeviceDisplayName) + && Objects.equals(mRouteId, ((SuggestedDeviceInfo) obj).mRouteId) + && mType == ((SuggestedDeviceInfo) obj).mType; + } + + @Override + public int hashCode() { + return Objects.hash(mDeviceDisplayName, mRouteId, mType); + } + + @Override + public String toString() { + return mDeviceDisplayName + " | " + mRouteId + " | " + mType; + } + + /** Builder for {@link SuggestedDeviceInfo}. */ + public static final class Builder { + @NonNull private String mDeviceDisplayName; + + @NonNull private String mRouteId; + + @NonNull private Integer mType; + + private Bundle mExtras = Bundle.EMPTY; + + /** + * Creates a new SuggestedDeviceInfo. The device display name, route ID, and type must be + * set. The extras cannot be null, but default to an empty {@link Bundle}. + * + * @throws IllegalArgumentException if the builder has a mandatory unset field. + */ + public SuggestedDeviceInfo build() { + if (mDeviceDisplayName == null) { + throw new IllegalArgumentException("Device display name cannot be null"); + } + + if (mRouteId == null) { + throw new IllegalArgumentException("Route ID cannot be null."); + } + + if (mType == null) { + throw new IllegalArgumentException("Device type cannot be null."); + } + + if (mExtras == null) { + throw new IllegalArgumentException("Extras cannot be null."); + } + + return new SuggestedDeviceInfo(this); + } + + /** + * Sets the {@link #getDeviceDisplayName() device display name}. + * + * @throws IllegalArgumentException if the name is null or empty. + */ + public Builder setDeviceDisplayName(@NonNull String deviceDisplayName) { + if (TextUtils.isEmpty(deviceDisplayName)) { + throw new IllegalArgumentException("Device display name cannot be null"); + } + mDeviceDisplayName = deviceDisplayName; + return this; + } + + /** + * Sets the {@link #getRouteId() route id}. + * + * @throws IllegalArgumentException if the route id is null or empty. + */ + public Builder setRouteId(@NonNull String routeId) { + if (TextUtils.isEmpty(routeId)) { + throw new IllegalArgumentException("Device display name cannot be null"); + } + mRouteId = routeId; + return this; + } + + /** Sets the {@link #getType() device type}. */ + public Builder setType(int type) { + mType = type; + return this; + } + + /** + * Sets the {@link #getExtras() extras}. + * + * <p>The default value is an empty {@link Bundle}. + * + * <p>Do not mutate the given {@link Bundle} after passing it to this method. You can use + * {@link Bundle#deepCopy()} to keep a mutable copy. + * + * @throws NullPointerException if the extras are null. + */ + public Builder setExtras(@NonNull Bundle extras) { + mExtras = Objects.requireNonNull(extras, "extras must not be null"); + return this; + } + } +} diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig index 0deed3982d9b..e39a0aa8717e 100644 --- a/media/java/android/media/flags/media_better_together.aconfig +++ b/media/java/android/media/flags/media_better_together.aconfig @@ -62,6 +62,14 @@ flag { } flag { + name: "enable_suggested_device_api" + is_exported: true + namespace: "media_better_together" + description: "Enables the API allowing proxy routers to suggest routes." + bug: "393216553" +} + +flag { name: "enable_full_scan_with_media_content_control" namespace: "media_better_together" description: "Allows holders of the MEDIA_CONTENT_CONTROL permission to scan for routes while not in the foreground." diff --git a/media/java/android/media/quality/Android.bp b/media/java/android/media/quality/Android.bp new file mode 100644 index 000000000000..080d5266ccb7 --- /dev/null +++ b/media/java/android/media/quality/Android.bp @@ -0,0 +1,39 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +filegroup { + name: "framework-media-quality-sources-aidl", + srcs: [ + "aidl/android/media/quality/*.aidl", + ], + path: "aidl", +} + +aidl_interface { + name: "media_quality_aidl_interface", + unstable: true, + local_include_dir: "aidl", + backend: { + java: { + enabled: true, + }, + cpp: { + enabled: false, + }, + ndk: { + enabled: false, + }, + rust: { + enabled: false, + }, + }, + srcs: [ + ":framework-media-quality-sources-aidl", + ], +} diff --git a/media/java/android/media/quality/MediaQualityContract.java b/media/java/android/media/quality/MediaQualityContract.java index e4de3e4420fe..fccdba8e727f 100644 --- a/media/java/android/media/quality/MediaQualityContract.java +++ b/media/java/android/media/quality/MediaQualityContract.java @@ -82,7 +82,7 @@ public class MediaQualityContract { String PARAMETER_NAME = "_name"; String PARAMETER_PACKAGE = "_package"; String PARAMETER_INPUT_ID = "_input_id"; - + String VENDOR_PARAMETERS = "_vendor_parameters"; } /** diff --git a/media/java/android/media/quality/MediaQualityManager.java b/media/java/android/media/quality/MediaQualityManager.java index 0d6d32a22dae..bfd01380a2ee 100644 --- a/media/java/android/media/quality/MediaQualityManager.java +++ b/media/java/android/media/quality/MediaQualityManager.java @@ -274,9 +274,9 @@ public final class MediaQualityManager { @NonNull String name, @Nullable ProfileQueryParams options) { try { - Bundle optionsBundle = options == null - ? ProfileQueryParams.DEFAULT.toBundle() : options.toBundle(); - return mService.getPictureProfile(type, name, optionsBundle, mUserHandle); + boolean includeParams = options == null || options.mParametersIncluded; + return mService.getPictureProfile( + type, name, includeParams, mUserHandle.getIdentifier()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -299,10 +299,9 @@ public final class MediaQualityManager { public List<PictureProfile> getPictureProfilesByPackage( @NonNull String packageName, @Nullable ProfileQueryParams options) { try { - Bundle optionsBundle = options == null - ? ProfileQueryParams.DEFAULT.toBundle() : options.toBundle(); + boolean includeParams = options == null || options.mParametersIncluded; return mService.getPictureProfilesByPackage( - packageName, optionsBundle, mUserHandle); + packageName, includeParams, mUserHandle.getIdentifier()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -321,9 +320,8 @@ public final class MediaQualityManager { @NonNull public List<PictureProfile> getAvailablePictureProfiles(@Nullable ProfileQueryParams options) { try { - Bundle optionsBundle = options == null - ? ProfileQueryParams.DEFAULT.toBundle() : options.toBundle(); - return mService.getAvailablePictureProfiles(optionsBundle, mUserHandle); + boolean includeParams = options == null || options.mParametersIncluded; + return mService.getAvailablePictureProfiles(includeParams, mUserHandle.getIdentifier()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -344,7 +342,7 @@ public final class MediaQualityManager { @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE) public boolean setDefaultPictureProfile(@Nullable String pictureProfileId) { try { - return mService.setDefaultPictureProfile(pictureProfileId, mUserHandle); + return mService.setDefaultPictureProfile(pictureProfileId, mUserHandle.getIdentifier()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -361,7 +359,7 @@ public final class MediaQualityManager { @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE) public List<String> getPictureProfilePackageNames() { try { - return mService.getPictureProfilePackageNames(mUserHandle); + return mService.getPictureProfilePackageNames(mUserHandle.getIdentifier()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -373,7 +371,7 @@ public final class MediaQualityManager { */ public List<PictureProfileHandle> getPictureProfileHandle(String[] id) { try { - return mService.getPictureProfileHandle(id, mUserHandle); + return mService.getPictureProfileHandle(id, mUserHandle.getIdentifier()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -385,7 +383,7 @@ public final class MediaQualityManager { */ public List<SoundProfileHandle> getSoundProfileHandle(String[] id) { try { - return mService.getSoundProfileHandle(id, mUserHandle); + return mService.getSoundProfileHandle(id, mUserHandle.getIdentifier()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -401,7 +399,7 @@ public final class MediaQualityManager { */ public void createPictureProfile(@NonNull PictureProfile pp) { try { - mService.createPictureProfile(pp, mUserHandle); + mService.createPictureProfile(pp, mUserHandle.getIdentifier()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -416,7 +414,7 @@ public final class MediaQualityManager { */ public void updatePictureProfile(@NonNull String profileId, @NonNull PictureProfile pp) { try { - mService.updatePictureProfile(profileId, pp, mUserHandle); + mService.updatePictureProfile(profileId, pp, mUserHandle.getIdentifier()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -430,7 +428,7 @@ public final class MediaQualityManager { */ public void removePictureProfile(@NonNull String profileId) { try { - mService.removePictureProfile(profileId, mUserHandle); + mService.removePictureProfile(profileId, mUserHandle.getIdentifier()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -484,9 +482,8 @@ public final class MediaQualityManager { @NonNull String name, @Nullable ProfileQueryParams options) { try { - Bundle optionsBundle = options == null - ? ProfileQueryParams.DEFAULT.toBundle() : options.toBundle(); - return mService.getSoundProfile(type, name, optionsBundle, mUserHandle); + boolean includeParams = options == null || options.mParametersIncluded; + return mService.getSoundProfile(type, name, includeParams, mUserHandle.getIdentifier()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -510,9 +507,9 @@ public final class MediaQualityManager { public List<SoundProfile> getSoundProfilesByPackage( @NonNull String packageName, @Nullable ProfileQueryParams options) { try { - Bundle optionsBundle = options == null - ? ProfileQueryParams.DEFAULT.toBundle() : options.toBundle(); - return mService.getSoundProfilesByPackage(packageName, optionsBundle, mUserHandle); + boolean includeParams = options == null || options.mParametersIncluded; + return mService.getSoundProfilesByPackage( + packageName, includeParams, mUserHandle.getIdentifier()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -531,9 +528,8 @@ public final class MediaQualityManager { @NonNull public List<SoundProfile> getAvailableSoundProfiles(@Nullable ProfileQueryParams options) { try { - Bundle optionsBundle = options == null - ? ProfileQueryParams.DEFAULT.toBundle() : options.toBundle(); - return mService.getAvailableSoundProfiles(optionsBundle, mUserHandle); + boolean includeParams = options == null || options.mParametersIncluded; + return mService.getAvailableSoundProfiles(includeParams, mUserHandle.getIdentifier()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -554,7 +550,7 @@ public final class MediaQualityManager { @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE) public boolean setDefaultSoundProfile(@Nullable String soundProfileId) { try { - return mService.setDefaultSoundProfile(soundProfileId, mUserHandle); + return mService.setDefaultSoundProfile(soundProfileId, mUserHandle.getIdentifier()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -572,7 +568,7 @@ public final class MediaQualityManager { @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE) public List<String> getSoundProfilePackageNames() { try { - return mService.getSoundProfilePackageNames(mUserHandle); + return mService.getSoundProfilePackageNames(mUserHandle.getIdentifier()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -589,7 +585,7 @@ public final class MediaQualityManager { */ public void createSoundProfile(@NonNull SoundProfile sp) { try { - mService.createSoundProfile(sp, mUserHandle); + mService.createSoundProfile(sp, mUserHandle.getIdentifier()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -604,7 +600,7 @@ public final class MediaQualityManager { */ public void updateSoundProfile(@NonNull String profileId, @NonNull SoundProfile sp) { try { - mService.updateSoundProfile(profileId, sp, mUserHandle); + mService.updateSoundProfile(profileId, sp, mUserHandle.getIdentifier()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -618,7 +614,7 @@ public final class MediaQualityManager { */ public void removeSoundProfile(@NonNull String profileId) { try { - mService.removeSoundProfile(profileId, mUserHandle); + mService.removeSoundProfile(profileId, mUserHandle.getIdentifier()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -636,7 +632,7 @@ public final class MediaQualityManager { @NonNull public List<ParameterCapability> getParameterCapabilities(@NonNull List<String> names) { try { - return mService.getParameterCapabilities(names, mUserHandle); + return mService.getParameterCapabilities(names, mUserHandle.getIdentifier()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -654,7 +650,7 @@ public final class MediaQualityManager { @NonNull public List<String> getPictureProfileAllowList() { try { - return mService.getPictureProfileAllowList(mUserHandle); + return mService.getPictureProfileAllowList(mUserHandle.getIdentifier()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -668,7 +664,7 @@ public final class MediaQualityManager { @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE) public void setPictureProfileAllowList(@NonNull List<String> packageNames) { try { - mService.setPictureProfileAllowList(packageNames, mUserHandle); + mService.setPictureProfileAllowList(packageNames, mUserHandle.getIdentifier()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -686,7 +682,7 @@ public final class MediaQualityManager { @NonNull public List<String> getSoundProfileAllowList() { try { - return mService.getSoundProfileAllowList(mUserHandle); + return mService.getSoundProfileAllowList(mUserHandle.getIdentifier()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -700,7 +696,7 @@ public final class MediaQualityManager { @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE) public void setSoundProfileAllowList(@NonNull List<String> packageNames) { try { - mService.setSoundProfileAllowList(packageNames, mUserHandle); + mService.setSoundProfileAllowList(packageNames, mUserHandle.getIdentifier()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -712,7 +708,7 @@ public final class MediaQualityManager { */ public boolean isSupported() { try { - return mService.isSupported(mUserHandle); + return mService.isSupported(mUserHandle.getIdentifier()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -730,7 +726,7 @@ public final class MediaQualityManager { @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE) public void setAutoPictureQualityEnabled(boolean enabled) { try { - mService.setAutoPictureQualityEnabled(enabled, mUserHandle); + mService.setAutoPictureQualityEnabled(enabled, mUserHandle.getIdentifier()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -741,7 +737,7 @@ public final class MediaQualityManager { */ public boolean isAutoPictureQualityEnabled() { try { - return mService.isAutoPictureQualityEnabled(mUserHandle); + return mService.isAutoPictureQualityEnabled(mUserHandle.getIdentifier()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -758,7 +754,7 @@ public final class MediaQualityManager { @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE) public void setSuperResolutionEnabled(boolean enabled) { try { - mService.setSuperResolutionEnabled(enabled, mUserHandle); + mService.setSuperResolutionEnabled(enabled, mUserHandle.getIdentifier()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -769,7 +765,7 @@ public final class MediaQualityManager { */ public boolean isSuperResolutionEnabled() { try { - return mService.isSuperResolutionEnabled(mUserHandle); + return mService.isSuperResolutionEnabled(mUserHandle.getIdentifier()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -787,7 +783,7 @@ public final class MediaQualityManager { @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE) public void setAutoSoundQualityEnabled(boolean enabled) { try { - mService.setAutoSoundQualityEnabled(enabled, mUserHandle); + mService.setAutoSoundQualityEnabled(enabled, mUserHandle.getIdentifier()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -798,7 +794,7 @@ public final class MediaQualityManager { */ public boolean isAutoSoundQualityEnabled() { try { - return mService.isAutoSoundQualityEnabled(mUserHandle); + return mService.isAutoSoundQualityEnabled(mUserHandle.getIdentifier()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -847,7 +843,7 @@ public final class MediaQualityManager { @NonNull AmbientBacklightSettings settings) { Preconditions.checkNotNull(settings); try { - mService.setAmbientBacklightSettings(settings, mUserHandle); + mService.setAmbientBacklightSettings(settings, mUserHandle.getIdentifier()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -858,7 +854,7 @@ public final class MediaQualityManager { */ public boolean isAmbientBacklightEnabled() { try { - return mService.isAmbientBacklightEnabled(mUserHandle); + return mService.isAmbientBacklightEnabled(mUserHandle.getIdentifier()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -872,7 +868,7 @@ public final class MediaQualityManager { @RequiresPermission(android.Manifest.permission.READ_COLOR_ZONES) public void setAmbientBacklightEnabled(boolean enabled) { try { - mService.setAmbientBacklightEnabled(enabled, mUserHandle); + mService.setAmbientBacklightEnabled(enabled, mUserHandle.getIdentifier()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/media/java/android/media/quality/SoundProfileHandle.java b/media/java/android/media/quality/SoundProfileHandle.java deleted file mode 100644 index edb546efdaf3..000000000000 --- a/media/java/android/media/quality/SoundProfileHandle.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.media.quality; - -import android.annotation.NonNull; -import android.os.Parcel; -import android.os.Parcelable; - -/** - * A type-safe handle to a sound profile. - * - * @hide - */ -public final class SoundProfileHandle implements Parcelable { - public static final @NonNull SoundProfileHandle NONE = new SoundProfileHandle(-1000); - - private final long mId; - - /** @hide */ - public SoundProfileHandle(long id) { - mId = id; - } - - /** @hide */ - public long getId() { - return mId; - } - - /** @hide */ - @Override - public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeLong(mId); - } - - /** @hide */ - @Override - public int describeContents() { - return 0; - } - - /** @hide */ - public static final @NonNull Creator<SoundProfileHandle> CREATOR = - new Creator<SoundProfileHandle>() { - @Override - public SoundProfileHandle createFromParcel(Parcel in) { - return new SoundProfileHandle(in); - } - - @Override - public SoundProfileHandle[] newArray(int size) { - return new SoundProfileHandle[size]; - } - }; - - private SoundProfileHandle(@NonNull Parcel in) { - mId = in.readLong(); - } -} diff --git a/media/java/android/media/quality/ActiveProcessingPicture.aidl b/media/java/android/media/quality/aidl/android/media/quality/ActiveProcessingPicture.aidl index 2851306f6e4d..2851306f6e4d 100644 --- a/media/java/android/media/quality/ActiveProcessingPicture.aidl +++ b/media/java/android/media/quality/aidl/android/media/quality/ActiveProcessingPicture.aidl diff --git a/media/java/android/media/quality/AmbientBacklightEvent.aidl b/media/java/android/media/quality/aidl/android/media/quality/AmbientBacklightEvent.aidl index 174cd461e846..174cd461e846 100644 --- a/media/java/android/media/quality/AmbientBacklightEvent.aidl +++ b/media/java/android/media/quality/aidl/android/media/quality/AmbientBacklightEvent.aidl diff --git a/media/java/android/media/quality/AmbientBacklightMetadata.aidl b/media/java/android/media/quality/aidl/android/media/quality/AmbientBacklightMetadata.aidl index b95a474fbf90..b95a474fbf90 100644 --- a/media/java/android/media/quality/AmbientBacklightMetadata.aidl +++ b/media/java/android/media/quality/aidl/android/media/quality/AmbientBacklightMetadata.aidl diff --git a/media/java/android/media/quality/AmbientBacklightSettings.aidl b/media/java/android/media/quality/aidl/android/media/quality/AmbientBacklightSettings.aidl index e2cdd03194cd..e2cdd03194cd 100644 --- a/media/java/android/media/quality/AmbientBacklightSettings.aidl +++ b/media/java/android/media/quality/aidl/android/media/quality/AmbientBacklightSettings.aidl diff --git a/media/java/android/media/quality/IAmbientBacklightCallback.aidl b/media/java/android/media/quality/aidl/android/media/quality/IAmbientBacklightCallback.aidl index 159f5b7b5e71..159f5b7b5e71 100644 --- a/media/java/android/media/quality/IAmbientBacklightCallback.aidl +++ b/media/java/android/media/quality/aidl/android/media/quality/IAmbientBacklightCallback.aidl diff --git a/media/java/android/media/quality/IMediaQualityManager.aidl b/media/java/android/media/quality/aidl/android/media/quality/IMediaQualityManager.aidl index 6e9fa1dcf93d..0191ea786de0 100644 --- a/media/java/android/media/quality/IMediaQualityManager.aidl +++ b/media/java/android/media/quality/aidl/android/media/quality/IMediaQualityManager.aidl @@ -25,57 +25,57 @@ import android.media.quality.PictureProfileHandle; import android.media.quality.PictureProfile; import android.media.quality.SoundProfileHandle; import android.media.quality.SoundProfile; -import android.os.Bundle; -import android.os.UserHandle; /** * Interface for Media Quality Manager * @hide */ interface IMediaQualityManager { - PictureProfile createPictureProfile(in PictureProfile pp, in UserHandle user); - void updatePictureProfile(in String id, in PictureProfile pp, in UserHandle user); - void removePictureProfile(in String id, in UserHandle user); - boolean setDefaultPictureProfile(in String id, in UserHandle user); + // TODO: use UserHandle + PictureProfile createPictureProfile(in PictureProfile pp, int userId); + void updatePictureProfile(in String id, in PictureProfile pp, int userId); + void removePictureProfile(in String id, int userId); + boolean setDefaultPictureProfile(in String id, int userId); + // TODO: use Bundle for includeParams PictureProfile getPictureProfile( - in int type, in String name, in Bundle options, in UserHandle user); + in int type, in String name, in boolean includeParams, int userId); List<PictureProfile> getPictureProfilesByPackage( - in String packageName, in Bundle options, in UserHandle user); - List<PictureProfile> getAvailablePictureProfiles(in Bundle options, in UserHandle user); - List<String> getPictureProfilePackageNames(in UserHandle user); - List<String> getPictureProfileAllowList(in UserHandle user); - void setPictureProfileAllowList(in List<String> packages, in UserHandle user); - List<PictureProfileHandle> getPictureProfileHandle(in String[] id, in UserHandle user); + in String packageName, in boolean includeParams, int userId); + List<PictureProfile> getAvailablePictureProfiles(in boolean includeParams, int userId); + List<String> getPictureProfilePackageNames(int userId); + List<String> getPictureProfileAllowList(int userId); + void setPictureProfileAllowList(in List<String> packages, int userId); + List<PictureProfileHandle> getPictureProfileHandle(in String[] id, int userId); - SoundProfile createSoundProfile(in SoundProfile pp, in UserHandle user); - void updateSoundProfile(in String id, in SoundProfile pp, in UserHandle user); - void removeSoundProfile(in String id, in UserHandle user); - boolean setDefaultSoundProfile(in String id, in UserHandle user); + SoundProfile createSoundProfile(in SoundProfile pp, int userId); + void updateSoundProfile(in String id, in SoundProfile pp, int userId); + void removeSoundProfile(in String id, int userId); + boolean setDefaultSoundProfile(in String id, int userId); SoundProfile getSoundProfile( - in int type, in String name, in Bundle options, in UserHandle user); + in int type, in String name, in boolean includeParams, int userId); List<SoundProfile> getSoundProfilesByPackage( - in String packageName, in Bundle options, in UserHandle user); - List<SoundProfile> getAvailableSoundProfiles(in Bundle options, in UserHandle user); - List<String> getSoundProfilePackageNames(in UserHandle user); - List<String> getSoundProfileAllowList(in UserHandle user); - void setSoundProfileAllowList(in List<String> packages, in UserHandle user); - List<SoundProfileHandle> getSoundProfileHandle(in String[] id, in UserHandle user); + in String packageName, in boolean includeParams, int userId); + List<SoundProfile> getAvailableSoundProfiles(in boolean includeParams, int userId); + List<String> getSoundProfilePackageNames(int userId); + List<String> getSoundProfileAllowList(int userId); + void setSoundProfileAllowList(in List<String> packages, int userId); + List<SoundProfileHandle> getSoundProfileHandle(in String[] id, int userId); void registerPictureProfileCallback(in IPictureProfileCallback cb); void registerSoundProfileCallback(in ISoundProfileCallback cb); void registerAmbientBacklightCallback(in IAmbientBacklightCallback cb); - List<ParameterCapability> getParameterCapabilities(in List<String> names, in UserHandle user); + List<ParameterCapability> getParameterCapabilities(in List<String> names, int userId); - boolean isSupported(in UserHandle user); - void setAutoPictureQualityEnabled(in boolean enabled, in UserHandle user); - boolean isAutoPictureQualityEnabled(in UserHandle user); - void setSuperResolutionEnabled(in boolean enabled, in UserHandle user); - boolean isSuperResolutionEnabled(in UserHandle user); - void setAutoSoundQualityEnabled(in boolean enabled, in UserHandle user); - boolean isAutoSoundQualityEnabled(in UserHandle user); + boolean isSupported(int userId); + void setAutoPictureQualityEnabled(in boolean enabled, int userId); + boolean isAutoPictureQualityEnabled(int userId); + void setSuperResolutionEnabled(in boolean enabled, int userId); + boolean isSuperResolutionEnabled(int userId); + void setAutoSoundQualityEnabled(in boolean enabled, int userId); + boolean isAutoSoundQualityEnabled(int userId); - void setAmbientBacklightSettings(in AmbientBacklightSettings settings, in UserHandle user); - void setAmbientBacklightEnabled(in boolean enabled, in UserHandle user); - boolean isAmbientBacklightEnabled(in UserHandle user); + void setAmbientBacklightSettings(in AmbientBacklightSettings settings, int userId); + void setAmbientBacklightEnabled(in boolean enabled, int userId); + boolean isAmbientBacklightEnabled(int userId); } diff --git a/media/java/android/media/quality/IPictureProfileCallback.aidl b/media/java/android/media/quality/aidl/android/media/quality/IPictureProfileCallback.aidl index eed77f695416..eed77f695416 100644 --- a/media/java/android/media/quality/IPictureProfileCallback.aidl +++ b/media/java/android/media/quality/aidl/android/media/quality/IPictureProfileCallback.aidl diff --git a/media/java/android/media/quality/ISoundProfileCallback.aidl b/media/java/android/media/quality/aidl/android/media/quality/ISoundProfileCallback.aidl index 3871fb212259..3871fb212259 100644 --- a/media/java/android/media/quality/ISoundProfileCallback.aidl +++ b/media/java/android/media/quality/aidl/android/media/quality/ISoundProfileCallback.aidl diff --git a/media/java/android/media/quality/ParameterCapability.aidl b/media/java/android/media/quality/aidl/android/media/quality/ParameterCapability.aidl index eb2ac97916f3..eb2ac97916f3 100644 --- a/media/java/android/media/quality/ParameterCapability.aidl +++ b/media/java/android/media/quality/aidl/android/media/quality/ParameterCapability.aidl diff --git a/media/java/android/media/quality/PictureProfile.aidl b/media/java/android/media/quality/aidl/android/media/quality/PictureProfile.aidl index 41d018b12f33..41d018b12f33 100644 --- a/media/java/android/media/quality/PictureProfile.aidl +++ b/media/java/android/media/quality/aidl/android/media/quality/PictureProfile.aidl diff --git a/media/java/android/media/quality/PictureProfileHandle.aidl b/media/java/android/media/quality/aidl/android/media/quality/PictureProfileHandle.aidl index 5d14631dbb73..5d14631dbb73 100644 --- a/media/java/android/media/quality/PictureProfileHandle.aidl +++ b/media/java/android/media/quality/aidl/android/media/quality/PictureProfileHandle.aidl diff --git a/media/java/android/media/quality/SoundProfile.aidl b/media/java/android/media/quality/aidl/android/media/quality/SoundProfile.aidl index e79fcaac97be..e79fcaac97be 100644 --- a/media/java/android/media/quality/SoundProfile.aidl +++ b/media/java/android/media/quality/aidl/android/media/quality/SoundProfile.aidl diff --git a/media/java/android/media/quality/SoundProfileHandle.aidl b/media/java/android/media/quality/aidl/android/media/quality/SoundProfileHandle.aidl index 6b8161c8cc43..ea26b19d84d7 100644 --- a/media/java/android/media/quality/SoundProfileHandle.aidl +++ b/media/java/android/media/quality/aidl/android/media/quality/SoundProfileHandle.aidl @@ -16,4 +16,7 @@ package android.media.quality; -parcelable SoundProfileHandle; +// TODO: add SoundProfileHandle.java +parcelable SoundProfileHandle { + long id; +} diff --git a/media/java/android/media/tv/extension/scan/IHDPlusInfo.aidl b/media/java/android/media/tv/extension/scan/IHDPlusInfo.aidl index cdf6e23f4b47..40848fe6f875 100644 --- a/media/java/android/media/tv/extension/scan/IHDPlusInfo.aidl +++ b/media/java/android/media/tv/extension/scan/IHDPlusInfo.aidl @@ -21,5 +21,5 @@ package android.media.tv.extension.scan; */ interface IHDPlusInfo { // Specifying a HDPlusInfo and start a network scan. - int setHDPlusInfo(String isBlindScanContinue, String isHDMode); + int setHDPlusInfo(boolean isBlindScanContinue, boolean isHDMode); } diff --git a/media/java/android/media/tv/extension/scan/IScanListener.aidl b/media/java/android/media/tv/extension/scan/IScanListener.aidl index 2c4807f97c58..79810a7b035d 100644 --- a/media/java/android/media/tv/extension/scan/IScanListener.aidl +++ b/media/java/android/media/tv/extension/scan/IScanListener.aidl @@ -27,7 +27,7 @@ oneway interface IScanListener { // notify the scan progress. void onScanProgress(String scanProgress, in Bundle scanProgressInfo); // notify the scan completion. - void onScanCompleted(int scanResult); + void onScanCompleted(int scanResult, in Bundle optionScanInfo); // notify that the temporaily held channel list is stored. void onStoreCompleted(int storeResult); } diff --git a/media/java/android/media/tv/extension/scan/IScanSession.aidl b/media/java/android/media/tv/extension/scan/IScanSession.aidl index d42eca1342b5..f53b09661ba6 100644 --- a/media/java/android/media/tv/extension/scan/IScanSession.aidl +++ b/media/java/android/media/tv/extension/scan/IScanSession.aidl @@ -24,7 +24,7 @@ import android.os.Bundle; interface IScanSession { // Start a service scan. int startScan(int broadcastType, String countryCode, String operator, in int[] frequency, - String scanType, String languageCode); + String scanType, String languageCode, in Bundle optionalScanParams); // Reset the scan information held in TIS. int resetScan(); // Cancel scan. diff --git a/media/java/android/media/tv/extension/servicedb/IServiceListEdit.aidl b/media/java/android/media/tv/extension/servicedb/IServiceListEdit.aidl index 1b1577ffc015..0e9ac5f04e5d 100644 --- a/media/java/android/media/tv/extension/servicedb/IServiceListEdit.aidl +++ b/media/java/android/media/tv/extension/servicedb/IServiceListEdit.aidl @@ -78,4 +78,11 @@ interface IServiceListEdit { int addPredefinedChannelList(String serviceListId, in Bundle[] predefinedListBundle); // Add predefined satellite info of Hotbird 13E in scan two satellite scene EU region. int addPredefinedSatInfo(String serviceListId, in Bundle predefinedSatInfoBundle); + + // Get the logo URI for a specific service - DVB-I only. + String getServiceLogoUri(int serviceRecordId); + // Get the installed service list information for a specific channel list id - DVB-I only. + Bundle getInstalledServiceListInfo(String channelListId); + // Get all installed service list information - DVB-I only. + Bundle[] getAllInstalledServiceListInfo(); } diff --git a/media/java/android/media/tv/extension/servicedb/IServiceListImportListener.aidl b/media/java/android/media/tv/extension/servicedb/IServiceListImportListener.aidl index abd8320df11d..fced45b11d3b 100644 --- a/media/java/android/media/tv/extension/servicedb/IServiceListImportListener.aidl +++ b/media/java/android/media/tv/extension/servicedb/IServiceListImportListener.aidl @@ -16,10 +16,12 @@ package android.media.tv.extension.servicedb; +import android.os.Bundle; + /** * @hide */ interface IServiceListImportListener { void onImported(int importResult); - void onPreloaded(int preloadResult); + void onPreloaded(int preloadResult, in Bundle serviceListInfo); }
\ No newline at end of file diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp index 1e6a7b7f2810..45b746d254e1 100644 --- a/native/android/performance_hint.cpp +++ b/native/android/performance_hint.cpp @@ -971,7 +971,7 @@ void FMQWrapper::writeBuffer<HalChannelMessageContents::workDuration>(hal::WorkD .timeStampNanos = (i == count - 1) ? now : message.timeStampNanos, .data = HalChannelMessageContents::make<HalChannelMessageContents::workDuration, hal::WorkDurationFixedV1>({ - .durationNanos = message.cpuDurationNanos, + .durationNanos = message.durationNanos, .workPeriodStartTimestampNanos = message.workPeriodStartTimestampNanos, .cpuDurationNanos = message.cpuDurationNanos, .gpuDurationNanos = message.gpuDurationNanos, diff --git a/packages/CompanionDeviceManager/res/values-af/strings.xml b/packages/CompanionDeviceManager/res/values-af/strings.xml index b802d0f69a08..8e5a2b7c3663 100644 --- a/packages/CompanionDeviceManager/res/values-af/strings.xml +++ b/packages/CompanionDeviceManager/res/values-af/strings.xml @@ -51,10 +51,8 @@ <string name="consent_no" msgid="2640796915611404382">"Moenie toelaat nie"</string> <string name="consent_cancel" msgid="5655005528379285841">"Kanselleer"</string> <string name="consent_back" msgid="2560683030046918882">"Terug"</string> - <!-- no translation found for downward_arrow_action (2327165938832076333) --> - <skip /> - <!-- no translation found for downward_arrow (2292427714411156088) --> - <skip /> + <string name="downward_arrow_action" msgid="2327165938832076333">"Rollees met die lys af"</string> + <string name="downward_arrow" msgid="2292427714411156088">"Afwaartse pyl"</string> <string name="permission_expand" msgid="893185038020887411">"Vou <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> uit"</string> <string name="permission_collapse" msgid="3320833884220844084">"Vou <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> in"</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Gee programme op <strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> dieselfde toestemmings as op <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong>?"</string> diff --git a/packages/CompanionDeviceManager/res/values-am/strings.xml b/packages/CompanionDeviceManager/res/values-am/strings.xml index d4d0b0708302..40472d253072 100644 --- a/packages/CompanionDeviceManager/res/values-am/strings.xml +++ b/packages/CompanionDeviceManager/res/values-am/strings.xml @@ -51,10 +51,8 @@ <string name="consent_no" msgid="2640796915611404382">"አትፍቀድ"</string> <string name="consent_cancel" msgid="5655005528379285841">"ይቅር"</string> <string name="consent_back" msgid="2560683030046918882">"ተመለስ"</string> - <!-- no translation found for downward_arrow_action (2327165938832076333) --> - <skip /> - <!-- no translation found for downward_arrow (2292427714411156088) --> - <skip /> + <string name="downward_arrow_action" msgid="2327165938832076333">"በዝርዝሩ ላይ ወደታች ያሸብልሉ"</string> + <string name="downward_arrow" msgid="2292427714411156088">"የአውርድ ቀስት"</string> <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>ን ዘርጋ"</string> <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>ን ሰብስብ"</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"በ<strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> ላይ ላሉ መተግበሪያዎች በ<strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong> ላይ ካሉት ጋር ተመሳሳይ ፈቃዶች ይሰጣቸው?"</string> diff --git a/packages/CompanionDeviceManager/res/values-as/strings.xml b/packages/CompanionDeviceManager/res/values-as/strings.xml index 322d422f60d7..1f3ee732c8ce 100644 --- a/packages/CompanionDeviceManager/res/values-as/strings.xml +++ b/packages/CompanionDeviceManager/res/values-as/strings.xml @@ -51,10 +51,8 @@ <string name="consent_no" msgid="2640796915611404382">"অনুমতি নিদিব"</string> <string name="consent_cancel" msgid="5655005528379285841">"বাতিল কৰক"</string> <string name="consent_back" msgid="2560683030046918882">"উভতি যাওক"</string> - <!-- no translation found for downward_arrow_action (2327165938832076333) --> - <skip /> - <!-- no translation found for downward_arrow (2292427714411156088) --> - <skip /> + <string name="downward_arrow_action" msgid="2327165938832076333">"সূচীখনত তললৈ স্ক্ৰ’ল কৰক"</string> + <string name="downward_arrow" msgid="2292427714411156088">"তলমুৱা কাঁড়"</string> <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> বিস্তাৰ কৰক"</string> <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> সংকোচন কৰক"</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"এপ্সমূহক <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong>ত দিয়াৰ দৰে <strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong>তো একে অনুমতি প্ৰদান কৰিবনে?"</string> diff --git a/packages/CompanionDeviceManager/res/values-az/strings.xml b/packages/CompanionDeviceManager/res/values-az/strings.xml index 9f3ab2bd50cd..aa4788ac7e1e 100644 --- a/packages/CompanionDeviceManager/res/values-az/strings.xml +++ b/packages/CompanionDeviceManager/res/values-az/strings.xml @@ -51,10 +51,8 @@ <string name="consent_no" msgid="2640796915611404382">"İcazə verməyin"</string> <string name="consent_cancel" msgid="5655005528379285841">"Ləğv edin"</string> <string name="consent_back" msgid="2560683030046918882">"Geriyə"</string> - <!-- no translation found for downward_arrow_action (2327165938832076333) --> - <skip /> - <!-- no translation found for downward_arrow (2292427714411156088) --> - <skip /> + <string name="downward_arrow_action" msgid="2327165938832076333">"Siyahını aşağı sürüşdürün"</string> + <string name="downward_arrow" msgid="2292427714411156088">"Aşağı ox"</string> <string name="permission_expand" msgid="893185038020887411">"Genişləndirin: <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_collapse" msgid="3320833884220844084">"Yığcamlaşdırın: <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"<strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> cihazındakı tətbiqlərə <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong> cihazındakılarla eyni icazələr verilsin?"</string> diff --git a/packages/CompanionDeviceManager/res/values-b+sr+Latn/strings.xml b/packages/CompanionDeviceManager/res/values-b+sr+Latn/strings.xml index d28b9b26fb07..3dcb585a0bc2 100644 --- a/packages/CompanionDeviceManager/res/values-b+sr+Latn/strings.xml +++ b/packages/CompanionDeviceManager/res/values-b+sr+Latn/strings.xml @@ -51,10 +51,8 @@ <string name="consent_no" msgid="2640796915611404382">"Ne dozvoli"</string> <string name="consent_cancel" msgid="5655005528379285841">"Otkaži"</string> <string name="consent_back" msgid="2560683030046918882">"Nazad"</string> - <!-- no translation found for downward_arrow_action (2327165938832076333) --> - <skip /> - <!-- no translation found for downward_arrow (2292427714411156088) --> - <skip /> + <string name="downward_arrow_action" msgid="2327165938832076333">"Skrolujte nadole na listi"</string> + <string name="downward_arrow" msgid="2292427714411156088">"Strelica nadole"</string> <string name="permission_expand" msgid="893185038020887411">"Proširi <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_collapse" msgid="3320833884220844084">"Skupi <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Aplikcijama na uređaju <strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> dajete sve dozvole kao na uređaju <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong>?"</string> diff --git a/packages/CompanionDeviceManager/res/values-bn/strings.xml b/packages/CompanionDeviceManager/res/values-bn/strings.xml index 214dc71a6a88..f5cf720a64a5 100644 --- a/packages/CompanionDeviceManager/res/values-bn/strings.xml +++ b/packages/CompanionDeviceManager/res/values-bn/strings.xml @@ -51,10 +51,8 @@ <string name="consent_no" msgid="2640796915611404382">"অনুমতি দেবেন না"</string> <string name="consent_cancel" msgid="5655005528379285841">"বাতিল করুন"</string> <string name="consent_back" msgid="2560683030046918882">"ফিরুন"</string> - <!-- no translation found for downward_arrow_action (2327165938832076333) --> - <skip /> - <!-- no translation found for downward_arrow (2292427714411156088) --> - <skip /> + <string name="downward_arrow_action" msgid="2327165938832076333">"তালিকা নিচের দিকে স্ক্রল করুন"</string> + <string name="downward_arrow" msgid="2292427714411156088">"নিম্নমুখী তীরচিহ্ন"</string> <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> বড় করুন"</string> <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> আড়াল করুন"</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"<strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong>-এ যে অনুমতি দেওয়া আছে <strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong>-এও সেই একই অনুমতি দিতে চান?"</string> diff --git a/packages/CompanionDeviceManager/res/values-bs/strings.xml b/packages/CompanionDeviceManager/res/values-bs/strings.xml index b23f600cbcf0..a87ea18f831c 100644 --- a/packages/CompanionDeviceManager/res/values-bs/strings.xml +++ b/packages/CompanionDeviceManager/res/values-bs/strings.xml @@ -51,10 +51,8 @@ <string name="consent_no" msgid="2640796915611404382">"Nemoj dozvoliti"</string> <string name="consent_cancel" msgid="5655005528379285841">"Otkaži"</string> <string name="consent_back" msgid="2560683030046918882">"Nazad"</string> - <!-- no translation found for downward_arrow_action (2327165938832076333) --> - <skip /> - <!-- no translation found for downward_arrow (2292427714411156088) --> - <skip /> + <string name="downward_arrow_action" msgid="2327165938832076333">"Klizanje nadolje na listi"</string> + <string name="downward_arrow" msgid="2292427714411156088">"Strelica nadolje"</string> <string name="permission_expand" msgid="893185038020887411">"Proširivanje stavke <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_collapse" msgid="3320833884220844084">"Sužavanje stavke <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Dati aplikacijama na uređaju <strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> ista odobrenja kao na uređaju <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong>?"</string> diff --git a/packages/CompanionDeviceManager/res/values-ca/strings.xml b/packages/CompanionDeviceManager/res/values-ca/strings.xml index 21f3871d8209..7ec2b8b0dab6 100644 --- a/packages/CompanionDeviceManager/res/values-ca/strings.xml +++ b/packages/CompanionDeviceManager/res/values-ca/strings.xml @@ -51,10 +51,8 @@ <string name="consent_no" msgid="2640796915611404382">"No permetis"</string> <string name="consent_cancel" msgid="5655005528379285841">"Cancel·la"</string> <string name="consent_back" msgid="2560683030046918882">"Enrere"</string> - <!-- no translation found for downward_arrow_action (2327165938832076333) --> - <skip /> - <!-- no translation found for downward_arrow (2292427714411156088) --> - <skip /> + <string name="downward_arrow_action" msgid="2327165938832076333">"Desplaça la llista cap avall"</string> + <string name="downward_arrow" msgid="2292427714411156088">"Fletxa avall"</string> <string name="permission_expand" msgid="893185038020887411">"Desplega <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_collapse" msgid="3320833884220844084">"Replega <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Vols concedir a les aplicacions del dispositiu <strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> els mateixos permisos que tenen a <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong>?"</string> diff --git a/packages/CompanionDeviceManager/res/values-cs/strings.xml b/packages/CompanionDeviceManager/res/values-cs/strings.xml index cd8913012eeb..a1ea39b26eeb 100644 --- a/packages/CompanionDeviceManager/res/values-cs/strings.xml +++ b/packages/CompanionDeviceManager/res/values-cs/strings.xml @@ -51,10 +51,8 @@ <string name="consent_no" msgid="2640796915611404382">"Nepovolovat"</string> <string name="consent_cancel" msgid="5655005528379285841">"Zrušit"</string> <string name="consent_back" msgid="2560683030046918882">"Zpět"</string> - <!-- no translation found for downward_arrow_action (2327165938832076333) --> - <skip /> - <!-- no translation found for downward_arrow (2292427714411156088) --> - <skip /> + <string name="downward_arrow_action" msgid="2327165938832076333">"Přejít v seznamu dolů"</string> + <string name="downward_arrow" msgid="2292427714411156088">"Šipka dolů"</string> <string name="permission_expand" msgid="893185038020887411">"Rozbalit sekci <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_collapse" msgid="3320833884220844084">"Sbalit sekci <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Udělit aplikacím v zařízení <strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> stejné oprávnění, jako mají v zařízení <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong>?"</string> diff --git a/packages/CompanionDeviceManager/res/values-da/strings.xml b/packages/CompanionDeviceManager/res/values-da/strings.xml index 6dc34e77c5ce..97e6fe563dd6 100644 --- a/packages/CompanionDeviceManager/res/values-da/strings.xml +++ b/packages/CompanionDeviceManager/res/values-da/strings.xml @@ -51,10 +51,8 @@ <string name="consent_no" msgid="2640796915611404382">"Tillad ikke"</string> <string name="consent_cancel" msgid="5655005528379285841">"Annuller"</string> <string name="consent_back" msgid="2560683030046918882">"Tilbage"</string> - <!-- no translation found for downward_arrow_action (2327165938832076333) --> - <skip /> - <!-- no translation found for downward_arrow (2292427714411156088) --> - <skip /> + <string name="downward_arrow_action" msgid="2327165938832076333">"Rul ned på listen"</string> + <string name="downward_arrow" msgid="2292427714411156088">"Pil ned"</string> <string name="permission_expand" msgid="893185038020887411">"Udvid <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_collapse" msgid="3320833884220844084">"Skjul <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Vil du give apps på <strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> de samme tilladelser som på <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong>?"</string> diff --git a/packages/CompanionDeviceManager/res/values-de/strings.xml b/packages/CompanionDeviceManager/res/values-de/strings.xml index 4dc292916628..fc0231f24445 100644 --- a/packages/CompanionDeviceManager/res/values-de/strings.xml +++ b/packages/CompanionDeviceManager/res/values-de/strings.xml @@ -51,10 +51,8 @@ <string name="consent_no" msgid="2640796915611404382">"Nicht zulassen"</string> <string name="consent_cancel" msgid="5655005528379285841">"Abbrechen"</string> <string name="consent_back" msgid="2560683030046918882">"Zurück"</string> - <!-- no translation found for downward_arrow_action (2327165938832076333) --> - <skip /> - <!-- no translation found for downward_arrow (2292427714411156088) --> - <skip /> + <string name="downward_arrow_action" msgid="2327165938832076333">"In der Liste nach unten scrollen"</string> + <string name="downward_arrow" msgid="2292427714411156088">"Abwärtspfeil"</string> <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> maximieren"</string> <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> minimieren"</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Apps auf <strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> die gleichen Berechtigungen geben wie auf <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong>?"</string> diff --git a/packages/CompanionDeviceManager/res/values-en-rAU/strings.xml b/packages/CompanionDeviceManager/res/values-en-rAU/strings.xml index bbd81bf68b9d..68c74e0e82f8 100644 --- a/packages/CompanionDeviceManager/res/values-en-rAU/strings.xml +++ b/packages/CompanionDeviceManager/res/values-en-rAU/strings.xml @@ -51,10 +51,8 @@ <string name="consent_no" msgid="2640796915611404382">"Don\'t allow"</string> <string name="consent_cancel" msgid="5655005528379285841">"Cancel"</string> <string name="consent_back" msgid="2560683030046918882">"Back"</string> - <!-- no translation found for downward_arrow_action (2327165938832076333) --> - <skip /> - <!-- no translation found for downward_arrow (2292427714411156088) --> - <skip /> + <string name="downward_arrow_action" msgid="2327165938832076333">"Scroll down the list"</string> + <string name="downward_arrow" msgid="2292427714411156088">"Downward arrow"</string> <string name="permission_expand" msgid="893185038020887411">"Expand <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_collapse" msgid="3320833884220844084">"Collapse <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Give apps on <strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> the same permissions as on <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong>?"</string> diff --git a/packages/CompanionDeviceManager/res/values-en-rGB/strings.xml b/packages/CompanionDeviceManager/res/values-en-rGB/strings.xml index bbd81bf68b9d..68c74e0e82f8 100644 --- a/packages/CompanionDeviceManager/res/values-en-rGB/strings.xml +++ b/packages/CompanionDeviceManager/res/values-en-rGB/strings.xml @@ -51,10 +51,8 @@ <string name="consent_no" msgid="2640796915611404382">"Don\'t allow"</string> <string name="consent_cancel" msgid="5655005528379285841">"Cancel"</string> <string name="consent_back" msgid="2560683030046918882">"Back"</string> - <!-- no translation found for downward_arrow_action (2327165938832076333) --> - <skip /> - <!-- no translation found for downward_arrow (2292427714411156088) --> - <skip /> + <string name="downward_arrow_action" msgid="2327165938832076333">"Scroll down the list"</string> + <string name="downward_arrow" msgid="2292427714411156088">"Downward arrow"</string> <string name="permission_expand" msgid="893185038020887411">"Expand <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_collapse" msgid="3320833884220844084">"Collapse <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Give apps on <strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> the same permissions as on <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong>?"</string> diff --git a/packages/CompanionDeviceManager/res/values-en-rIN/strings.xml b/packages/CompanionDeviceManager/res/values-en-rIN/strings.xml index bbd81bf68b9d..68c74e0e82f8 100644 --- a/packages/CompanionDeviceManager/res/values-en-rIN/strings.xml +++ b/packages/CompanionDeviceManager/res/values-en-rIN/strings.xml @@ -51,10 +51,8 @@ <string name="consent_no" msgid="2640796915611404382">"Don\'t allow"</string> <string name="consent_cancel" msgid="5655005528379285841">"Cancel"</string> <string name="consent_back" msgid="2560683030046918882">"Back"</string> - <!-- no translation found for downward_arrow_action (2327165938832076333) --> - <skip /> - <!-- no translation found for downward_arrow (2292427714411156088) --> - <skip /> + <string name="downward_arrow_action" msgid="2327165938832076333">"Scroll down the list"</string> + <string name="downward_arrow" msgid="2292427714411156088">"Downward arrow"</string> <string name="permission_expand" msgid="893185038020887411">"Expand <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_collapse" msgid="3320833884220844084">"Collapse <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Give apps on <strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> the same permissions as on <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong>?"</string> diff --git a/packages/CompanionDeviceManager/res/values-es/strings.xml b/packages/CompanionDeviceManager/res/values-es/strings.xml index f8e2c742cccc..d74b4f822c15 100644 --- a/packages/CompanionDeviceManager/res/values-es/strings.xml +++ b/packages/CompanionDeviceManager/res/values-es/strings.xml @@ -51,10 +51,8 @@ <string name="consent_no" msgid="2640796915611404382">"No permitir"</string> <string name="consent_cancel" msgid="5655005528379285841">"Cancelar"</string> <string name="consent_back" msgid="2560683030046918882">"Atrás"</string> - <!-- no translation found for downward_arrow_action (2327165938832076333) --> - <skip /> - <!-- no translation found for downward_arrow (2292427714411156088) --> - <skip /> + <string name="downward_arrow_action" msgid="2327165938832076333">"Desplazarse hacia abajo por la lista"</string> + <string name="downward_arrow" msgid="2292427714411156088">"Flecha hacia abajo"</string> <string name="permission_expand" msgid="893185038020887411">"Desplegar <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_collapse" msgid="3320833884220844084">"Contraer <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"¿Dar a las aplicaciones de <strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> los mismos permisos que <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong>?"</string> diff --git a/packages/CompanionDeviceManager/res/values-fa/strings.xml b/packages/CompanionDeviceManager/res/values-fa/strings.xml index fb36c46d2d6a..cf4fe25ab0d0 100644 --- a/packages/CompanionDeviceManager/res/values-fa/strings.xml +++ b/packages/CompanionDeviceManager/res/values-fa/strings.xml @@ -51,10 +51,8 @@ <string name="consent_no" msgid="2640796915611404382">"اجازه ندادن"</string> <string name="consent_cancel" msgid="5655005528379285841">"لغو"</string> <string name="consent_back" msgid="2560683030046918882">"برگشتن"</string> - <!-- no translation found for downward_arrow_action (2327165938832076333) --> - <skip /> - <!-- no translation found for downward_arrow (2292427714411156088) --> - <skip /> + <string name="downward_arrow_action" msgid="2327165938832076333">"پیمایش بهپایین فهرست"</string> + <string name="downward_arrow" msgid="2292427714411156088">"جهتنمای پایین"</string> <string name="permission_expand" msgid="893185038020887411">"ازهم بازکردن <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_collapse" msgid="3320833884220844084">"جمع کردن <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"به برنامههای موجود در <strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> همان اجازههای <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong> داده شود؟"</string> diff --git a/packages/CompanionDeviceManager/res/values-fi/strings.xml b/packages/CompanionDeviceManager/res/values-fi/strings.xml index ff6cae1bbe3c..4be6a5acbf34 100644 --- a/packages/CompanionDeviceManager/res/values-fi/strings.xml +++ b/packages/CompanionDeviceManager/res/values-fi/strings.xml @@ -51,10 +51,8 @@ <string name="consent_no" msgid="2640796915611404382">"Älä salli"</string> <string name="consent_cancel" msgid="5655005528379285841">"Peruuta"</string> <string name="consent_back" msgid="2560683030046918882">"Takaisin"</string> - <!-- no translation found for downward_arrow_action (2327165938832076333) --> - <skip /> - <!-- no translation found for downward_arrow (2292427714411156088) --> - <skip /> + <string name="downward_arrow_action" msgid="2327165938832076333">"Vieritä listaa alaspäin"</string> + <string name="downward_arrow" msgid="2292427714411156088">"Alanuoli"</string> <string name="permission_expand" msgid="893185038020887411">"Laajenna <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_collapse" msgid="3320833884220844084">"Tiivistä <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Anna laitteen <strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> sovelluksille samat luvat kuin laitteella <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong>?"</string> diff --git a/packages/CompanionDeviceManager/res/values-fr-rCA/strings.xml b/packages/CompanionDeviceManager/res/values-fr-rCA/strings.xml index 4d7de76334de..70beafba782c 100644 --- a/packages/CompanionDeviceManager/res/values-fr-rCA/strings.xml +++ b/packages/CompanionDeviceManager/res/values-fr-rCA/strings.xml @@ -51,10 +51,8 @@ <string name="consent_no" msgid="2640796915611404382">"Ne pas autoriser"</string> <string name="consent_cancel" msgid="5655005528379285841">"Annuler"</string> <string name="consent_back" msgid="2560683030046918882">"Retour"</string> - <!-- no translation found for downward_arrow_action (2327165938832076333) --> - <skip /> - <!-- no translation found for downward_arrow (2292427714411156088) --> - <skip /> + <string name="downward_arrow_action" msgid="2327165938832076333">"Faire défiler la liste en bas"</string> + <string name="downward_arrow" msgid="2292427714411156088">"Flèche vers le bas"</string> <string name="permission_expand" msgid="893185038020887411">"Développer <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_collapse" msgid="3320833884220844084">"Réduire <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Accorder aux applis sur <strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> les autorisations déjà accordées sur <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong>?"</string> diff --git a/packages/CompanionDeviceManager/res/values-fr/strings.xml b/packages/CompanionDeviceManager/res/values-fr/strings.xml index d4c12fba1492..394efae9da0e 100644 --- a/packages/CompanionDeviceManager/res/values-fr/strings.xml +++ b/packages/CompanionDeviceManager/res/values-fr/strings.xml @@ -51,10 +51,8 @@ <string name="consent_no" msgid="2640796915611404382">"Ne pas autoriser"</string> <string name="consent_cancel" msgid="5655005528379285841">"Annuler"</string> <string name="consent_back" msgid="2560683030046918882">"Retour"</string> - <!-- no translation found for downward_arrow_action (2327165938832076333) --> - <skip /> - <!-- no translation found for downward_arrow (2292427714411156088) --> - <skip /> + <string name="downward_arrow_action" msgid="2327165938832076333">"Faire défiler la liste vers le bas"</string> + <string name="downward_arrow" msgid="2292427714411156088">"Flèche vers le bas"</string> <string name="permission_expand" msgid="893185038020887411">"Développer <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_collapse" msgid="3320833884220844084">"Réduire <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Accorder les mêmes autorisations aux applis sur <strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> que sur <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong> ?"</string> diff --git a/packages/CompanionDeviceManager/res/values-gl/strings.xml b/packages/CompanionDeviceManager/res/values-gl/strings.xml index 999ab9741084..ce23be8e8fbb 100644 --- a/packages/CompanionDeviceManager/res/values-gl/strings.xml +++ b/packages/CompanionDeviceManager/res/values-gl/strings.xml @@ -51,10 +51,8 @@ <string name="consent_no" msgid="2640796915611404382">"Non permitir"</string> <string name="consent_cancel" msgid="5655005528379285841">"Cancelar"</string> <string name="consent_back" msgid="2560683030046918882">"Atrás"</string> - <!-- no translation found for downward_arrow_action (2327165938832076333) --> - <skip /> - <!-- no translation found for downward_arrow (2292427714411156088) --> - <skip /> + <string name="downward_arrow_action" msgid="2327165938832076333">"Desprazarse abaixo na lista"</string> + <string name="downward_arrow" msgid="2292427714411156088">"Frecha cara abaixo"</string> <string name="permission_expand" msgid="893185038020887411">"Despregar <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_collapse" msgid="3320833884220844084">"Contraer <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Queres darlles ás aplicacións de <strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> os mesmos permisos que teñen as de <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong>?"</string> diff --git a/packages/CompanionDeviceManager/res/values-gu/strings.xml b/packages/CompanionDeviceManager/res/values-gu/strings.xml index 51d07c97fa5c..7956ba6814ff 100644 --- a/packages/CompanionDeviceManager/res/values-gu/strings.xml +++ b/packages/CompanionDeviceManager/res/values-gu/strings.xml @@ -51,10 +51,8 @@ <string name="consent_no" msgid="2640796915611404382">"મંજૂરી આપશો નહીં"</string> <string name="consent_cancel" msgid="5655005528379285841">"રદ કરો"</string> <string name="consent_back" msgid="2560683030046918882">"પાછળ"</string> - <!-- no translation found for downward_arrow_action (2327165938832076333) --> - <skip /> - <!-- no translation found for downward_arrow (2292427714411156088) --> - <skip /> + <string name="downward_arrow_action" msgid="2327165938832076333">"સૂચિ પર નીચે સ્ક્રોલ કરો"</string> + <string name="downward_arrow" msgid="2292427714411156088">"નીચેની તરફ ઍરો"</string> <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>ને મોટું કરો"</string> <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>ને નાનું કરો"</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"<strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> પરની ઍપને <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong> પર છે તે જ પરવાનગીઓ આપીએ?"</string> diff --git a/packages/CompanionDeviceManager/res/values-hr/strings.xml b/packages/CompanionDeviceManager/res/values-hr/strings.xml index 04872e53cb36..326564e90f5e 100644 --- a/packages/CompanionDeviceManager/res/values-hr/strings.xml +++ b/packages/CompanionDeviceManager/res/values-hr/strings.xml @@ -51,10 +51,8 @@ <string name="consent_no" msgid="2640796915611404382">"Nemoj dopustiti"</string> <string name="consent_cancel" msgid="5655005528379285841">"Odustani"</string> <string name="consent_back" msgid="2560683030046918882">"Natrag"</string> - <!-- no translation found for downward_arrow_action (2327165938832076333) --> - <skip /> - <!-- no translation found for downward_arrow (2292427714411156088) --> - <skip /> + <string name="downward_arrow_action" msgid="2327165938832076333">"Pomicanje po popisu"</string> + <string name="downward_arrow" msgid="2292427714411156088">"Strelica prema dolje"</string> <string name="permission_expand" msgid="893185038020887411">"Proširi <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_collapse" msgid="3320833884220844084">"Sažmi <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Dati jednaka dopuštenja aplikacijama na uređaju <strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> kao i na uređaju <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong>?"</string> diff --git a/packages/CompanionDeviceManager/res/values-hy/strings.xml b/packages/CompanionDeviceManager/res/values-hy/strings.xml index e7c71391fb91..cbf1d8d7f921 100644 --- a/packages/CompanionDeviceManager/res/values-hy/strings.xml +++ b/packages/CompanionDeviceManager/res/values-hy/strings.xml @@ -51,10 +51,8 @@ <string name="consent_no" msgid="2640796915611404382">"Չթույլատրել"</string> <string name="consent_cancel" msgid="5655005528379285841">"Չեղարկել"</string> <string name="consent_back" msgid="2560683030046918882">"Հետ"</string> - <!-- no translation found for downward_arrow_action (2327165938832076333) --> - <skip /> - <!-- no translation found for downward_arrow (2292427714411156088) --> - <skip /> + <string name="downward_arrow_action" msgid="2327165938832076333">"Թերթեք ներքև"</string> + <string name="downward_arrow" msgid="2292427714411156088">"Ներքև սլաք"</string> <string name="permission_expand" msgid="893185038020887411">"Ծավալել «<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>» բաժինը"</string> <string name="permission_collapse" msgid="3320833884220844084">"Ծալել «<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>» բաժինը"</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"<strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong>-ում հավելվածներին տա՞լ նույն թույլտվությունները, ինչ <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong>-ում"</string> diff --git a/packages/CompanionDeviceManager/res/values-in/strings.xml b/packages/CompanionDeviceManager/res/values-in/strings.xml index f58220563e4e..fe3406ecd6be 100644 --- a/packages/CompanionDeviceManager/res/values-in/strings.xml +++ b/packages/CompanionDeviceManager/res/values-in/strings.xml @@ -51,10 +51,8 @@ <string name="consent_no" msgid="2640796915611404382">"Jangan izinkan"</string> <string name="consent_cancel" msgid="5655005528379285841">"Batal"</string> <string name="consent_back" msgid="2560683030046918882">"Kembali"</string> - <!-- no translation found for downward_arrow_action (2327165938832076333) --> - <skip /> - <!-- no translation found for downward_arrow (2292427714411156088) --> - <skip /> + <string name="downward_arrow_action" msgid="2327165938832076333">"Scroll daftar ke bawah"</string> + <string name="downward_arrow" msgid="2292427714411156088">"Panah bawah"</string> <string name="permission_expand" msgid="893185038020887411">"Luaskan <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_collapse" msgid="3320833884220844084">"Ciutkan <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Berikan aplikasi di <strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> izin yang sama seperti di <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong>?"</string> diff --git a/packages/CompanionDeviceManager/res/values-is/strings.xml b/packages/CompanionDeviceManager/res/values-is/strings.xml index b66133cddeb6..9e366d6ec3a9 100644 --- a/packages/CompanionDeviceManager/res/values-is/strings.xml +++ b/packages/CompanionDeviceManager/res/values-is/strings.xml @@ -51,10 +51,8 @@ <string name="consent_no" msgid="2640796915611404382">"Ekki leyfa"</string> <string name="consent_cancel" msgid="5655005528379285841">"Hætta við"</string> <string name="consent_back" msgid="2560683030046918882">"Til baka"</string> - <!-- no translation found for downward_arrow_action (2327165938832076333) --> - <skip /> - <!-- no translation found for downward_arrow (2292427714411156088) --> - <skip /> + <string name="downward_arrow_action" msgid="2327165938832076333">"Fletta niður listann"</string> + <string name="downward_arrow" msgid="2292427714411156088">"Ör niður"</string> <string name="permission_expand" msgid="893185038020887411">"Stækka <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_collapse" msgid="3320833884220844084">"Minnka <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Veita forritum í <strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> sömu heimildir og í <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong>?"</string> diff --git a/packages/CompanionDeviceManager/res/values-iw/strings.xml b/packages/CompanionDeviceManager/res/values-iw/strings.xml index 626ae7a3b50e..1e299b72ffb6 100644 --- a/packages/CompanionDeviceManager/res/values-iw/strings.xml +++ b/packages/CompanionDeviceManager/res/values-iw/strings.xml @@ -51,10 +51,8 @@ <string name="consent_no" msgid="2640796915611404382">"אין אישור"</string> <string name="consent_cancel" msgid="5655005528379285841">"ביטול"</string> <string name="consent_back" msgid="2560683030046918882">"חזרה"</string> - <!-- no translation found for downward_arrow_action (2327165938832076333) --> - <skip /> - <!-- no translation found for downward_arrow (2292427714411156088) --> - <skip /> + <string name="downward_arrow_action" msgid="2327165938832076333">"גלילה למטה ברשימה"</string> + <string name="downward_arrow" msgid="2292427714411156088">"חץ למטה"</string> <string name="permission_expand" msgid="893185038020887411">"הרחבה של <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_collapse" msgid="3320833884220844084">"כיווץ של <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"האם לתת לאפליקציות ב-<strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> את אותן הרשאות כמו ב-<strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong>?"</string> diff --git a/packages/CompanionDeviceManager/res/values-ka/strings.xml b/packages/CompanionDeviceManager/res/values-ka/strings.xml index 97f3087663cf..d0214aeb13ca 100644 --- a/packages/CompanionDeviceManager/res/values-ka/strings.xml +++ b/packages/CompanionDeviceManager/res/values-ka/strings.xml @@ -51,10 +51,8 @@ <string name="consent_no" msgid="2640796915611404382">"არ დაიშვას"</string> <string name="consent_cancel" msgid="5655005528379285841">"გაუქმება"</string> <string name="consent_back" msgid="2560683030046918882">"უკან"</string> - <!-- no translation found for downward_arrow_action (2327165938832076333) --> - <skip /> - <!-- no translation found for downward_arrow (2292427714411156088) --> - <skip /> + <string name="downward_arrow_action" msgid="2327165938832076333">"გადაადგილება სიის ქვემოთ"</string> + <string name="downward_arrow" msgid="2292427714411156088">"ქვემოთ მიმართული ისარი"</string> <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>-ის გაფართოება"</string> <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>-ის ჩაკეცვა"</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"გსურთ აპებს <strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong>-ზე იგივე ნებართვები მიანიჭოთ, როგორიც აქვს <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong>-ზე?"</string> diff --git a/packages/CompanionDeviceManager/res/values-kk/strings.xml b/packages/CompanionDeviceManager/res/values-kk/strings.xml index 4d77c6cc588c..e7944154b516 100644 --- a/packages/CompanionDeviceManager/res/values-kk/strings.xml +++ b/packages/CompanionDeviceManager/res/values-kk/strings.xml @@ -51,10 +51,8 @@ <string name="consent_no" msgid="2640796915611404382">"Рұқсат бермеу"</string> <string name="consent_cancel" msgid="5655005528379285841">"Бас тарту"</string> <string name="consent_back" msgid="2560683030046918882">"Артқа"</string> - <!-- no translation found for downward_arrow_action (2327165938832076333) --> - <skip /> - <!-- no translation found for downward_arrow (2292427714411156088) --> - <skip /> + <string name="downward_arrow_action" msgid="2327165938832076333">"Тізім соңына дейін айналдыру"</string> + <string name="downward_arrow" msgid="2292427714411156088">"Төмен бағытталған перне"</string> <string name="permission_expand" msgid="893185038020887411">"\"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>\" панелін жаю"</string> <string name="permission_collapse" msgid="3320833884220844084">"\"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>\" панелін жию"</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"<strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> құрылғысындағы қолданбаларға <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong> құрылғысындағыдай рұқсаттар берілсін бе?"</string> diff --git a/packages/CompanionDeviceManager/res/values-kn/strings.xml b/packages/CompanionDeviceManager/res/values-kn/strings.xml index 751ac1f1a5ad..a957f48f1a13 100644 --- a/packages/CompanionDeviceManager/res/values-kn/strings.xml +++ b/packages/CompanionDeviceManager/res/values-kn/strings.xml @@ -51,10 +51,8 @@ <string name="consent_no" msgid="2640796915611404382">"ಅನುಮತಿಸಬೇಡಿ"</string> <string name="consent_cancel" msgid="5655005528379285841">"ರದ್ದುಮಾಡಿ"</string> <string name="consent_back" msgid="2560683030046918882">"ಹಿಂದೆ"</string> - <!-- no translation found for downward_arrow_action (2327165938832076333) --> - <skip /> - <!-- no translation found for downward_arrow (2292427714411156088) --> - <skip /> + <string name="downward_arrow_action" msgid="2327165938832076333">"ಪಟ್ಟಿಯನ್ನು ಕೆಳಗೆ ಸ್ಕ್ರಾಲ್ ಮಾಡಿ"</string> + <string name="downward_arrow" msgid="2292427714411156088">"ಕೆಳಮುಖ ಬಾಣ"</string> <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> ಅನ್ನು ವಿಸ್ತರಿಸಿ"</string> <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> ಅನ್ನು ಕುಗ್ಗಿಸಿ"</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"</strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong> ನಲ್ಲಿನ ಅನುಮತಿಗಳನ್ನೇ </strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> ನಲ್ಲಿನ ಆ್ಯಪ್ಗಳಿಗೆ ನೀಡಬೇಕೆ?"</string> diff --git a/packages/CompanionDeviceManager/res/values-ko/strings.xml b/packages/CompanionDeviceManager/res/values-ko/strings.xml index a7a5dc1bd204..d7d489af9755 100644 --- a/packages/CompanionDeviceManager/res/values-ko/strings.xml +++ b/packages/CompanionDeviceManager/res/values-ko/strings.xml @@ -51,10 +51,8 @@ <string name="consent_no" msgid="2640796915611404382">"허용 안함"</string> <string name="consent_cancel" msgid="5655005528379285841">"취소"</string> <string name="consent_back" msgid="2560683030046918882">"뒤로"</string> - <!-- no translation found for downward_arrow_action (2327165938832076333) --> - <skip /> - <!-- no translation found for downward_arrow (2292427714411156088) --> - <skip /> + <string name="downward_arrow_action" msgid="2327165938832076333">"목록을 아래로 스크롤"</string> + <string name="downward_arrow" msgid="2292427714411156088">"아래쪽 화살표"</string> <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> 펼치기"</string> <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> 접기"</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"<strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong>에 설치된 앱에 <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong>에 설치된 앱과 동일한 권한을 부여하시겠습니까?"</string> diff --git a/packages/CompanionDeviceManager/res/values-ky/strings.xml b/packages/CompanionDeviceManager/res/values-ky/strings.xml index 991fc9f13248..3f8c58af129a 100644 --- a/packages/CompanionDeviceManager/res/values-ky/strings.xml +++ b/packages/CompanionDeviceManager/res/values-ky/strings.xml @@ -51,10 +51,8 @@ <string name="consent_no" msgid="2640796915611404382">"Уруксат берилбесин"</string> <string name="consent_cancel" msgid="5655005528379285841">"Жок"</string> <string name="consent_back" msgid="2560683030046918882">"Артка"</string> - <!-- no translation found for downward_arrow_action (2327165938832076333) --> - <skip /> - <!-- no translation found for downward_arrow (2292427714411156088) --> - <skip /> + <string name="downward_arrow_action" msgid="2327165938832076333">"Тизменин ылдый жагына сыдыруу"</string> + <string name="downward_arrow" msgid="2292427714411156088">"Ылдый караган жебе"</string> <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> жайып көрсөтүү"</string> <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> жыйыштыруу"</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"<strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> түзмөгүнө да <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong> түзмөгүнө берилген уруксаттар берилсинби?"</string> diff --git a/packages/CompanionDeviceManager/res/values-lo/strings.xml b/packages/CompanionDeviceManager/res/values-lo/strings.xml index 91399b60f7b7..457efdb6f864 100644 --- a/packages/CompanionDeviceManager/res/values-lo/strings.xml +++ b/packages/CompanionDeviceManager/res/values-lo/strings.xml @@ -51,10 +51,8 @@ <string name="consent_no" msgid="2640796915611404382">"ບໍ່ອະນຸຍາດ"</string> <string name="consent_cancel" msgid="5655005528379285841">"ຍົກເລີກ"</string> <string name="consent_back" msgid="2560683030046918882">"ກັບຄືນ"</string> - <!-- no translation found for downward_arrow_action (2327165938832076333) --> - <skip /> - <!-- no translation found for downward_arrow (2292427714411156088) --> - <skip /> + <string name="downward_arrow_action" msgid="2327165938832076333">"ເລື່ອນລາຍຊື່ລົງ"</string> + <string name="downward_arrow" msgid="2292427714411156088">"ລູກສອນຊີ້ລົງ"</string> <string name="permission_expand" msgid="893185038020887411">"ຂະຫຍາຍ <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_collapse" msgid="3320833884220844084">"ຫຍໍ້ <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> ລົງ"</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"ໃຫ້ການອະນຸຍາດແອັບຢູ່ <strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> ເປັນການອະນຸຍາດດຽວກັນກັບຢູ່ <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong> ບໍ?"</string> diff --git a/packages/CompanionDeviceManager/res/values-lv/strings.xml b/packages/CompanionDeviceManager/res/values-lv/strings.xml index 35c748125180..01c9ed5e4657 100644 --- a/packages/CompanionDeviceManager/res/values-lv/strings.xml +++ b/packages/CompanionDeviceManager/res/values-lv/strings.xml @@ -51,10 +51,8 @@ <string name="consent_no" msgid="2640796915611404382">"Neatļaut"</string> <string name="consent_cancel" msgid="5655005528379285841">"Atcelt"</string> <string name="consent_back" msgid="2560683030046918882">"Atpakaļ"</string> - <!-- no translation found for downward_arrow_action (2327165938832076333) --> - <skip /> - <!-- no translation found for downward_arrow (2292427714411156088) --> - <skip /> + <string name="downward_arrow_action" msgid="2327165938832076333">"Ritināt sarakstu lejup"</string> + <string name="downward_arrow" msgid="2292427714411156088">"Lejupvērsta bultiņa"</string> <string name="permission_expand" msgid="893185038020887411">"Izvērst: <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_collapse" msgid="3320833884220844084">"Sakļaut: <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Vai lietotnēm ierīcē <strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> piešķirt tādas pašas atļaujas kā ierīcē <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong>?"</string> diff --git a/packages/CompanionDeviceManager/res/values-mk/strings.xml b/packages/CompanionDeviceManager/res/values-mk/strings.xml index 1eaf2d23690b..d9c8695d7c13 100644 --- a/packages/CompanionDeviceManager/res/values-mk/strings.xml +++ b/packages/CompanionDeviceManager/res/values-mk/strings.xml @@ -51,10 +51,8 @@ <string name="consent_no" msgid="2640796915611404382">"Не дозволувај"</string> <string name="consent_cancel" msgid="5655005528379285841">"Откажи"</string> <string name="consent_back" msgid="2560683030046918882">"Назад"</string> - <!-- no translation found for downward_arrow_action (2327165938832076333) --> - <skip /> - <!-- no translation found for downward_arrow (2292427714411156088) --> - <skip /> + <string name="downward_arrow_action" msgid="2327165938832076333">"Лизгај надолу по списокот"</string> + <string name="downward_arrow" msgid="2292427714411156088">"Стрелка надолу"</string> <string name="permission_expand" msgid="893185038020887411">"Прошири <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_collapse" msgid="3320833884220844084">"Собери <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Дасе дадат исти дозволи на апликациите на <strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> како на <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong>?"</string> diff --git a/packages/CompanionDeviceManager/res/values-ml/strings.xml b/packages/CompanionDeviceManager/res/values-ml/strings.xml index 52fcb1f9a7b6..828b6cfac332 100644 --- a/packages/CompanionDeviceManager/res/values-ml/strings.xml +++ b/packages/CompanionDeviceManager/res/values-ml/strings.xml @@ -51,10 +51,8 @@ <string name="consent_no" msgid="2640796915611404382">"അനുവദിക്കരുത്"</string> <string name="consent_cancel" msgid="5655005528379285841">"റദ്ദാക്കുക"</string> <string name="consent_back" msgid="2560683030046918882">"മടങ്ങുക"</string> - <!-- no translation found for downward_arrow_action (2327165938832076333) --> - <skip /> - <!-- no translation found for downward_arrow (2292427714411156088) --> - <skip /> + <string name="downward_arrow_action" msgid="2327165938832076333">"ലിസ്റ്റ് താഴേക്ക് സ്ക്രോൾ ചെയ്യൂ"</string> + <string name="downward_arrow" msgid="2292427714411156088">"താഴേക്കുള്ള അമ്പടയാളം"</string> <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> വികസിപ്പിക്കുക"</string> <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> ചുരുക്കുക"</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"<strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong> എന്നതിലെ അതേ അനുമതികൾ <strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> എന്നതിലെ ആപ്പുകൾക്ക് നൽകണോ?"</string> diff --git a/packages/CompanionDeviceManager/res/values-mn/strings.xml b/packages/CompanionDeviceManager/res/values-mn/strings.xml index 5faf241c81a1..be6eb8e6fe58 100644 --- a/packages/CompanionDeviceManager/res/values-mn/strings.xml +++ b/packages/CompanionDeviceManager/res/values-mn/strings.xml @@ -51,10 +51,8 @@ <string name="consent_no" msgid="2640796915611404382">"Бүү зөвшөөр"</string> <string name="consent_cancel" msgid="5655005528379285841">"Цуцлах"</string> <string name="consent_back" msgid="2560683030046918882">"Буцах"</string> - <!-- no translation found for downward_arrow_action (2327165938832076333) --> - <skip /> - <!-- no translation found for downward_arrow (2292427714411156088) --> - <skip /> + <string name="downward_arrow_action" msgid="2327165938832076333">"Жагсаалтыг доош гүйлгэх"</string> + <string name="downward_arrow" msgid="2292427714411156088">"Доош заасан сум"</string> <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>-г дэлгэх"</string> <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>-г хураах"</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"<strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> дээрх аппуудад <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong> дээрхтэй адил зөвшөөрөл өгөх үү?"</string> diff --git a/packages/CompanionDeviceManager/res/values-mr/strings.xml b/packages/CompanionDeviceManager/res/values-mr/strings.xml index 94e49fecd7e0..99f59a641b89 100644 --- a/packages/CompanionDeviceManager/res/values-mr/strings.xml +++ b/packages/CompanionDeviceManager/res/values-mr/strings.xml @@ -51,10 +51,8 @@ <string name="consent_no" msgid="2640796915611404382">"अनुमती देऊ नका"</string> <string name="consent_cancel" msgid="5655005528379285841">"रद्द करा"</string> <string name="consent_back" msgid="2560683030046918882">"मागे जा"</string> - <!-- no translation found for downward_arrow_action (2327165938832076333) --> - <skip /> - <!-- no translation found for downward_arrow (2292427714411156088) --> - <skip /> + <string name="downward_arrow_action" msgid="2327165938832076333">"सूचीवर खाली स्क्रोल करा"</string> + <string name="downward_arrow" msgid="2292427714411156088">"खालच्या दिशेचा अॅरो"</string> <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> चा विस्तार करा"</string> <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> कोलॅप्स करा"</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"<strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> वरील अॅप्सना <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong> प्रमाणेच परवानग्या द्यायच्या आहेत का?"</string> diff --git a/packages/CompanionDeviceManager/res/values-ms/strings.xml b/packages/CompanionDeviceManager/res/values-ms/strings.xml index 8b1170ba03d2..9c15c0607b7b 100644 --- a/packages/CompanionDeviceManager/res/values-ms/strings.xml +++ b/packages/CompanionDeviceManager/res/values-ms/strings.xml @@ -51,10 +51,8 @@ <string name="consent_no" msgid="2640796915611404382">"Jangan benarkan"</string> <string name="consent_cancel" msgid="5655005528379285841">"Batal"</string> <string name="consent_back" msgid="2560683030046918882">"Kembali"</string> - <!-- no translation found for downward_arrow_action (2327165938832076333) --> - <skip /> - <!-- no translation found for downward_arrow (2292427714411156088) --> - <skip /> + <string name="downward_arrow_action" msgid="2327165938832076333">"Tatal ke bawah senarai"</string> + <string name="downward_arrow" msgid="2292427714411156088">"Anak panah ke bawah"</string> <string name="permission_expand" msgid="893185038020887411">"Kembangkan <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_collapse" msgid="3320833884220844084">"Kuncupkan <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Beri apl pada <strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> kebenaran yang sama seperti pada <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong>?"</string> diff --git a/packages/CompanionDeviceManager/res/values-my/strings.xml b/packages/CompanionDeviceManager/res/values-my/strings.xml index faca3e35d549..6068c43b5274 100644 --- a/packages/CompanionDeviceManager/res/values-my/strings.xml +++ b/packages/CompanionDeviceManager/res/values-my/strings.xml @@ -51,10 +51,8 @@ <string name="consent_no" msgid="2640796915611404382">"ခွင့်မပြုပါ"</string> <string name="consent_cancel" msgid="5655005528379285841">"မလုပ်တော့"</string> <string name="consent_back" msgid="2560683030046918882">"နောက်သို့"</string> - <!-- no translation found for downward_arrow_action (2327165938832076333) --> - <skip /> - <!-- no translation found for downward_arrow (2292427714411156088) --> - <skip /> + <string name="downward_arrow_action" msgid="2327165938832076333">"စာရင်းအောက်သို့ လှိမ့်ရန်"</string> + <string name="downward_arrow" msgid="2292427714411156088">"အောက်ညွှန်မြား"</string> <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> ကို ပိုပြရန်"</string> <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> ကို လျှော့ပြရန်"</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"အက်ပ်များကို <strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> တွင်ပေးထားသည့် ခွင့်ပြုချက်များအတိုင်း <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong> တွင် ပေးမလား။"</string> diff --git a/packages/CompanionDeviceManager/res/values-nb/strings.xml b/packages/CompanionDeviceManager/res/values-nb/strings.xml index 4188951a15ed..eb5e2e996bdc 100644 --- a/packages/CompanionDeviceManager/res/values-nb/strings.xml +++ b/packages/CompanionDeviceManager/res/values-nb/strings.xml @@ -51,10 +51,8 @@ <string name="consent_no" msgid="2640796915611404382">"Ikke tillat"</string> <string name="consent_cancel" msgid="5655005528379285841">"Avbryt"</string> <string name="consent_back" msgid="2560683030046918882">"Tilbake"</string> - <!-- no translation found for downward_arrow_action (2327165938832076333) --> - <skip /> - <!-- no translation found for downward_arrow (2292427714411156088) --> - <skip /> + <string name="downward_arrow_action" msgid="2327165938832076333">"Rull nedover i listen"</string> + <string name="downward_arrow" msgid="2292427714411156088">"Nedoverpil"</string> <string name="permission_expand" msgid="893185038020887411">"Vis <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_collapse" msgid="3320833884220844084">"Skjul <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Vil du gi apper på <strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> de samme tillatelsene som på <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong>?"</string> diff --git a/packages/CompanionDeviceManager/res/values-ne/strings.xml b/packages/CompanionDeviceManager/res/values-ne/strings.xml index 2cd4fef2c53a..f3b56de35f12 100644 --- a/packages/CompanionDeviceManager/res/values-ne/strings.xml +++ b/packages/CompanionDeviceManager/res/values-ne/strings.xml @@ -51,10 +51,8 @@ <string name="consent_no" msgid="2640796915611404382">"अनुमति नदिनुहोस्"</string> <string name="consent_cancel" msgid="5655005528379285841">"रद्द गर्नुहोस्"</string> <string name="consent_back" msgid="2560683030046918882">"पछाडि"</string> - <!-- no translation found for downward_arrow_action (2327165938832076333) --> - <skip /> - <!-- no translation found for downward_arrow (2292427714411156088) --> - <skip /> + <string name="downward_arrow_action" msgid="2327165938832076333">"सूचीको तलतिर स्क्रोल गर्नुहोस्"</string> + <string name="downward_arrow" msgid="2292427714411156088">"डाउनवार्ड एरो"</string> <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> एक्स्पान्ड गर्नुहोस्"</string> <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> कोल्याप्स गर्नुहोस्"</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"<strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> मा भएका एपहरूलाई पनि <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong> मा दिइएकै अनुमति दिने हो?"</string> diff --git a/packages/CompanionDeviceManager/res/values-or/strings.xml b/packages/CompanionDeviceManager/res/values-or/strings.xml index 8298a5b9d411..bd602e496d74 100644 --- a/packages/CompanionDeviceManager/res/values-or/strings.xml +++ b/packages/CompanionDeviceManager/res/values-or/strings.xml @@ -51,10 +51,8 @@ <string name="consent_no" msgid="2640796915611404382">"ଅନୁମତି ଦିଅନ୍ତୁ ନାହିଁ"</string> <string name="consent_cancel" msgid="5655005528379285841">"ବାତିଲ କରନ୍ତୁ"</string> <string name="consent_back" msgid="2560683030046918882">"ପଛକୁ ଫେରନ୍ତୁ"</string> - <!-- no translation found for downward_arrow_action (2327165938832076333) --> - <skip /> - <!-- no translation found for downward_arrow (2292427714411156088) --> - <skip /> + <string name="downward_arrow_action" msgid="2327165938832076333">"ତାଲିକା ତଳକୁ ସ୍କ୍ରୋଲ କରନ୍ତୁ"</string> + <string name="downward_arrow" msgid="2292427714411156088">"ଡାଉନୱାର୍ଡ ତୀର"</string> <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>କୁ ବିସ୍ତାର କରନ୍ତୁ"</string> <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>କୁ ସଙ୍କୁଚିତ କରନ୍ତୁ"</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"<strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong>ପରି <strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong>ରେ ଥିବା ଆପ୍ସକୁ ସମାନ ଅନୁମତିଗୁଡ଼ିକ ଦେବେ?"</string> diff --git a/packages/CompanionDeviceManager/res/values-pa/strings.xml b/packages/CompanionDeviceManager/res/values-pa/strings.xml index fe13c00dfb98..ae0b59dbc21d 100644 --- a/packages/CompanionDeviceManager/res/values-pa/strings.xml +++ b/packages/CompanionDeviceManager/res/values-pa/strings.xml @@ -51,10 +51,8 @@ <string name="consent_no" msgid="2640796915611404382">"ਆਗਿਆ ਨਾ ਦਿਓ"</string> <string name="consent_cancel" msgid="5655005528379285841">"ਰੱਦ ਕਰੋ"</string> <string name="consent_back" msgid="2560683030046918882">"ਪਿੱਛੇ"</string> - <!-- no translation found for downward_arrow_action (2327165938832076333) --> - <skip /> - <!-- no translation found for downward_arrow (2292427714411156088) --> - <skip /> + <string name="downward_arrow_action" msgid="2327165938832076333">"ਸੂਚੀ ਨੂੰ ਹੇਠਾਂ ਵੱਲ ਸਕ੍ਰੋਲ ਕਰੋ"</string> + <string name="downward_arrow" msgid="2292427714411156088">"ਹੇਠਾਂ ਤੀਰ"</string> <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> ਦਾ ਵਿਸਤਾਰ ਕਰੋ"</string> <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> ਨੂੰ ਸਮੇਟੋ"</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"ਕੀ <strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> \'ਤੇ ਮੌਜੂਦ ਐਪਾਂ ਨੂੰ <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong> \'ਤੇ ਮੌਜੂਦ ਐਪਾਂ ਵਾਂਗ ਇਜਾਜ਼ਤਾਂ ਦੇਣੀਆਂ ਹਨ?"</string> diff --git a/packages/CompanionDeviceManager/res/values-pl/strings.xml b/packages/CompanionDeviceManager/res/values-pl/strings.xml index 1599f3f333fe..bce0a6ebdaa5 100644 --- a/packages/CompanionDeviceManager/res/values-pl/strings.xml +++ b/packages/CompanionDeviceManager/res/values-pl/strings.xml @@ -51,10 +51,8 @@ <string name="consent_no" msgid="2640796915611404382">"Nie zezwalaj"</string> <string name="consent_cancel" msgid="5655005528379285841">"Anuluj"</string> <string name="consent_back" msgid="2560683030046918882">"Wstecz"</string> - <!-- no translation found for downward_arrow_action (2327165938832076333) --> - <skip /> - <!-- no translation found for downward_arrow (2292427714411156088) --> - <skip /> + <string name="downward_arrow_action" msgid="2327165938832076333">"Przewiń listę w dół"</string> + <string name="downward_arrow" msgid="2292427714411156088">"Strzałka w dół"</string> <string name="permission_expand" msgid="893185038020887411">"Rozwiń sekcję <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_collapse" msgid="3320833884220844084">"Zwiń sekcję <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Czy aplikacjom na urządzeniu <strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> przyznać te same uprawnienia co na urządzeniu <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong>?"</string> diff --git a/packages/CompanionDeviceManager/res/values-pt-rBR/strings.xml b/packages/CompanionDeviceManager/res/values-pt-rBR/strings.xml index 85c23ea7f253..35b653a4e6de 100644 --- a/packages/CompanionDeviceManager/res/values-pt-rBR/strings.xml +++ b/packages/CompanionDeviceManager/res/values-pt-rBR/strings.xml @@ -51,10 +51,8 @@ <string name="consent_no" msgid="2640796915611404382">"Não permitir"</string> <string name="consent_cancel" msgid="5655005528379285841">"Cancelar"</string> <string name="consent_back" msgid="2560683030046918882">"Voltar"</string> - <!-- no translation found for downward_arrow_action (2327165938832076333) --> - <skip /> - <!-- no translation found for downward_arrow (2292427714411156088) --> - <skip /> + <string name="downward_arrow_action" msgid="2327165938832076333">"Role para baixo na lista"</string> + <string name="downward_arrow" msgid="2292427714411156088">"Seta para baixo"</string> <string name="permission_expand" msgid="893185038020887411">"Abrir <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_collapse" msgid="3320833884220844084">"Fechar <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Dar aos apps no dispositivo <strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> as mesmas permissões do dispositivo <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong>?"</string> diff --git a/packages/CompanionDeviceManager/res/values-pt/strings.xml b/packages/CompanionDeviceManager/res/values-pt/strings.xml index 85c23ea7f253..35b653a4e6de 100644 --- a/packages/CompanionDeviceManager/res/values-pt/strings.xml +++ b/packages/CompanionDeviceManager/res/values-pt/strings.xml @@ -51,10 +51,8 @@ <string name="consent_no" msgid="2640796915611404382">"Não permitir"</string> <string name="consent_cancel" msgid="5655005528379285841">"Cancelar"</string> <string name="consent_back" msgid="2560683030046918882">"Voltar"</string> - <!-- no translation found for downward_arrow_action (2327165938832076333) --> - <skip /> - <!-- no translation found for downward_arrow (2292427714411156088) --> - <skip /> + <string name="downward_arrow_action" msgid="2327165938832076333">"Role para baixo na lista"</string> + <string name="downward_arrow" msgid="2292427714411156088">"Seta para baixo"</string> <string name="permission_expand" msgid="893185038020887411">"Abrir <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_collapse" msgid="3320833884220844084">"Fechar <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Dar aos apps no dispositivo <strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> as mesmas permissões do dispositivo <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong>?"</string> diff --git a/packages/CompanionDeviceManager/res/values-ro/strings.xml b/packages/CompanionDeviceManager/res/values-ro/strings.xml index 6820d5b44633..0fdeb29f0400 100644 --- a/packages/CompanionDeviceManager/res/values-ro/strings.xml +++ b/packages/CompanionDeviceManager/res/values-ro/strings.xml @@ -51,10 +51,8 @@ <string name="consent_no" msgid="2640796915611404382">"Nu permite"</string> <string name="consent_cancel" msgid="5655005528379285841">"Anulează"</string> <string name="consent_back" msgid="2560683030046918882">"Înapoi"</string> - <!-- no translation found for downward_arrow_action (2327165938832076333) --> - <skip /> - <!-- no translation found for downward_arrow (2292427714411156088) --> - <skip /> + <string name="downward_arrow_action" msgid="2327165938832076333">"Derulează în jos lista"</string> + <string name="downward_arrow" msgid="2292427714411156088">"Săgeată în jos"</string> <string name="permission_expand" msgid="893185038020887411">"Extinde <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_collapse" msgid="3320833884220844084">"Restrânge <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Acorzi aplicațiilor de pe <strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> aceleași permisiuni ca pe <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong>?"</string> diff --git a/packages/CompanionDeviceManager/res/values-ru/strings.xml b/packages/CompanionDeviceManager/res/values-ru/strings.xml index 3213a2faa7d8..872f45eae425 100644 --- a/packages/CompanionDeviceManager/res/values-ru/strings.xml +++ b/packages/CompanionDeviceManager/res/values-ru/strings.xml @@ -51,10 +51,8 @@ <string name="consent_no" msgid="2640796915611404382">"Запретить"</string> <string name="consent_cancel" msgid="5655005528379285841">"Отмена"</string> <string name="consent_back" msgid="2560683030046918882">"Назад"</string> - <!-- no translation found for downward_arrow_action (2327165938832076333) --> - <skip /> - <!-- no translation found for downward_arrow (2292427714411156088) --> - <skip /> + <string name="downward_arrow_action" msgid="2327165938832076333">"Прокрутить список вниз"</string> + <string name="downward_arrow" msgid="2292427714411156088">"Стрелка вниз"</string> <string name="permission_expand" msgid="893185038020887411">"Разворачивать список \"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>\"."</string> <string name="permission_collapse" msgid="3320833884220844084">"Сворачивать список \"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>\"."</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Предоставить приложениям на устройстве <strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> те же разрешения, что на устройстве <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong>?"</string> diff --git a/packages/CompanionDeviceManager/res/values-si/strings.xml b/packages/CompanionDeviceManager/res/values-si/strings.xml index 7fd66110c10b..ec362f2c33f2 100644 --- a/packages/CompanionDeviceManager/res/values-si/strings.xml +++ b/packages/CompanionDeviceManager/res/values-si/strings.xml @@ -51,10 +51,8 @@ <string name="consent_no" msgid="2640796915611404382">"ඉඩ නොදෙන්න"</string> <string name="consent_cancel" msgid="5655005528379285841">"අවලංගු කරන්න"</string> <string name="consent_back" msgid="2560683030046918882">"ආපසු"</string> - <!-- no translation found for downward_arrow_action (2327165938832076333) --> - <skip /> - <!-- no translation found for downward_arrow (2292427714411156088) --> - <skip /> + <string name="downward_arrow_action" msgid="2327165938832076333">"ලැයිස්තුව පහළට අනුචලනය කරන්න"</string> + <string name="downward_arrow" msgid="2292427714411156088">"පහළට ඊතලය"</string> <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> විදහන්න"</string> <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> හකුළන්න"</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"<strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> the හි යෙදුම්වලට <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong> හි අවසරම දෙන්නද?"</string> diff --git a/packages/CompanionDeviceManager/res/values-sk/strings.xml b/packages/CompanionDeviceManager/res/values-sk/strings.xml index 5194f215444a..cbf56486a044 100644 --- a/packages/CompanionDeviceManager/res/values-sk/strings.xml +++ b/packages/CompanionDeviceManager/res/values-sk/strings.xml @@ -51,10 +51,8 @@ <string name="consent_no" msgid="2640796915611404382">"Nepovoliť"</string> <string name="consent_cancel" msgid="5655005528379285841">"Zrušiť"</string> <string name="consent_back" msgid="2560683030046918882">"Späť"</string> - <!-- no translation found for downward_arrow_action (2327165938832076333) --> - <skip /> - <!-- no translation found for downward_arrow (2292427714411156088) --> - <skip /> + <string name="downward_arrow_action" msgid="2327165938832076333">"Posunúť zoznam nadol"</string> + <string name="downward_arrow" msgid="2292427714411156088">"Šípka nadol"</string> <string name="permission_expand" msgid="893185038020887411">"Rozbaliť sekciu <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_collapse" msgid="3320833884220844084">"Zbaliť sekciu <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Chcete udeliť aplikáciám v zariadení <strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> rovnaké povolenia ako v zariadení <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong>?"</string> diff --git a/packages/CompanionDeviceManager/res/values-sl/strings.xml b/packages/CompanionDeviceManager/res/values-sl/strings.xml index 220022095677..20570c300038 100644 --- a/packages/CompanionDeviceManager/res/values-sl/strings.xml +++ b/packages/CompanionDeviceManager/res/values-sl/strings.xml @@ -51,10 +51,8 @@ <string name="consent_no" msgid="2640796915611404382">"Ne dovoli"</string> <string name="consent_cancel" msgid="5655005528379285841">"Prekliči"</string> <string name="consent_back" msgid="2560683030046918882">"Nazaj"</string> - <!-- no translation found for downward_arrow_action (2327165938832076333) --> - <skip /> - <!-- no translation found for downward_arrow (2292427714411156088) --> - <skip /> + <string name="downward_arrow_action" msgid="2327165938832076333">"Pomikanje navzdol po seznamu"</string> + <string name="downward_arrow" msgid="2292427714411156088">"Puščica navzdol"</string> <string name="permission_expand" msgid="893185038020887411">"Razširi dovoljenje »<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>«"</string> <string name="permission_collapse" msgid="3320833884220844084">"Strni dovoljenje »<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>«"</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Ali želite aplikacijam v napravi <strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> odobriti enaka dovoljenja kot v napravi <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong>?"</string> diff --git a/packages/CompanionDeviceManager/res/values-sq/strings.xml b/packages/CompanionDeviceManager/res/values-sq/strings.xml index f683dd9e2974..32ede4022156 100644 --- a/packages/CompanionDeviceManager/res/values-sq/strings.xml +++ b/packages/CompanionDeviceManager/res/values-sq/strings.xml @@ -51,10 +51,8 @@ <string name="consent_no" msgid="2640796915611404382">"Mos lejo"</string> <string name="consent_cancel" msgid="5655005528379285841">"Anulo"</string> <string name="consent_back" msgid="2560683030046918882">"Pas"</string> - <!-- no translation found for downward_arrow_action (2327165938832076333) --> - <skip /> - <!-- no translation found for downward_arrow (2292427714411156088) --> - <skip /> + <string name="downward_arrow_action" msgid="2327165938832076333">"Lëviz poshtë në listë"</string> + <string name="downward_arrow" msgid="2292427714411156088">"Shigjeta poshtë"</string> <string name="permission_expand" msgid="893185038020887411">"Zgjero: <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_collapse" msgid="3320833884220844084">"Palos: <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"T\'i jepen aplikacioneve në <strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> të njëjtat leje si në <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong>?"</string> diff --git a/packages/CompanionDeviceManager/res/values-sr/strings.xml b/packages/CompanionDeviceManager/res/values-sr/strings.xml index d56800958d81..ac84c345b923 100644 --- a/packages/CompanionDeviceManager/res/values-sr/strings.xml +++ b/packages/CompanionDeviceManager/res/values-sr/strings.xml @@ -51,10 +51,8 @@ <string name="consent_no" msgid="2640796915611404382">"Не дозволи"</string> <string name="consent_cancel" msgid="5655005528379285841">"Откажи"</string> <string name="consent_back" msgid="2560683030046918882">"Назад"</string> - <!-- no translation found for downward_arrow_action (2327165938832076333) --> - <skip /> - <!-- no translation found for downward_arrow (2292427714411156088) --> - <skip /> + <string name="downward_arrow_action" msgid="2327165938832076333">"Скролујте надоле на листи"</string> + <string name="downward_arrow" msgid="2292427714411156088">"Стрелица надоле"</string> <string name="permission_expand" msgid="893185038020887411">"Прошири <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_collapse" msgid="3320833884220844084">"Скупи <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Апликцијама на уређају <strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> дајете све дозволе као на уређају <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong>?"</string> diff --git a/packages/CompanionDeviceManager/res/values-sv/strings.xml b/packages/CompanionDeviceManager/res/values-sv/strings.xml index 5c6f5ba65525..c9364b8564ef 100644 --- a/packages/CompanionDeviceManager/res/values-sv/strings.xml +++ b/packages/CompanionDeviceManager/res/values-sv/strings.xml @@ -51,10 +51,8 @@ <string name="consent_no" msgid="2640796915611404382">"Tillåt inte"</string> <string name="consent_cancel" msgid="5655005528379285841">"Avbryt"</string> <string name="consent_back" msgid="2560683030046918882">"Tillbaka"</string> - <!-- no translation found for downward_arrow_action (2327165938832076333) --> - <skip /> - <!-- no translation found for downward_arrow (2292427714411156088) --> - <skip /> + <string name="downward_arrow_action" msgid="2327165938832076333">"Scrolla nedåt i listan"</string> + <string name="downward_arrow" msgid="2292427714411156088">"Nedåtpil"</string> <string name="permission_expand" msgid="893185038020887411">"Utöka <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_collapse" msgid="3320833884220844084">"Komprimera <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Vill du ge apparna på <strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> samma behörigheter som de har på <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong>?"</string> diff --git a/packages/CompanionDeviceManager/res/values-sw/strings.xml b/packages/CompanionDeviceManager/res/values-sw/strings.xml index 7a1cf98832a4..2a9cbee0f2ad 100644 --- a/packages/CompanionDeviceManager/res/values-sw/strings.xml +++ b/packages/CompanionDeviceManager/res/values-sw/strings.xml @@ -51,10 +51,8 @@ <string name="consent_no" msgid="2640796915611404382">"Usiruhusu"</string> <string name="consent_cancel" msgid="5655005528379285841">"Ghairi"</string> <string name="consent_back" msgid="2560683030046918882">"Nyuma"</string> - <!-- no translation found for downward_arrow_action (2327165938832076333) --> - <skip /> - <!-- no translation found for downward_arrow (2292427714411156088) --> - <skip /> + <string name="downward_arrow_action" msgid="2327165938832076333">"Sogeza chini kwenye orodha"</string> + <string name="downward_arrow" msgid="2292427714411156088">"Kishale kinachoelekeza chini"</string> <string name="permission_expand" msgid="893185038020887411">"Panua <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_collapse" msgid="3320833884220844084">"Kunja <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Ungependa kuzipa programu katika <strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> ruhusa ile ile kama kwenye <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong>?"</string> diff --git a/packages/CompanionDeviceManager/res/values-ta/strings.xml b/packages/CompanionDeviceManager/res/values-ta/strings.xml index 2313cc3d3b2a..2503826b9074 100644 --- a/packages/CompanionDeviceManager/res/values-ta/strings.xml +++ b/packages/CompanionDeviceManager/res/values-ta/strings.xml @@ -51,10 +51,8 @@ <string name="consent_no" msgid="2640796915611404382">"அனுமதிக்க வேண்டாம்"</string> <string name="consent_cancel" msgid="5655005528379285841">"ரத்துசெய்"</string> <string name="consent_back" msgid="2560683030046918882">"பின்செல்"</string> - <!-- no translation found for downward_arrow_action (2327165938832076333) --> - <skip /> - <!-- no translation found for downward_arrow (2292427714411156088) --> - <skip /> + <string name="downward_arrow_action" msgid="2327165938832076333">"பட்டியலில் கீழே நகர்த்து"</string> + <string name="downward_arrow" msgid="2292427714411156088">"கீழ்நோக்கிய அம்புக்குறி"</string> <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> ஐ விரிவாக்கும்"</string> <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> ஐச் சுருக்கும்"</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"<strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong> சாதனத்தில் இருக்கும் அதே அனுமதிகளை <strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> சாதனத்தில் உள்ள ஆப்ஸுக்கும் வழங்கவா?"</string> diff --git a/packages/CompanionDeviceManager/res/values-te/strings.xml b/packages/CompanionDeviceManager/res/values-te/strings.xml index eed0eeef8143..d82c57c3e8fd 100644 --- a/packages/CompanionDeviceManager/res/values-te/strings.xml +++ b/packages/CompanionDeviceManager/res/values-te/strings.xml @@ -51,10 +51,8 @@ <string name="consent_no" msgid="2640796915611404382">"అనుమతించవద్దు"</string> <string name="consent_cancel" msgid="5655005528379285841">"రద్దు చేయండి"</string> <string name="consent_back" msgid="2560683030046918882">"వెనుకకు"</string> - <!-- no translation found for downward_arrow_action (2327165938832076333) --> - <skip /> - <!-- no translation found for downward_arrow (2292427714411156088) --> - <skip /> + <string name="downward_arrow_action" msgid="2327165938832076333">"లిస్ట్ను కిందకు స్క్రోల్ చేయి"</string> + <string name="downward_arrow" msgid="2292427714411156088">"కింది వైపు బాణం గుర్తు"</string> <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>ను విస్తరించండి"</string> <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>ను కుదించండి"</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"<strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong>లోని యాప్లకు <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong>లో ఉన్న అనుమతులను ఇవ్వాలా?"</string> diff --git a/packages/CompanionDeviceManager/res/values-th/strings.xml b/packages/CompanionDeviceManager/res/values-th/strings.xml index bf14f4c529fd..26aa9ddc4205 100644 --- a/packages/CompanionDeviceManager/res/values-th/strings.xml +++ b/packages/CompanionDeviceManager/res/values-th/strings.xml @@ -51,10 +51,8 @@ <string name="consent_no" msgid="2640796915611404382">"ไม่อนุญาต"</string> <string name="consent_cancel" msgid="5655005528379285841">"ยกเลิก"</string> <string name="consent_back" msgid="2560683030046918882">"กลับ"</string> - <!-- no translation found for downward_arrow_action (2327165938832076333) --> - <skip /> - <!-- no translation found for downward_arrow (2292427714411156088) --> - <skip /> + <string name="downward_arrow_action" msgid="2327165938832076333">"เลื่อนรายการลง"</string> + <string name="downward_arrow" msgid="2292427714411156088">"ลูกศรชี้ลง"</string> <string name="permission_expand" msgid="893185038020887411">"ขยาย <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_collapse" msgid="3320833884220844084">"ยุบ <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"ให้แอปใน <strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> มีสิทธิ์เหมือนกับใน <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong> ไหม"</string> diff --git a/packages/CompanionDeviceManager/res/values-tr/strings.xml b/packages/CompanionDeviceManager/res/values-tr/strings.xml index 56997d7a09c3..01fc767868d4 100644 --- a/packages/CompanionDeviceManager/res/values-tr/strings.xml +++ b/packages/CompanionDeviceManager/res/values-tr/strings.xml @@ -51,10 +51,8 @@ <string name="consent_no" msgid="2640796915611404382">"İzin verme"</string> <string name="consent_cancel" msgid="5655005528379285841">"İptal"</string> <string name="consent_back" msgid="2560683030046918882">"Geri"</string> - <!-- no translation found for downward_arrow_action (2327165938832076333) --> - <skip /> - <!-- no translation found for downward_arrow (2292427714411156088) --> - <skip /> + <string name="downward_arrow_action" msgid="2327165938832076333">"Listeyi aşağı kaydırın"</string> + <string name="downward_arrow" msgid="2292427714411156088">"Aşağı ok"</string> <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> panelini genişlet"</string> <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> panelini daralt"</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"<strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> cihazındaki uygulamalara, <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong> cihazındakiyle aynı izinler verilsin mi?"</string> diff --git a/packages/CompanionDeviceManager/res/values-uk/strings.xml b/packages/CompanionDeviceManager/res/values-uk/strings.xml index 7d7b9dedde31..73e5d585c5a9 100644 --- a/packages/CompanionDeviceManager/res/values-uk/strings.xml +++ b/packages/CompanionDeviceManager/res/values-uk/strings.xml @@ -51,10 +51,8 @@ <string name="consent_no" msgid="2640796915611404382">"Не дозволяти"</string> <string name="consent_cancel" msgid="5655005528379285841">"Скасувати"</string> <string name="consent_back" msgid="2560683030046918882">"Назад"</string> - <!-- no translation found for downward_arrow_action (2327165938832076333) --> - <skip /> - <!-- no translation found for downward_arrow (2292427714411156088) --> - <skip /> + <string name="downward_arrow_action" msgid="2327165938832076333">"Прокрутити список униз"</string> + <string name="downward_arrow" msgid="2292427714411156088">"Стрілка вниз"</string> <string name="permission_expand" msgid="893185038020887411">"Розгорнути дозвіл \"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>\""</string> <string name="permission_collapse" msgid="3320833884220844084">"Згорнути дозвіл \"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>\""</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Надати додаткам на пристрої <strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> такі самі дозволи, що й на пристрої <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong>?"</string> diff --git a/packages/CompanionDeviceManager/res/values-uz/strings.xml b/packages/CompanionDeviceManager/res/values-uz/strings.xml index e1a024c985fd..b4ac45c9ceb6 100644 --- a/packages/CompanionDeviceManager/res/values-uz/strings.xml +++ b/packages/CompanionDeviceManager/res/values-uz/strings.xml @@ -51,10 +51,8 @@ <string name="consent_no" msgid="2640796915611404382">"Ruxsat berilmasin"</string> <string name="consent_cancel" msgid="5655005528379285841">"Bekor qilish"</string> <string name="consent_back" msgid="2560683030046918882">"Orqaga"</string> - <!-- no translation found for downward_arrow_action (2327165938832076333) --> - <skip /> - <!-- no translation found for downward_arrow (2292427714411156088) --> - <skip /> + <string name="downward_arrow_action" msgid="2327165938832076333">"Roʻyxatni pastga varaqlash"</string> + <string name="downward_arrow" msgid="2292427714411156088">"Pastga strelka"</string> <string name="permission_expand" msgid="893185038020887411">"Yoyish: <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_collapse" msgid="3320833884220844084">"Yopish: <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"<strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> ilovalariga <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong> qurilmasidagi kabi bir xil ruxsatlar berilsinmi?"</string> diff --git a/packages/CompanionDeviceManager/res/values-vi/strings.xml b/packages/CompanionDeviceManager/res/values-vi/strings.xml index 029331701fd7..11cceaf2c4d6 100644 --- a/packages/CompanionDeviceManager/res/values-vi/strings.xml +++ b/packages/CompanionDeviceManager/res/values-vi/strings.xml @@ -51,10 +51,8 @@ <string name="consent_no" msgid="2640796915611404382">"Không cho phép"</string> <string name="consent_cancel" msgid="5655005528379285841">"Huỷ"</string> <string name="consent_back" msgid="2560683030046918882">"Quay lại"</string> - <!-- no translation found for downward_arrow_action (2327165938832076333) --> - <skip /> - <!-- no translation found for downward_arrow (2292427714411156088) --> - <skip /> + <string name="downward_arrow_action" msgid="2327165938832076333">"Di chuyển xuống danh sách"</string> + <string name="downward_arrow" msgid="2292427714411156088">"Mũi tên xuống"</string> <string name="permission_expand" msgid="893185038020887411">"Mở rộng <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_collapse" msgid="3320833884220844084">"Thu gọn <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Cấp cho các ứng dụng trên <strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> các quyền giống như trên <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong>?"</string> diff --git a/packages/CompanionDeviceManager/res/values-zh-rCN/strings.xml b/packages/CompanionDeviceManager/res/values-zh-rCN/strings.xml index ed68dd3d08db..7632af0a3ca0 100644 --- a/packages/CompanionDeviceManager/res/values-zh-rCN/strings.xml +++ b/packages/CompanionDeviceManager/res/values-zh-rCN/strings.xml @@ -51,10 +51,8 @@ <string name="consent_no" msgid="2640796915611404382">"不允许"</string> <string name="consent_cancel" msgid="5655005528379285841">"取消"</string> <string name="consent_back" msgid="2560683030046918882">"返回"</string> - <!-- no translation found for downward_arrow_action (2327165938832076333) --> - <skip /> - <!-- no translation found for downward_arrow (2292427714411156088) --> - <skip /> + <string name="downward_arrow_action" msgid="2327165938832076333">"向下滚动列表"</string> + <string name="downward_arrow" msgid="2292427714411156088">"向下箭头"</string> <string name="permission_expand" msgid="893185038020887411">"展开<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_collapse" msgid="3320833884220844084">"收起<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"要让<strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong>上的应用享有在<strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong>上的同等权限吗?"</string> diff --git a/packages/CompanionDeviceManager/res/values-zh-rHK/strings.xml b/packages/CompanionDeviceManager/res/values-zh-rHK/strings.xml index 17d3482f0dbc..9f988c3ff585 100644 --- a/packages/CompanionDeviceManager/res/values-zh-rHK/strings.xml +++ b/packages/CompanionDeviceManager/res/values-zh-rHK/strings.xml @@ -51,10 +51,8 @@ <string name="consent_no" msgid="2640796915611404382">"不允許"</string> <string name="consent_cancel" msgid="5655005528379285841">"取消"</string> <string name="consent_back" msgid="2560683030046918882">"返回"</string> - <!-- no translation found for downward_arrow_action (2327165938832076333) --> - <skip /> - <!-- no translation found for downward_arrow (2292427714411156088) --> - <skip /> + <string name="downward_arrow_action" msgid="2327165938832076333">"向下捲動清單"</string> + <string name="downward_arrow" msgid="2292427714411156088">"向下箭咀"</string> <string name="permission_expand" msgid="893185038020887411">"展開<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_collapse" msgid="3320833884220844084">"收合<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"<strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> 上的應用程式可獲在 <strong><xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong> 上的相同權限嗎?"</string> diff --git a/packages/CompanionDeviceManager/res/values-zh-rTW/strings.xml b/packages/CompanionDeviceManager/res/values-zh-rTW/strings.xml index 542a73fc099b..5bc7bfa3781a 100644 --- a/packages/CompanionDeviceManager/res/values-zh-rTW/strings.xml +++ b/packages/CompanionDeviceManager/res/values-zh-rTW/strings.xml @@ -51,10 +51,8 @@ <string name="consent_no" msgid="2640796915611404382">"不允許"</string> <string name="consent_cancel" msgid="5655005528379285841">"取消"</string> <string name="consent_back" msgid="2560683030046918882">"返回"</string> - <!-- no translation found for downward_arrow_action (2327165938832076333) --> - <skip /> - <!-- no translation found for downward_arrow (2292427714411156088) --> - <skip /> + <string name="downward_arrow_action" msgid="2327165938832076333">"向下捲動清單"</string> + <string name="downward_arrow" msgid="2292427714411156088">"向下箭頭"</string> <string name="permission_expand" msgid="893185038020887411">"展開<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_collapse" msgid="3320833884220844084">"收合<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"要讓「<xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g>」<strong></strong>的應用程式沿用在「<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g>」<strong></strong>上的權限嗎?"</string> diff --git a/packages/CompanionDeviceManager/res/values-zu/strings.xml b/packages/CompanionDeviceManager/res/values-zu/strings.xml index b1c7a14d1210..37034fa6718b 100644 --- a/packages/CompanionDeviceManager/res/values-zu/strings.xml +++ b/packages/CompanionDeviceManager/res/values-zu/strings.xml @@ -51,10 +51,8 @@ <string name="consent_no" msgid="2640796915611404382">"Ungavumeli"</string> <string name="consent_cancel" msgid="5655005528379285841">"Khansela"</string> <string name="consent_back" msgid="2560683030046918882">"Emuva"</string> - <!-- no translation found for downward_arrow_action (2327165938832076333) --> - <skip /> - <!-- no translation found for downward_arrow (2292427714411156088) --> - <skip /> + <string name="downward_arrow_action" msgid="2327165938832076333">"Skrola uye ezansi ohlwini"</string> + <string name="downward_arrow" msgid="2292427714411156088">"Umcibisholo obheke phansi"</string> <string name="permission_expand" msgid="893185038020887411">"Nweba i-<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_collapse" msgid="3320833884220844084">"Goqa i-<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string> <string name="permission_sync_confirmation_title" msgid="4409622174437248702">"Nikeza ama-app <strong><xliff:g id="COMPANION_DEVICE_NAME">%1$s</xliff:g></strong> izimvume ezifanayot <strong>njengaku-<xliff:g id="PRIMARY_DEVICE_NAME">%2$s</xliff:g></strong>?"</string> diff --git a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java index e818a603c5b4..bf739620bc99 100644 --- a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java +++ b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java @@ -138,8 +138,12 @@ public class IllustrationPreference extends Preference implements GroupSectionDi ImageView backgroundViewTablet = (ImageView) holder.findViewById(R.id.background_view_tablet); - backgroundView.setVisibility(mIsTablet ? View.GONE : View.VISIBLE); - backgroundViewTablet.setVisibility(mIsTablet ? View.VISIBLE : View.GONE); + if (backgroundView != null) { + backgroundView.setVisibility(mIsTablet ? View.GONE : View.VISIBLE); + } + if (backgroundViewTablet != null) { + backgroundViewTablet.setVisibility(mIsTablet ? View.VISIBLE : View.GONE); + } if (mIsTablet) { backgroundView = backgroundViewTablet; } @@ -183,7 +187,9 @@ public class IllustrationPreference extends Preference implements GroupSectionDi if (mLottieDynamicColor) { LottieColorUtils.applyDynamicColors(getContext(), illustrationView); } - LottieColorUtils.applyMaterialColor(getContext(), illustrationView); + if (SettingsThemeHelper.isExpressiveTheme(getContext())) { + LottieColorUtils.applyMaterialColor(getContext(), illustrationView); + } if (mOnBindListener != null) { mOnBindListener.onBind(illustrationView); @@ -443,6 +449,10 @@ public class IllustrationPreference extends Preference implements GroupSectionDi illustrationView.setMaxWidth((int) (restrictedMaxHeight * aspectRatio)); } + public boolean isAnimatable() { + return mIsAnimatable; + } + private void startAnimation(Drawable drawable) { if (!(drawable instanceof Animatable)) { return; diff --git a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/LottieColorUtils.java b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/LottieColorUtils.java index 4421424c0e39..e59cc81d3ba8 100644 --- a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/LottieColorUtils.java +++ b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/LottieColorUtils.java @@ -157,10 +157,6 @@ public class LottieColorUtils { /** Applies material colors. */ public static void applyMaterialColor(@NonNull Context context, @NonNull LottieAnimationView lottieAnimationView) { - if (!SettingsThemeHelper.isExpressiveTheme(context)) { - return; - } - for (String key : MATERIAL_COLOR_MAP.keySet()) { final int color = context.getColor(MATERIAL_COLOR_MAP.get(key)); lottieAnimationView.addValueCallback( diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values/strings.xml index 75809730a514..2ffdc930ccea 100644 --- a/packages/SettingsLib/RestrictedLockUtils/res/values/strings.xml +++ b/packages/SettingsLib/RestrictedLockUtils/res/values/strings.xml @@ -21,8 +21,4 @@ <string name="enabled_by_admin">Enabled by admin</string> <!-- Summary for switch preference to denote it is switched off by an admin [CHAR LIMIT=50] --> <string name="disabled_by_admin">Disabled by admin</string> - <!-- Summary for switch preference to denote it is switched on by Advanced protection [CHAR LIMIT=50] --> - <string name="enabled_by_advanced_protection">Enabled by Advanced Protection</string> - <!-- Summary for switch preference to denote it is switched off by Advanced protection [CHAR LIMIT=50] --> - <string name="disabled_by_advanced_protection">Disabled by Advanced Protection</string> </resources> diff --git a/packages/SettingsLib/SettingsSpinner/src/com/android/settingslib/widget/SettingsSpinnerPreference.java b/packages/SettingsLib/SettingsSpinner/src/com/android/settingslib/widget/SettingsSpinnerPreference.java index 0f6a2a082e0c..1170f1e7c695 100644 --- a/packages/SettingsLib/SettingsSpinner/src/com/android/settingslib/widget/SettingsSpinnerPreference.java +++ b/packages/SettingsLib/SettingsSpinner/src/com/android/settingslib/widget/SettingsSpinnerPreference.java @@ -19,6 +19,7 @@ package com.android.settingslib.widget; import android.content.Context; import android.util.AttributeSet; import android.view.View; +import android.view.accessibility.AccessibilityEvent; import android.widget.AdapterView; import android.widget.Spinner; @@ -110,7 +111,6 @@ public class SettingsSpinnerPreference extends Preference notifyChanged(); } - @Override public void onBindViewHolder(PreferenceViewHolder holder) { super.onBindViewHolder(holder); @@ -119,6 +119,18 @@ public class SettingsSpinnerPreference extends Preference spinner.setSelection(mPosition); spinner.setOnItemSelectedListener(mOnSelectedListener); spinner.setLongClickable(false); + spinner.setAccessibilityDelegate( + new View.AccessibilityDelegate() { + @Override + public void sendAccessibilityEvent(View host, int eventType) { + if (eventType == AccessibilityEvent.TYPE_VIEW_SELECTED) { + // Ignore the INTERRUPT events TYPE_VIEW_SELECTED or Talkback will speak + // for it while fragment updating. + return; + } + super.sendAccessibilityEvent(host, eventType); + } + }); if (mShouldPerformClick) { mShouldPerformClick = false; // To show dropdown view. diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictedMode.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictedMode.kt index a8483308556d..6f37f0cc5799 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictedMode.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictedMode.kt @@ -46,7 +46,7 @@ internal data class BlockedByAdminImpl( ) : BlockedByAdmin { override fun getSummary(checked: Boolean?) = when (checked) { true -> enterpriseRepository.getAdminSummaryString( - advancedProtectionStringId = R.string.enabled_by_advanced_protection, + advancedProtectionStringId = com.android.settingslib.R.string.enabled, updatableStringId = Settings.ENABLED_BY_ADMIN_SWITCH_SUMMARY, resId = R.string.enabled_by_admin, enforcedAdmin = enforcedAdmin, @@ -54,7 +54,7 @@ internal data class BlockedByAdminImpl( ) false -> enterpriseRepository.getAdminSummaryString( - advancedProtectionStringId = R.string.disabled_by_advanced_protection, + advancedProtectionStringId = com.android.settingslib.R.string.disabled, updatableStringId = Settings.DISABLED_BY_ADMIN_SWITCH_SUMMARY, resId = R.string.disabled_by_admin, enforcedAdmin = enforcedAdmin, diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsSystemInteger.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsSystemInteger.kt new file mode 100644 index 000000000000..db7a640be893 --- /dev/null +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsSystemInteger.kt @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.spaprivileged.settingsprovider + +import android.content.ContentResolver +import android.content.Context +import android.provider.Settings +import com.android.settingslib.spaprivileged.database.contentChangeFlow +import kotlin.properties.ReadWriteProperty +import kotlin.reflect.KProperty +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.conflate +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map + +fun Context.settingsSystemInteger( + name: String, + defaultValue: Int +): ReadWriteProperty<Any?, Int> = SettingsSystemIntegerDelegate(this, name, defaultValue) + +fun Context.settingsSystemIntegerFlow(name: String, defaultValue: Int): Flow<Int> { + val value by settingsSystemInteger(name, defaultValue) + return contentChangeFlow(Settings.System.getUriFor(name)) + .map { value } + .distinctUntilChanged() + .conflate() + .flowOn(Dispatchers.IO) +} + +private class SettingsSystemIntegerDelegate( + context: Context, + private val name: String, + private val defaultValue: Int, +) : ReadWriteProperty<Any?, Int> { + + private val contentResolver: ContentResolver = context.contentResolver + + override fun getValue(thisRef: Any?, property: KProperty<*>): Int = + Settings.System.getInt(contentResolver, name, defaultValue) + + override fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) { + Settings.System.putInt(contentResolver, name, value) + } +} diff --git a/packages/SettingsLib/SpaPrivileged/tests/robotests/Android.bp b/packages/SettingsLib/SpaPrivileged/tests/robotests/Android.bp new file mode 100644 index 000000000000..e3faf73a69fb --- /dev/null +++ b/packages/SettingsLib/SpaPrivileged/tests/robotests/Android.bp @@ -0,0 +1,59 @@ +// +// Copyright (C) 2025 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +package { + default_applicable_licenses: ["frameworks_base_license"], +} + +android_app { + name: "SpaPrivilegedRoboTestStub", + defaults: [ + "SpaPrivilegedLib-defaults", + ], + platform_apis: true, + certificate: "platform", + privileged: true, +} + +android_robolectric_test { + name: "SpaPrivilegedRoboTests", + srcs: [ + ":SpaPrivilegedLib_srcs", + "src/**/*.java", + "src/**/*.kt", + ], + + defaults: [ + "SpaPrivilegedLib-defaults", + ], + + static_libs: [ + "SpaLibTestUtils", + "androidx.test.ext.junit", + "androidx.test.runner", + ], + + java_resource_dirs: [ + "config", + ], + + instrumentation_for: "SpaPrivilegedRoboTestStub", + + test_options: { + timeout: 36000, + }, + + strict_mode: false, +} diff --git a/packages/SettingsLib/SpaPrivileged/tests/robotests/AndroidManifest.xml b/packages/SettingsLib/SpaPrivileged/tests/robotests/AndroidManifest.xml new file mode 100644 index 000000000000..113852d74f69 --- /dev/null +++ b/packages/SettingsLib/SpaPrivileged/tests/robotests/AndroidManifest.xml @@ -0,0 +1,25 @@ +<?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" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + coreApp="true" + package="com.android.settingslib.spaprivileged.settingsprovider"> + + <uses-permission android:name="android.permission.WRITE_SETTINGS" /> + + <application/> +</manifest>
\ No newline at end of file diff --git a/packages/SettingsLib/SpaPrivileged/tests/robotests/config/robolectric.properties b/packages/SettingsLib/SpaPrivileged/tests/robotests/config/robolectric.properties new file mode 100644 index 000000000000..95a24bde00f6 --- /dev/null +++ b/packages/SettingsLib/SpaPrivileged/tests/robotests/config/robolectric.properties @@ -0,0 +1,16 @@ +/* +* Copyright (C) 2025 The Android Open Source Project +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +sdk=NEWEST_SDK
\ No newline at end of file diff --git a/packages/SettingsLib/SpaPrivileged/tests/robotests/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsSystemIntegerTest.kt b/packages/SettingsLib/SpaPrivileged/tests/robotests/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsSystemIntegerTest.kt new file mode 100644 index 000000000000..67e4180a624b --- /dev/null +++ b/packages/SettingsLib/SpaPrivileged/tests/robotests/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsSystemIntegerTest.kt @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.settingslib.spaprivileged.settingsprovider + +import android.content.Context +import android.provider.Settings + +import androidx.test.core.app.ApplicationProvider + +import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull +import com.android.settingslib.spa.testutils.toListWithTimeout +import com.google.common.truth.Truth.assertThat + +import kotlinx.coroutines.async +import kotlinx.coroutines.delay +import kotlinx.coroutines.runBlocking + +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner + +@RunWith(RobolectricTestRunner::class) +class SettingsSystemIntegerTest { + private val context: Context = ApplicationProvider.getApplicationContext() + + @Before + fun setUp() { + Settings.System.putString(context.contentResolver, TEST_NAME, null) + } + + @Test + fun setIntValue_returnSameValueByDelegate() { + val settingValue = 250 + + Settings.System.putInt(context.contentResolver, TEST_NAME, settingValue) + + val value by context.settingsSystemInteger(TEST_NAME, TEST_SETTING_DEFAULT_VALUE) + + assertThat(value).isEqualTo(settingValue) + } + + @Test + fun setZero_returnZeroByDelegate() { + val settingValue = 0 + Settings.System.putInt(context.contentResolver, TEST_NAME, settingValue) + + val value by context.settingsSystemInteger(TEST_NAME, TEST_SETTING_DEFAULT_VALUE) + + assertThat(value).isEqualTo(settingValue) + } + + @Test + fun setValueByDelegate_getValueFromSettings() { + val settingsValue = 5 + var value by context.settingsSystemInteger(TEST_NAME, TEST_SETTING_DEFAULT_VALUE) + + value = settingsValue + + assertThat(Settings.System.getInt(context.contentResolver, TEST_NAME, TEST_SETTING_DEFAULT_VALUE)).isEqualTo(settingsValue) + } + + @Test + fun setZeroByDelegate_getZeroFromSettings() { + val settingValue = 0 + var value by context.settingsSystemInteger(TEST_NAME, TEST_SETTING_DEFAULT_VALUE) + + value = settingValue + + assertThat(Settings.System.getInt(context.contentResolver, TEST_NAME, TEST_SETTING_DEFAULT_VALUE)).isEqualTo(settingValue) + } + + @Test + fun setValueByDelegate_returnValueFromsettingsSystemIntegerFlow() = runBlocking<Unit> { + val settingValue = 7 + var value by context.settingsSystemInteger(TEST_NAME, TEST_SETTING_DEFAULT_VALUE) + value = settingValue + + val flow = context.settingsSystemIntegerFlow(TEST_NAME, TEST_SETTING_DEFAULT_VALUE) + + assertThat(flow.firstWithTimeoutOrNull()).isEqualTo(settingValue) + } + + @Test + fun setValueByDelegateTwice_collectAfterValueChanged_onlyKeepLatest() = runBlocking<Unit> { + val firstSettingValue = 5 + val secondSettingValue = 10 + + var value by context.settingsSystemInteger(TEST_NAME, TEST_SETTING_DEFAULT_VALUE) + value = firstSettingValue + + val flow = context.settingsSystemIntegerFlow(TEST_NAME, TEST_SETTING_DEFAULT_VALUE) + value = secondSettingValue + + assertThat(flow.firstWithTimeoutOrNull()).isEqualTo(value) + } + + @Test + fun settingsSystemIntegerFlow_collectBeforeValueChanged_getBoth() = runBlocking<Unit> { + val firstSettingValue = 12 + val secondSettingValue = 17 + val delay_ms = 100L + + var value by context.settingsSystemInteger(TEST_NAME, TEST_SETTING_DEFAULT_VALUE) + value = firstSettingValue + + + val listDeferred = async { + context.settingsSystemIntegerFlow(TEST_NAME, TEST_SETTING_DEFAULT_VALUE).toListWithTimeout() + } + + delay(delay_ms) + value = secondSettingValue + + assertThat(listDeferred.await()) + .containsAtLeast(firstSettingValue, secondSettingValue).inOrder() + } + + private companion object { + const val TEST_NAME = "test_system_integer_delegate" + const val TEST_SETTING_DEFAULT_VALUE = -1 + } +} diff --git a/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictedModeTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictedModeTest.kt index f3245c9085e7..189bf363420c 100644 --- a/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictedModeTest.kt +++ b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictedModeTest.kt @@ -77,8 +77,8 @@ class RestrictedModeTest { if (RestrictedLockUtilsInternal.isPolicyEnforcedByAdvancedProtection(context, RESTRICTION, userId)) { return when (advancedProtectionStringId) { - R.string.enabled_by_advanced_protection -> ENABLED_BY_ADVANCED_PROTECTION - R.string.disabled_by_advanced_protection -> DISABLED_BY_ADVANCED_PROTECTION + com.android.settingslib.R.string.enabled -> ENABLED + com.android.settingslib.R.string.disabled -> DISABLED else -> "" } } @@ -129,7 +129,7 @@ class RestrictedModeTest { val summary = blockedByAdmin.getSummary(true) - assertThat(summary).isEqualTo(ENABLED_BY_ADVANCED_PROTECTION) + assertThat(summary).isEqualTo(ENABLED) } @RequiresFlagsEnabled(Flags.FLAG_AAPM_API) @@ -148,7 +148,7 @@ class RestrictedModeTest { val summary = blockedByAdmin.getSummary(false) - assertThat(summary).isEqualTo(DISABLED_BY_ADVANCED_PROTECTION) + assertThat(summary).isEqualTo(DISABLED) } @RequiresFlagsEnabled(Flags.FLAG_AAPM_API) @@ -202,7 +202,7 @@ class RestrictedModeTest { const val ENABLED_BY_ADMIN = "Enabled by admin" const val DISABLED_BY_ADMIN = "Disabled by admin" - const val ENABLED_BY_ADVANCED_PROTECTION = "Enabled by advanced protection" - const val DISABLED_BY_ADVANCED_PROTECTION = "Disabled by advanced protection" + const val ENABLED = "Enabled" + const val DISABLED = "Disabled" } } diff --git a/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt index 79085af63c6d..308b285e0cfc 100644 --- a/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt +++ b/packages/SettingsLib/SpaPrivileged/tests/unit/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt @@ -175,13 +175,7 @@ class TogglePermissionAppListPageTest { val summary = getSummary(listModel) - assertThat(summary) - .isEqualTo( - context.getString( - com.android.settingslib.widget.restricted.R.string - .disabled_by_advanced_protection - ) - ) + assertThat(summary).isEqualTo(context.getString(com.android.settingslib.R.string.disabled)) } @RequiresFlagsEnabled(Flags.FLAG_AAPM_API) diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index 91ec83690722..03cb1ffbdef1 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -1263,6 +1263,8 @@ <!-- [CHAR LIMIT=25] Manage applications, text telling using an application is disabled. --> <string name="disabled">Disabled</string> + <!-- Summary for a settings preference indicating it is enabled [CHAR LIMIT = 30] --> + <string name="enabled">Enabled</string> <!-- Summary of app trusted to install apps [CHAR LIMIT=45] --> <string name="external_source_trusted">Allowed</string> <!-- Summary of app not trusted to install apps [CHAR LIMIT=45] --> diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java index 1044750bae25..9d979019be58 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java @@ -120,7 +120,7 @@ public class RestrictedPreferenceHelper { final TextView summaryView = (TextView) holder.findViewById(android.R.id.summary); if (summaryView != null) { final CharSequence disabledText = getDisabledByAdminSummaryString(); - if (mDisabledByAdmin) { + if (mDisabledByAdmin && disabledText != null) { summaryView.setText(disabledText); } else if (mDisabledByEcm) { summaryView.setText(getEcmTextResId()); @@ -132,10 +132,10 @@ public class RestrictedPreferenceHelper { } } - private String getDisabledByAdminSummaryString() { + private @Nullable String getDisabledByAdminSummaryString() { if (isRestrictionEnforcedByAdvancedProtection()) { - return mContext.getString(com.android.settingslib.widget.restricted - .R.string.disabled_by_advanced_protection); + // Advanced Protection doesn't set the summary string, it keeps the current summary. + return null; } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { return mContext.getSystemService(DevicePolicyManager.class).getResources().getString( @@ -321,7 +321,10 @@ public class RestrictedPreferenceHelper { } if (android.security.Flags.aapmApi() && !isEnabled && mDisabledByAdmin) { - mPreference.setSummary(getDisabledByAdminSummaryString()); + String summary = getDisabledByAdminSummaryString(); + if (summary != null) { + mPreference.setSummary(summary); + } } if (!isEnabled && mDisabledByEcm) { diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java index a5fa6a854e97..67c4207cb8be 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java @@ -36,6 +36,7 @@ import android.widget.LinearLayout; import android.widget.TextView; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.preference.PreferenceManager; import androidx.preference.PreferenceViewHolder; @@ -141,7 +142,7 @@ public class RestrictedSwitchPreference extends SwitchPreferenceCompat implement final TextView additionalSummaryView = (TextView) holder.findViewById( R.id.additional_summary); if (additionalSummaryView != null) { - if (isDisabledByAdmin()) { + if (isDisabledByAdmin() && switchSummary != null) { additionalSummaryView.setText(switchSummary); additionalSummaryView.setVisibility(View.VISIBLE); } else { @@ -151,7 +152,7 @@ public class RestrictedSwitchPreference extends SwitchPreferenceCompat implement } else { final TextView summaryView = (TextView) holder.findViewById(android.R.id.summary); if (summaryView != null) { - if (isDisabledByAdmin()) { + if (isDisabledByAdmin() && switchSummary != null) { summaryView.setText(switchSummary); summaryView.setVisibility(View.VISIBLE); } @@ -171,14 +172,10 @@ public class RestrictedSwitchPreference extends SwitchPreferenceCompat implement () -> context.getString(resId)); } - private String getRestrictedSwitchSummary() { + private @Nullable String getRestrictedSwitchSummary() { if (mHelper.isRestrictionEnforcedByAdvancedProtection()) { - final int apmResId = isChecked() - ? com.android.settingslib.widget.restricted.R.string - .enabled_by_advanced_protection - : com.android.settingslib.widget.restricted.R.string - .disabled_by_advanced_protection; - return getContext().getString(apmResId); + // Advanced Protection doesn't set the summary string, it keeps the current summary. + return null; } return isChecked() diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt index 88bccc9f6ebd..2ed437c94439 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt @@ -509,6 +509,7 @@ open class WifiUtils { val intent = AdvancedProtectionManager.createSupportIntent( AdvancedProtectionManager.FEATURE_ID_DISALLOW_WEP, AdvancedProtectionManager.SUPPORT_DIALOG_TYPE_BLOCKED_INTERACTION) + intent.putExtra(DIALOG_WINDOW_TYPE, dialogWindowType) onStartActivity(intent) } else if (wifiManager.isWepSupported == true && wifiManager.queryWepAllowed()) { onAllowed() diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedPreferenceHelperTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedPreferenceHelperTest.java index dbbbd5bf8089..f9769fa61e0d 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedPreferenceHelperTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedPreferenceHelperTest.java @@ -23,6 +23,7 @@ import static com.google.common.truth.Truth.assertWithMessage; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.RETURNS_DEEP_STUBS; +import static org.mockito.Mockito.atMostOnce; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -130,7 +131,7 @@ public class RestrictedPreferenceHelperTest { @RequiresFlagsEnabled(android.security.Flags.FLAG_AAPM_API) @Test - public void bindPreference_disabled_byAdvancedProtection_shouldDisplayDisabledSummary() { + public void bindPreference_disabled_byAdvancedProtection_shouldKeepExistingSummary() { final TextView summaryView = mock(TextView.class, RETURNS_DEEP_STUBS); final String userRestriction = UserManager.DISALLOW_UNINSTALL_APPS; final RestrictedLockUtils.EnforcedAdmin enforcedAdmin = new RestrictedLockUtils @@ -143,16 +144,14 @@ public class RestrictedPreferenceHelperTest { .thenReturn(summaryView); when(mDevicePolicyManager.getEnforcingAdmin(UserHandle.myUserId(), userRestriction)) .thenReturn(advancedProtectionEnforcingAdmin); - when(mContext.getString( - com.android.settingslib.widget.restricted.R.string.disabled_by_advanced_protection)) - .thenReturn("advanced_protection"); + summaryView.setText("existing summary"); mHelper.useAdminDisabledSummary(true); mHelper.setDisabledByAdmin(enforcedAdmin); mHelper.onBindViewHolder(mViewHolder); - verify(summaryView).setText("advanced_protection"); - verify(summaryView, never()).setVisibility(View.GONE); + verify(summaryView, atMostOnce()).setText(any()); // To set it to existing summary + verify(summaryView, never()).setVisibility(View.VISIBLE); } @RequiresFlagsEnabled(android.security.Flags.FLAG_AAPM_API) diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java index 3d16d6f1cd56..c387d48b33da 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java @@ -507,4 +507,25 @@ public class MediaDeviceTest { assertThat(mPhoneMediaDevice.getSelectionBehavior()).isEqualTo( SELECTION_BEHAVIOR_TRANSFER); } + + @Test + public void getSelectionBehavior_withRouteListingPreferenceItem_returnPreferenceBehavior() { + mItem = + new RouteListingPreference.Item.Builder(DEVICE_ADDRESS_1) + .setSelectionBehavior(SELECTION_BEHAVIOR_GO_TO_APP) + .build(); + MediaDevice castMediaDevice = new ComplexMediaDevice(mContext, mRouteInfo1, mItem); + + assertThat(castMediaDevice.hasRouteListingPreferenceItem()).isTrue(); + assertThat(castMediaDevice.getSelectionBehavior()).isEqualTo(SELECTION_BEHAVIOR_GO_TO_APP); + } + + @Test + public void getSelectionBehavior_withoutRouteListingPreferenceItem_returnTransfer() { + MediaDevice castMediaDevice = + new ComplexMediaDevice(mContext, mRouteInfo1, /* item= */ null); + + assertThat(castMediaDevice.hasRouteListingPreferenceItem()).isFalse(); + assertThat(castMediaDevice.getSelectionBehavior()).isEqualTo(SELECTION_BEHAVIOR_TRANSFER); + } } diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml index dafcc729b8f1..d929b0de391a 100644 --- a/packages/SettingsProvider/res/values/defaults.xml +++ b/packages/SettingsProvider/res/values/defaults.xml @@ -187,6 +187,9 @@ <!-- Default state of tap to wake --> <bool name="def_double_tap_to_wake">true</bool> + <!-- Default setting for double tap to sleep (Settings.Secure.DOUBLE_TAP_TO_SLEEP) --> + <bool name="def_double_tap_to_sleep">false</bool> + <!-- Default for Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT --> <string name="def_nfc_payment_component"></string> diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java index c0105298899b..a2291123e192 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java @@ -85,6 +85,7 @@ public class SecureSettings { Settings.Secure.MOUNT_UMS_PROMPT, Settings.Secure.MOUNT_UMS_NOTIFY_ENABLED, Settings.Secure.DOUBLE_TAP_TO_WAKE, + Settings.Secure.DOUBLE_TAP_TO_SLEEP, Settings.Secure.WAKE_GESTURE_ENABLED, Settings.Secure.LONG_PRESS_TIMEOUT, Settings.Secure.KEY_REPEAT_ENABLED, @@ -229,6 +230,7 @@ public class SecureSettings { Settings.Secure.ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED, Settings.Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED, Settings.Secure.ACCESSIBILITY_MAGNIFICATION_ALWAYS_ON_ENABLED, + Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE, Settings.Secure.ACCESSIBILITY_MAGNIFICATION_JOYSTICK_ENABLED, Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED, Settings.Secure.ACCESSIBILITY_MOUSE_KEYS_ENABLED, @@ -266,6 +268,7 @@ public class SecureSettings { Settings.Secure.SEARCH_ALL_ENTRYPOINTS_ENABLED, Settings.Secure.HUB_MODE_TUTORIAL_STATE, Settings.Secure.GLANCEABLE_HUB_ENABLED, + Settings.Secure.WHEN_TO_START_GLANCEABLE_HUB, Settings.Secure.STYLUS_BUTTONS_ENABLED, Settings.Secure.STYLUS_HANDWRITING_ENABLED, Settings.Secure.DEFAULT_NOTE_TASK_PROFILE, @@ -293,5 +296,7 @@ public class SecureSettings { Settings.Secure.FINGERPRINT_APP_ENABLED, Settings.Secure.FINGERPRINT_KEYGUARD_ENABLED, Settings.Secure.DUAL_SHADE, + Settings.Secure.BROWSER_CONTENT_FILTERS_ENABLED, + Settings.Secure.SEARCH_CONTENT_FILTERS_ENABLED, }; } diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java index 0ffdf53f2036..a4325344709a 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java @@ -131,6 +131,7 @@ public class SecureSettingsValidators { VALIDATORS.put(Secure.MOUNT_UMS_PROMPT, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.MOUNT_UMS_NOTIFY_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.DOUBLE_TAP_TO_WAKE, BOOLEAN_VALIDATOR); + VALIDATORS.put(Secure.DOUBLE_TAP_TO_SLEEP, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.WAKE_GESTURE_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.LONG_PRESS_TIMEOUT, NON_NEGATIVE_INTEGER_VALIDATOR); VALIDATORS.put(Secure.KEY_REPEAT_ENABLED, BOOLEAN_VALIDATOR); @@ -323,6 +324,10 @@ public class SecureSettingsValidators { Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL)); VALIDATORS.put(Secure.ACCESSIBILITY_MAGNIFICATION_FOLLOW_TYPING_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.ACCESSIBILITY_MAGNIFICATION_ALWAYS_ON_ENABLED, BOOLEAN_VALIDATOR); + VALIDATORS.put(Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE, + new InclusiveIntegerRangeValidator( + Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_CONTINUOUS, + Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_EDGE)); VALIDATORS.put(Secure.ACCESSIBILITY_MAGNIFICATION_JOYSTICK_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put( Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED, @@ -429,6 +434,8 @@ public class SecureSettingsValidators { VALIDATORS.put(Secure.DND_CONFIGS_MIGRATED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.HUB_MODE_TUTORIAL_STATE, NON_NEGATIVE_INTEGER_VALIDATOR); VALIDATORS.put(Secure.GLANCEABLE_HUB_ENABLED, new InclusiveIntegerRangeValidator(0, 1)); + VALIDATORS.put(Secure.WHEN_TO_START_GLANCEABLE_HUB, + new InclusiveIntegerRangeValidator(0, 3)); VALIDATORS.put(Secure.STYLUS_BUTTONS_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.STYLUS_HANDWRITING_ENABLED, new DiscreteValueValidator(new String[] {"-1", "0", "1"})); @@ -461,5 +468,7 @@ public class SecureSettingsValidators { VALIDATORS.put(Secure.FINGERPRINT_APP_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.FINGERPRINT_KEYGUARD_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.DUAL_SHADE, BOOLEAN_VALIDATOR); + VALIDATORS.put(Secure.BROWSER_CONTENT_FILTERS_ENABLED, BOOLEAN_VALIDATOR); + VALIDATORS.put(Secure.SEARCH_CONTENT_FILTERS_ENABLED, BOOLEAN_VALIDATOR); } } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java index e07832eea65e..57facdaa388c 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java @@ -1881,6 +1881,10 @@ class SettingsProtoDumpUtil { SecureSettingsProto.Accessibility .ACCESSIBILITY_MAGNIFICATION_JOYSTICK_ENABLED); dumpSetting(s, p, + Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE, + SecureSettingsProto.Accessibility + .ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE); + dumpSetting(s, p, Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED, SecureSettingsProto.Accessibility .ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 4a225bdbd7e5..65ede9d804d0 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -4080,7 +4080,7 @@ public class SettingsProvider extends ContentProvider { @VisibleForTesting final class UpgradeController { - private static final int SETTINGS_VERSION = 227; + private static final int SETTINGS_VERSION = 228; private final int mUserId; @@ -6319,6 +6319,23 @@ public class SettingsProvider extends ContentProvider { currentVersion = 227; } + // Version 227: Add default value for DOUBLE_TAP_TO_SLEEP. + if (currentVersion == 227) { + final SettingsState secureSettings = getSecureSettingsLocked(userId); + final Setting doubleTapToSleep = secureSettings.getSettingLocked( + Settings.Secure.DOUBLE_TAP_TO_SLEEP); + if (doubleTapToSleep.isNull()) { + secureSettings.insertSettingOverrideableByRestoreLocked( + Settings.Secure.DOUBLE_TAP_TO_SLEEP, + getContext().getResources().getBoolean( + R.bool.def_double_tap_to_sleep) ? "1" : "0", + null /* tag */, + true /* makeDefault */, + SettingsState.SYSTEM_PACKAGE_NAME); + } + currentVersion = 228; + } + // vXXX: Add new settings above this point. if (currentVersion != newVersion) { diff --git a/packages/Shell/res/values-et/strings.xml b/packages/Shell/res/values-et/strings.xml index 48c73345ddcc..c18687fd18ff 100644 --- a/packages/Shell/res/values-et/strings.xml +++ b/packages/Shell/res/values-et/strings.xml @@ -21,7 +21,7 @@ <string name="bugreport_in_progress_title" msgid="4311705936714972757">"Luuakse veaaruannet <xliff:g id="ID">#%d</xliff:g>"</string> <string name="bugreport_finished_title" msgid="4429132808670114081">"Jäädvustati veaaruanne <xliff:g id="ID">#%d</xliff:g>"</string> <string name="bugreport_updating_title" msgid="4423539949559634214">"Üksikasjade lisamine veaaruandesse"</string> - <string name="bugreport_updating_wait" msgid="3322151947853929470">"Oodake …"</string> + <string name="bugreport_updating_wait" msgid="3322151947853929470">"Palun oodake…"</string> <string name="bugreport_finished_text" product="watch" msgid="1223616207145252689">"Veaaruanne kuvatakse telefonis peagi"</string> <string name="bugreport_finished_text" product="tv" msgid="5758325479058638893">"Veaaruande jagamiseks valige"</string> <string name="bugreport_finished_text" product="default" msgid="8353769438382138847">"Veaaruande jagamiseks puudutage"</string> diff --git a/packages/SystemUI/aconfig/predictive_back.aconfig b/packages/SystemUI/aconfig/predictive_back.aconfig index 89a0d895a17c..dd9a8b19f498 100644 --- a/packages/SystemUI/aconfig/predictive_back.aconfig +++ b/packages/SystemUI/aconfig/predictive_back.aconfig @@ -9,8 +9,11 @@ flag { } flag { - name: "predictive_back_delay_transition" + name: "predictive_back_delay_wm_transition" namespace: "systemui" description: "Slightly delays the back transition start" bug: "301195601" + metadata { + purpose: PURPOSE_BUGFIX + } } diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 9db4346a08b7..9dd49d6b1015 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -292,6 +292,17 @@ flag { } flag { + name: "notification_row_accessibility_expanded" + namespace: "systemui" + description: "Prepare ExpandableNotificationRow for new A11y expansion APIs." + bug: "380027122" + metadata { + purpose: PURPOSE_BUGFIX + } +} + + +flag { name: "scene_container" namespace: "systemui" description: "Enables the scene container framework go/flexiglass." @@ -1390,6 +1401,16 @@ flag { } flag { + name: "media_controls_device_manager_background_execution" + namespace: "systemui" + description: "Sends some instances creation to background thread" + bug: "400200474" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "output_switcher_redesign" namespace: "systemui" description: "Enables visual update for Media Output Switcher" @@ -1832,6 +1853,16 @@ flag { } flag { + name: "disable_shade_visible_with_blur" + namespace: "systemui" + description: "Removes the check for a blur radius when determining shade window visibility" + bug: "356804470" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "notification_row_transparency" namespace: "systemui" description: "Enables transparency on the Notification Shade." @@ -2110,3 +2141,10 @@ flag { description: "Enables moving the launching window on top of the origin window in the Animation library." bug: "390422470" } + +flag { + name: "status_bar_chips_return_animations" + namespace: "systemui" + description: "Enables return animations for status bar chips" + bug: "202516970" +} diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/FontVariationUtils.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/FontVariationUtils.kt index 9a746870c6ff..e07d7b337ba2 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/FontVariationUtils.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/FontVariationUtils.kt @@ -16,17 +16,18 @@ package com.android.systemui.animation +import kotlin.text.buildString + class FontVariationUtils { private var mWeight = -1 private var mWidth = -1 private var mOpticalSize = -1 private var mRoundness = -1 - private var isUpdated = false + private var mCurrentFVar = "" /* * generate fontVariationSettings string, used for key in typefaceCache in TextAnimator * the order of axes should align to the order of parameters - * if every axis remains unchanged, return "" */ fun updateFontVariation( weight: Int = -1, @@ -34,15 +35,17 @@ class FontVariationUtils { opticalSize: Int = -1, roundness: Int = -1, ): String { - isUpdated = false + var isUpdated = false if (weight >= 0 && mWeight != weight) { isUpdated = true mWeight = weight } + if (width >= 0 && mWidth != width) { isUpdated = true mWidth = width } + if (opticalSize >= 0 && mOpticalSize != opticalSize) { isUpdated = true mOpticalSize = opticalSize @@ -52,23 +55,32 @@ class FontVariationUtils { isUpdated = true mRoundness = roundness } - var resultString = "" - if (mWeight >= 0) { - resultString += "'${GSFAxes.WEIGHT.tag}' $mWeight" - } - if (mWidth >= 0) { - resultString += - (if (resultString.isBlank()) "" else ", ") + "'${GSFAxes.WIDTH.tag}' $mWidth" - } - if (mOpticalSize >= 0) { - resultString += - (if (resultString.isBlank()) "" else ", ") + - "'${GSFAxes.OPTICAL_SIZE.tag}' $mOpticalSize" - } - if (mRoundness >= 0) { - resultString += - (if (resultString.isBlank()) "" else ", ") + "'${GSFAxes.ROUND.tag}' $mRoundness" + + if (!isUpdated) { + return mCurrentFVar } - return if (isUpdated) resultString else "" + + return buildString { + if (mWeight >= 0) { + if (!isBlank()) append(", ") + append("'${GSFAxes.WEIGHT.tag}' $mWeight") + } + + if (mWidth >= 0) { + if (!isBlank()) append(", ") + append("'${GSFAxes.WIDTH.tag}' $mWidth") + } + + if (mOpticalSize >= 0) { + if (!isBlank()) append(", ") + append("'${GSFAxes.OPTICAL_SIZE.tag}' $mOpticalSize") + } + + if (mRoundness >= 0) { + if (!isBlank()) append(", ") + append("'${GSFAxes.ROUND.tag}' $mRoundness") + } + } + .also { mCurrentFVar = it } } } diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GSFAxes.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GSFAxes.kt index 96feeedb8793..e734dd26eb15 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/GSFAxes.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GSFAxes.kt @@ -25,6 +25,7 @@ data class AxisDefinition( ) object GSFAxes { + @JvmStatic val WEIGHT = AxisDefinition( tag = "wght", diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt index 5b073e49192a..4a39cff388a9 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt @@ -39,6 +39,7 @@ interface TypefaceVariantCache { fun getTypefaceForVariant(fvar: String?): Typeface? companion object { + @JvmStatic fun createVariantTypeface(baseTypeface: Typeface, fVar: String?): Typeface { if (fVar.isNullOrEmpty()) { return baseTypeface diff --git a/packages/SystemUI/compose/core/src/com/android/compose/ui/graphics/DrawInContainer.kt b/packages/SystemUI/compose/core/src/com/android/compose/ui/graphics/DrawInContainer.kt index d08d859ec0d7..fc4d53af4b53 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/ui/graphics/DrawInContainer.kt +++ b/packages/SystemUI/compose/core/src/com/android/compose/ui/graphics/DrawInContainer.kt @@ -22,7 +22,6 @@ import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.drawWithContent import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.drawscope.ContentDrawScope @@ -32,7 +31,6 @@ import androidx.compose.ui.graphics.drawscope.translate import androidx.compose.ui.graphics.layer.GraphicsLayer import androidx.compose.ui.graphics.layer.drawLayer import androidx.compose.ui.layout.LayoutCoordinates -import androidx.compose.ui.layout.onPlaced import androidx.compose.ui.layout.positionInWindow import androidx.compose.ui.modifier.ModifierLocalModifierNode import androidx.compose.ui.node.DrawModifierNode @@ -50,11 +48,7 @@ import androidx.compose.ui.util.fastForEach * The elements redirected to this container will be drawn above the content of this composable. */ fun Modifier.container(state: ContainerState): Modifier { - return onPlaced { state.lastOffsetInWindow = it.positionInWindow() } - .drawWithContent { - drawContent() - state.drawInOverlay(this) - } + return this then ContainerElement(state) } /** @@ -105,6 +99,30 @@ internal interface LayerRenderer { fun drawInOverlay(drawScope: DrawScope) } +private data class ContainerElement(private val state: ContainerState) : + ModifierNodeElement<ContainerNode>() { + override fun create(): ContainerNode { + return ContainerNode(state) + } + + override fun update(node: ContainerNode) { + node.state = state + } +} + +/** A node implementing [container] that can be delegated to. */ +class ContainerNode(var state: ContainerState) : + Modifier.Node(), LayoutAwareModifierNode, DrawModifierNode { + override fun onPlaced(coordinates: LayoutCoordinates) { + state.lastOffsetInWindow = coordinates.positionInWindow() + } + + override fun ContentDrawScope.draw() { + drawContent() + state.drawInOverlay(this) + } +} + private data class DrawInContainerElement( var state: ContainerState, var enabled: () -> Boolean, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt index 061fdd99eb1b..0a711487ccb1 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt @@ -356,7 +356,8 @@ private fun ContentScope.QuickSettingsScene( modifier = Modifier.padding(horizontal = 16.dp), ) } - else -> CollapsedShadeHeader(viewModel = headerViewModel) + else -> + CollapsedShadeHeader(viewModel = headerViewModel, isSplitShade = false) } Spacer(modifier = Modifier.height(16.dp)) // This view has its own horizontal padding diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt index a0216268308c..8f0fb20cef36 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt @@ -52,6 +52,7 @@ import com.android.compose.animation.scene.UserActionResult import com.android.compose.animation.scene.content.state.TransitionState import com.android.compose.modifiers.thenIf import com.android.systemui.brightness.ui.compose.BrightnessSliderContainer +import com.android.systemui.brightness.ui.compose.ContainerColors import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.dagger.SysUISingleton import com.android.systemui.lifecycle.rememberViewModel @@ -257,15 +258,18 @@ fun ContentScope.QuickSettingsLayout( modifier = Modifier.padding(horizontal = QuickSettingsShade.Dimensions.Padding), ) - BrightnessSliderContainer( - viewModel = viewModel.brightnessSliderViewModel, - containerColor = OverlayShade.Colors.PanelBackground, - modifier = - Modifier.systemGestureExclusionInShade( - enabled = { layoutState.transitionState is TransitionState.Idle } - ) - .fillMaxWidth(), - ) + Box( + Modifier.systemGestureExclusionInShade( + enabled = { layoutState.transitionState is TransitionState.Idle } + ) + ) { + BrightnessSliderContainer( + viewModel = viewModel.brightnessSliderViewModel, + containerColors = + ContainerColors.singleColor(OverlayShade.Colors.PanelBackground), + modifier = Modifier.fillMaxWidth(), + ) + } Box { GridAnchor() diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt index 23baeacd76ec..86c8fc34a63c 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt @@ -127,6 +127,7 @@ object ShadeHeader { @Composable fun ContentScope.CollapsedShadeHeader( viewModel: ShadeHeaderViewModel, + isSplitShade: Boolean, modifier: Modifier = Modifier, ) { val cutoutLocation = LocalDisplayCutout.current.location @@ -141,8 +142,6 @@ fun ContentScope.CollapsedShadeHeader( } } - val isShadeLayoutWide = viewModel.isShadeLayoutWide - val isPrivacyChipVisible by viewModel.isPrivacyChipVisible.collectAsStateWithLifecycle() // This layout assumes it is globally positioned at (0, 0) and is the same size as the screen. @@ -154,7 +153,7 @@ fun ContentScope.CollapsedShadeHeader( horizontalArrangement = Arrangement.spacedBy(5.dp), modifier = Modifier.padding(horizontal = horizontalPadding), ) { - Clock(scale = 1f, onClick = viewModel::onClockClicked) + Clock(onClick = viewModel::onClockClicked) VariableDayDate( longerDateText = viewModel.longerDateText, shorterDateText = viewModel.shorterDateText, @@ -184,11 +183,11 @@ fun ContentScope.CollapsedShadeHeader( Modifier.element(ShadeHeader.Elements.CollapsedContentEnd) .padding(horizontal = horizontalPadding), ) { - if (isShadeLayoutWide) { + if (isSplitShade) { ShadeCarrierGroup(viewModel = viewModel) } SystemIconChip( - onClick = viewModel::onSystemIconChipClicked.takeIf { isShadeLayoutWide } + onClick = viewModel::onSystemIconChipClicked.takeIf { isSplitShade } ) { StatusIcons( viewModel = viewModel, @@ -233,13 +232,11 @@ fun ContentScope.ExpandedShadeHeader( .defaultMinSize(minHeight = ShadeHeader.Dimensions.ExpandedHeight), ) { Box(modifier = Modifier.fillMaxWidth()) { - Box { - Clock( - scale = 2.57f, - onClick = viewModel::onClockClicked, - modifier = Modifier.align(Alignment.CenterStart), - ) - } + Clock( + onClick = viewModel::onClockClicked, + modifier = Modifier.align(Alignment.CenterStart), + scale = 2.57f, + ) Box( modifier = Modifier.element(ShadeHeader.Elements.ShadeCarrierGroup).fillMaxWidth() @@ -291,8 +288,6 @@ fun ContentScope.OverlayShadeHeader( val horizontalPadding = max(LocalScreenCornerRadius.current / 2f, Shade.Dimensions.HorizontalPadding) - val isShadeLayoutWide = viewModel.isShadeLayoutWide - val isPrivacyChipVisible by viewModel.isPrivacyChipVisible.collectAsStateWithLifecycle() // This layout assumes it is globally positioned at (0, 0) and is the same size as the screen. @@ -301,16 +296,15 @@ fun ContentScope.OverlayShadeHeader( startContent = { Row( verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(5.dp), modifier = Modifier.padding(horizontal = horizontalPadding), ) { val chipHighlight = viewModel.notificationsChipHighlight - if (isShadeLayoutWide) { + if (viewModel.showClock) { Clock( - scale = 1f, onClick = viewModel::onClockClicked, modifier = Modifier.padding(horizontal = 4.dp), ) - Spacer(modifier = Modifier.width(5.dp)) } NotificationsChip( onClick = viewModel::onNotificationIconChipClicked, @@ -437,7 +431,11 @@ private fun CutoutAwareShadeHeader( } @Composable -private fun ContentScope.Clock(scale: Float, onClick: () -> Unit, modifier: Modifier = Modifier) { +private fun ContentScope.Clock( + onClick: () -> Unit, + modifier: Modifier = Modifier, + scale: Float = 1f, +) { val layoutDirection = LocalLayoutDirection.current ElementWithValues(key = ShadeHeader.Elements.Clock, modifier = modifier) { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt index 5040490da8f6..885d34fb95c9 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt @@ -56,11 +56,11 @@ import androidx.compose.ui.layout.Layout import androidx.compose.ui.layout.layoutId import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex +import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.scene.ContentScope import com.android.compose.animation.scene.ElementKey @@ -68,6 +68,7 @@ import com.android.compose.animation.scene.LowestZIndexContentPicker import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult import com.android.compose.animation.scene.animateContentDpAsState +import com.android.compose.animation.scene.animateContentFloatAsState import com.android.compose.animation.scene.animateSceneFloatAsState import com.android.compose.animation.scene.content.state.TransitionState import com.android.compose.modifiers.padding @@ -223,9 +224,6 @@ private fun ContentScope.ShadeScene( viewModel = viewModel, headerViewModel = headerViewModel, notificationsPlaceholderViewModel = notificationsPlaceholderViewModel, - createTintedIconManager = createTintedIconManager, - createBatteryMeterViewController = createBatteryMeterViewController, - statusBarIconController = statusBarIconController, mediaCarouselController = mediaCarouselController, mediaHost = qqsMediaHost, modifier = modifier, @@ -253,9 +251,6 @@ private fun ContentScope.SingleShade( viewModel: ShadeSceneContentViewModel, headerViewModel: ShadeHeaderViewModel, notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel, - createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager, - createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController, - statusBarIconController: StatusBarIconController, mediaCarouselController: MediaCarouselController, mediaHost: MediaHost, modifier: Modifier = Modifier, @@ -340,6 +335,7 @@ private fun ContentScope.SingleShade( content = { CollapsedShadeHeader( viewModel = headerViewModel, + isSplitShade = false, modifier = Modifier.layoutId(SingleShadeMeasurePolicy.LayoutId.ShadeHeader), ) @@ -434,15 +430,13 @@ private fun ContentScope.SplitShade( val footerActionsViewModel = remember(lifecycleOwner, viewModel) { viewModel.getFooterActionsViewModel(lifecycleOwner) } val tileSquishiness by - animateSceneFloatAsState( + animateContentFloatAsState( value = 1f, key = QuickSettings.SharedValues.TilesSquishiness, canOverflow = false, ) val unfoldTranslationXForStartSide by viewModel.unfoldTranslationX(isOnStartSide = true).collectAsStateWithLifecycle(0f) - val unfoldTranslationXForEndSide by - viewModel.unfoldTranslationX(isOnStartSide = false).collectAsStateWithLifecycle(0f) val notificationStackPadding = dimensionResource(id = R.dimen.notification_side_paddings) val navBarBottomHeight = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding() @@ -512,6 +506,7 @@ private fun ContentScope.SplitShade( Column(modifier = Modifier.fillMaxSize()) { CollapsedShadeHeader( viewModel = headerViewModel, + isSplitShade = true, modifier = Modifier.then(brightnessMirrorShowingModifier) .padding(horizontal = { unfoldTranslationXForStartSide.roundToInt() }), diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt index 72ee75ad2d47..90bf92ae1dd0 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt @@ -32,12 +32,17 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ApproachLayoutModifierNode +import androidx.compose.ui.layout.ApproachMeasureScope import androidx.compose.ui.layout.LookaheadScope -import androidx.compose.ui.layout.approachLayout -import androidx.compose.ui.layout.layout +import androidx.compose.ui.layout.Measurable +import androidx.compose.ui.layout.MeasureResult +import androidx.compose.ui.layout.MeasureScope +import androidx.compose.ui.node.DelegatingNode +import androidx.compose.ui.node.ModifierNodeElement import androidx.compose.ui.platform.testTag +import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.IntSize -import androidx.compose.ui.zIndex import com.android.compose.animation.scene.Ancestor import com.android.compose.animation.scene.AnimatedState import com.android.compose.animation.scene.ContentKey @@ -68,8 +73,8 @@ import com.android.compose.gesture.NestedScrollControlState import com.android.compose.gesture.NestedScrollableBound import com.android.compose.gesture.nestedScrollController import com.android.compose.modifiers.thenIf +import com.android.compose.ui.graphics.ContainerNode import com.android.compose.ui.graphics.ContainerState -import com.android.compose.ui.graphics.container import kotlin.math.pow /** A content defined in a [SceneTransitionLayout], i.e. a scene or an overlay. */ @@ -158,24 +163,14 @@ internal sealed class Content( fun Content(modifier: Modifier = Modifier, isInvisible: Boolean = false) { // If this content has a custom factory, provide it to the content so that the factory is // automatically used when calling rememberOverscrollEffect(). + val isElevationPossible = + layoutImpl.state.isElevationPossible(content = key, element = null) Box( - modifier - .thenIf(isInvisible) { InvisibleModifier } - .zIndex(zIndex) - .approachLayout( - isMeasurementApproachInProgress = { layoutImpl.state.isTransitioning() } - ) { measurable, constraints -> - // TODO(b/353679003): Use the ModifierNode API to set this *before* the - // approach - // pass is started. - targetSize = lookaheadSize - val placeable = measurable.measure(constraints) - layout(placeable.width, placeable.height) { placeable.place(0, 0) } - } - .thenIf(layoutImpl.state.isElevationPossible(content = key, element = null)) { - Modifier.container(containerState) - } - .thenIf(layoutImpl.implicitTestTags) { Modifier.testTag(key.testTag) } + modifier.then(ContentElement(this, isElevationPossible, isInvisible)).thenIf( + layoutImpl.implicitTestTags + ) { + Modifier.testTag(key.testTag) + } ) { CompositionLocalProvider(LocalOverscrollFactory provides lastFactory) { scope.content() @@ -194,6 +189,72 @@ internal sealed class Content( } } +private data class ContentElement( + private val content: Content, + private val isElevationPossible: Boolean, + private val isInvisible: Boolean, +) : ModifierNodeElement<ContentNode>() { + override fun create(): ContentNode = ContentNode(content, isElevationPossible, isInvisible) + + override fun update(node: ContentNode) { + node.update(content, isElevationPossible, isInvisible) + } +} + +private class ContentNode( + private var content: Content, + private var isElevationPossible: Boolean, + private var isInvisible: Boolean, +) : DelegatingNode(), ApproachLayoutModifierNode { + private var containerDelegate = containerDelegate(isElevationPossible) + + private fun containerDelegate(isElevationPossible: Boolean): ContainerNode? { + return if (isElevationPossible) delegate(ContainerNode(content.containerState)) else null + } + + fun update(content: Content, isElevationPossible: Boolean, isInvisible: Boolean) { + if (content != this.content || isElevationPossible != this.isElevationPossible) { + this.content = content + this.isElevationPossible = isElevationPossible + + containerDelegate?.let { undelegate(it) } + containerDelegate = containerDelegate(isElevationPossible) + } + + this.isInvisible = isInvisible + } + + override fun isMeasurementApproachInProgress(lookaheadSize: IntSize): Boolean = false + + override fun MeasureScope.measure( + measurable: Measurable, + constraints: Constraints, + ): MeasureResult { + check(isLookingAhead) + return measurable.measure(constraints).run { + content.targetSize = IntSize(width, height) + layout(width, height) { + if (!isInvisible) { + place(0, 0, zIndex = content.zIndex) + } + } + } + } + + override fun ApproachMeasureScope.approachMeasure( + measurable: Measurable, + constraints: Constraints, + ): MeasureResult { + return measurable.measure(constraints).run { + layout(width, height) { + if (!isInvisible) { + place(0, 0, zIndex = content.zIndex) + } + } + } + } +} + internal class ContentEffects(factory: OverscrollFactory) { val overscrollEffect = factory.createOverscrollEffect() val gestureEffect = GestureEffect(overscrollEffect) @@ -307,8 +368,3 @@ internal class ContentScopeImpl( ) } } - -private val InvisibleModifier = - Modifier.layout { measurable, constraints -> - measurable.measure(constraints).run { layout(width, height) {} } - } diff --git a/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_dragFullyClose.json b/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_dragFullyClose.json index 0fcdfa3e1b53..57f67665242c 100644 --- a/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_dragFullyClose.json +++ b/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_dragFullyClose.json @@ -60,7 +60,9 @@ 912, 928, 944, - 960 + 960, + 976, + 992 ], "features": [ { @@ -310,6 +312,14 @@ { "x": 64, "y": 50 + }, + { + "x": 64, + "y": 50 + }, + { + "x": 64, + "y": 50 } ] }, @@ -491,67 +501,75 @@ }, { "width": 170.4, - "height": 39.2 + "height": 28.8 }, { "width": 166.8, - "height": 36 + "height": 15.2 }, { "width": 164, - "height": 31.6 + "height": 6.4 }, { "width": 162.4, - "height": 26.8 + "height": 0.8 }, { "width": 161.2, - "height": 22 + "height": 0 }, { "width": 160.4, - "height": 17.6 + "height": 0 + }, + { + "width": 160, + "height": 0 + }, + { + "width": 160, + "height": 0 }, { "width": 160, - "height": 14 + "height": 0 }, { "width": 160, - "height": 10.8 + "height": 0 }, { "width": 160, - "height": 8 + "height": 0 }, { "width": 160, - "height": 6 + "height": 0 }, { "width": 160, - "height": 4.4 + "height": 0 }, { "width": 160, - "height": 2.8 + "height": 0 }, { "width": 160, - "height": 2 + "height": 0 }, { "width": 160, - "height": 1.2 + "height": 0 }, { "width": 160, - "height": 0.8 + "height": 0 }, { "width": 160, - "height": 0.4 + "height": 0 }, { "width": 160, @@ -627,6 +645,8 @@ 0, 0, 0, + 0, + 0, 0 ] } diff --git a/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_dragHalfClose.json b/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_dragHalfClose.json index 3196334c5314..01bc852cf7f4 100644 --- a/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_dragHalfClose.json +++ b/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_dragHalfClose.json @@ -59,7 +59,9 @@ 896, 912, 928, - 944 + 944, + 960, + 976 ], "features": [ { @@ -305,6 +307,14 @@ { "x": 64, "y": 50 + }, + { + "x": 64, + "y": 50 + }, + { + "x": 64, + "y": 50 } ] }, @@ -482,71 +492,79 @@ }, { "width": 171.6, - "height": 39.6 + "height": 32 }, { "width": 167.6, - "height": 36.8 + "height": 18 }, { "width": 164.8, - "height": 32.4 + "height": 8.8 }, { "width": 162.8, - "height": 27.6 + "height": 2.8 }, { "width": 161.6, - "height": 22.8 + "height": 0 }, { "width": 160.8, - "height": 18.4 + "height": 0 }, { "width": 160.4, - "height": 14.4 + "height": 0 }, { "width": 160, - "height": 11.2 + "height": 0 }, { "width": 160, - "height": 8.4 + "height": 0 }, { "width": 160, - "height": 6.4 + "height": 0 }, { "width": 160, - "height": 4.4 + "height": 0 }, { "width": 160, - "height": 3.2 + "height": 0 }, { "width": 160, - "height": 2 + "height": 0 }, { "width": 160, - "height": 1.6 + "height": 0 }, { "width": 160, - "height": 0.8 + "height": 0 }, { "width": 160, - "height": 0.4 + "height": 0 }, { "width": 160, - "height": 0.4 + "height": 0 + }, + { + "width": 160, + "height": 0 + }, + { + "width": 160, + "height": 0 }, { "width": 160, @@ -617,6 +635,8 @@ 0, 0, 0, + 0, + 0, 0 ] } diff --git a/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_dragOpen.json b/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_dragOpen.json index 4b0306853903..b6e423afc6c4 100644 --- a/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_dragOpen.json +++ b/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_dragOpen.json @@ -336,55 +336,55 @@ }, { "width": 188, - "height": 24.8 + "height": 25.6 }, { "width": 188, - "height": 32.8 + "height": 36.4 }, { "width": 188, - "height": 44 + "height": 45.6 }, { "width": 188, - "height": 57.2 + "height": 59.2 }, { "width": 188, - "height": 70.8 + "height": 72.8 }, { "width": 188, - "height": 78 + "height": 79.6 }, { "width": 188, - "height": 91.2 + "height": 92.8 }, { "width": 188, - "height": 103.2 + "height": 104.4 }, { "width": 188, - "height": 114.4 + "height": 115.2 }, { "width": 188, - "height": 124.4 + "height": 125.2 }, { "width": 188, - "height": 134 + "height": 134.8 }, { "width": 188, - "height": 142.8 + "height": 143.2 }, { "width": 188, - "height": 150.8 + "height": 151.2 }, { "width": 188, @@ -392,7 +392,7 @@ }, { "width": 188, - "height": 159.6 + "height": 160 }, { "width": 188, @@ -400,7 +400,7 @@ }, { "width": 188, - "height": 174 + "height": 174.4 }, { "width": 188, @@ -412,7 +412,7 @@ }, { "width": 188, - "height": 187.6 + "height": 188 }, { "width": 188, diff --git a/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_flingClose.json b/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_flingClose.json index 10a9ba7e2760..a82db346ed58 100644 --- a/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_flingClose.json +++ b/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_flingClose.json @@ -39,7 +39,9 @@ 576, 592, 608, - 624 + 624, + 640, + 656 ], "features": [ { @@ -205,6 +207,14 @@ { "x": 64, "y": 50 + }, + { + "x": 64, + "y": 50 + }, + { + "x": 64, + "y": 50 } ] }, @@ -302,67 +312,75 @@ }, { "width": 170.8, - "height": 39.6 + "height": 29.6 }, { "width": 166.8, - "height": 36.8 + "height": 12.8 }, { "width": 164, - "height": 32.4 + "height": 2.4 }, { "width": 162, - "height": 27.6 + "height": 0 }, { "width": 160.8, - "height": 22.8 + "height": 0 }, { "width": 160.4, - "height": 18.4 + "height": 0 }, { "width": 160, - "height": 14.4 + "height": 0 }, { "width": 160, - "height": 11.2 + "height": 0 }, { "width": 160, - "height": 8.4 + "height": 0 }, { "width": 160, - "height": 6 + "height": 0 }, { "width": 160, - "height": 4.4 + "height": 0 }, { "width": 160, - "height": 3.2 + "height": 0 }, { "width": 160, - "height": 2 + "height": 0 }, { "width": 160, - "height": 1.2 + "height": 0 }, { "width": 160, - "height": 0.8 + "height": 0 }, { "width": 160, - "height": 0.4 + "height": 0 + }, + { + "width": 160, + "height": 0 + }, + { + "width": 160, + "height": 0 }, { "width": 160, @@ -417,6 +435,8 @@ 0, 0, 0, + 0, + 0, 0 ] } diff --git a/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_flingOpen.json b/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_flingOpen.json index d8bf48d32d20..6dc5a0e79e81 100644 --- a/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_flingOpen.json +++ b/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_flingOpen.json @@ -32,8 +32,7 @@ 464, 480, 496, - 512, - 528 + 512 ], "features": [ { @@ -163,10 +162,6 @@ { "x": 50, "y": 50 - }, - { - "x": 50, - "y": 50 } ] }, @@ -228,63 +223,59 @@ }, { "width": 188, - "height": 42.4 + "height": 44.4 }, { "width": 188, - "height": 100 + "height": 103.6 }, { "width": 188, - "height": 161.6 + "height": 166 }, { "width": 188, - "height": 218 + "height": 222.4 }, { "width": 188, - "height": 265.6 + "height": 270 }, { "width": 188, - "height": 303.6 + "height": 307.2 }, { "width": 188, - "height": 332.4 + "height": 335.6 }, { "width": 188, - "height": 354 + "height": 356.4 }, { "width": 188, - "height": 369.2 + "height": 371.2 }, { "width": 188, - "height": 380 + "height": 381.6 }, { "width": 188, - "height": 387.2 + "height": 388.8 }, { "width": 188, - "height": 392 + "height": 393.2 }, { "width": 188, - "height": 395.2 + "height": 396 }, { "width": 188, - "height": 397.6 - }, - { - "width": 188, - "height": 398.4 + "height": 398 }, { "width": 188, @@ -356,7 +347,6 @@ 1, 1, 1, - 1, 1 ] } diff --git a/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_magneticDetachAndReattach.json b/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_magneticDetachAndReattach.json index 57bdf3e1ecab..1cd971aa2898 100644 --- a/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_magneticDetachAndReattach.json +++ b/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_gesture_magneticDetachAndReattach.json @@ -69,9 +69,7 @@ 1056, 1072, 1088, - 1104, - 1120, - 1136 + 1104 ], "features": [ { @@ -353,14 +351,6 @@ { "x": 64, "y": 50 - }, - { - "x": 64, - "y": 50 - }, - { - "x": 64, - "y": 50 } ] }, @@ -466,43 +456,43 @@ }, { "width": 188, - "height": 24 + "height": 24.8 }, { "width": 188, - "height": 29.6 + "height": 30 }, { "width": 188, - "height": 37.2 + "height": 38 }, { "width": 188, - "height": 45.6 + "height": 46 }, { "width": 188, - "height": 53.6 + "height": 54 }, { "width": 188, - "height": 60.4 + "height": 61.2 }, { "width": 188, - "height": 66.4 + "height": 66.8 }, { "width": 188, - "height": 71.2 + "height": 71.6 }, { "width": 188, - "height": 75.2 + "height": 75.6 }, { "width": 188, - "height": 77.6 + "height": 78 }, { "width": 188, @@ -510,7 +500,7 @@ }, { "width": 188, - "height": 80.4 + "height": 80.8 }, { "width": 188, @@ -522,7 +512,7 @@ }, { "width": 188, - "height": 79.2 + "height": 79.6 }, { "width": 187.6, @@ -530,7 +520,7 @@ }, { "width": 186.8, - "height": 76 + "height": 76.4 }, { "width": 186, @@ -578,43 +568,39 @@ }, { "width": 172.4, - "height": 39.2 + "height": 37.6 }, { "width": 170.8, - "height": 38.4 + "height": 38 }, { "width": 169.2, - "height": 34.8 + "height": 30.4 }, { "width": 167.6, - "height": 30 + "height": 25.2 }, { "width": 166, - "height": 25.2 + "height": 20.4 }, { "width": 164, - "height": 20.4 + "height": 16 }, { "width": 162.4, - "height": 16.4 + "height": 12.4 }, { "width": 160.8, - "height": 12.8 - }, - { - "width": 160, - "height": 9.6 + "height": 9.2 }, { "width": 160, - "height": 7.2 + "height": 6.8 }, { "width": 160, @@ -626,7 +612,7 @@ }, { "width": 160, - "height": 2.8 + "height": 2.4 }, { "width": 160, @@ -634,10 +620,6 @@ }, { "width": 160, - "height": 1.2 - }, - { - "width": 160, "height": 0.8 }, { @@ -646,7 +628,7 @@ }, { "width": 160, - "height": 0 + "height": 0.4 }, { "width": 160, @@ -735,8 +717,6 @@ 0.03147719, 0.019312752, 0.011740655, - 0, - 0, 0 ] } diff --git a/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_triggeredRevealCloseTransition.json b/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_triggeredRevealCloseTransition.json index 9aa91c2d5e17..1030455e873f 100644 --- a/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_triggeredRevealCloseTransition.json +++ b/packages/SystemUI/compose/scene/tests/goldens/verticalReveal_triggeredRevealCloseTransition.json @@ -27,7 +27,9 @@ 384, 400, 416, - 432 + 432, + 448, + 464 ], "features": [ { @@ -145,6 +147,14 @@ { "x": 64, "y": 50 + }, + { + "x": 64, + "y": 50 + }, + { + "x": 64, + "y": 50 } ] }, @@ -194,67 +204,75 @@ }, { "width": 168.8, - "height": 38.4 + "height": 22.4 }, { "width": 165.2, - "height": 34.8 + "height": 10 }, { "width": 162.8, - "height": 30 + "height": 2.4 }, { "width": 161.2, - "height": 25.2 + "height": 0 }, { "width": 160.4, - "height": 20.4 + "height": 0 }, { "width": 160, - "height": 16.4 + "height": 0 }, { "width": 160, - "height": 12.8 + "height": 0 }, { "width": 160, - "height": 9.6 + "height": 0 }, { "width": 160, - "height": 7.2 + "height": 0 }, { "width": 160, - "height": 5.2 + "height": 0 }, { "width": 160, - "height": 3.6 + "height": 0 }, { "width": 160, - "height": 2.8 + "height": 0 }, { "width": 160, - "height": 1.6 + "height": 0 }, { "width": 160, - "height": 1.2 + "height": 0 }, { "width": 160, - "height": 0.8 + "height": 0 }, { "width": 160, - "height": 0.4 + "height": 0 + }, + { + "width": 160, + "height": 0 + }, + { + "width": 160, + "height": 0 }, { "width": 160, @@ -297,6 +315,8 @@ 0, 0, 0, + 0, + 0, 0 ] } diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/subjects/TransitionStateSubject.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/subjects/TransitionStateSubject.kt index 0bd51cd9822d..44f2353dcb75 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/subjects/TransitionStateSubject.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/subjects/TransitionStateSubject.kt @@ -111,8 +111,8 @@ private constructor(metadata: FailureMetadata, private val actual: TransitionSta } companion object { - fun transitionStates() = Factory { metadata, actual: TransitionState -> - TransitionStateSubject(metadata, actual) + fun transitionStates() = Factory { metadata, actual: TransitionState? -> + TransitionStateSubject(metadata, actual!!) } } } @@ -181,8 +181,8 @@ private constructor(metadata: FailureMetadata, actual: TransitionState.Transitio companion object { fun sceneTransitions() = - Factory { metadata, actual: TransitionState.Transition.ChangeScene -> - SceneTransitionSubject(metadata, actual) + Factory { metadata, actual: TransitionState.Transition.ChangeScene? -> + SceneTransitionSubject(metadata, actual!!) } } } @@ -202,8 +202,8 @@ private constructor( companion object { fun showOrHideOverlayTransitions() = - Factory { metadata, actual: TransitionState.Transition.ShowOrHideOverlay -> - ShowOrHideOverlayTransitionSubject(metadata, actual) + Factory { metadata, actual: TransitionState.Transition.ShowOrHideOverlay? -> + ShowOrHideOverlayTransitionSubject(metadata, actual!!) } } } @@ -221,8 +221,8 @@ private constructor(metadata: FailureMetadata, actual: TransitionState.Transitio companion object { fun replaceOverlayTransitions() = - Factory { metadata, actual: TransitionState.Transition.ReplaceOverlay -> - ReplaceOverlayTransitionSubject(metadata, actual) + Factory { metadata, actual: TransitionState.Transition.ReplaceOverlay? -> + ReplaceOverlayTransitionSubject(metadata, actual!!) } } } diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/subjects/DpOffsetSubject.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/subjects/DpOffsetSubject.kt index ab31038fac8f..368a333fbd78 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/subjects/DpOffsetSubject.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/subjects/DpOffsetSubject.kt @@ -49,8 +49,8 @@ class DpOffsetSubject(metadata: FailureMetadata, private val actual: DpOffset) : val DefaultTolerance = Dp(.5f) fun dpOffsets() = - Factory<DpOffsetSubject, DpOffset> { metadata, actual -> - DpOffsetSubject(metadata, actual) + Factory { metadata, actual: DpOffset? -> + DpOffsetSubject(metadata, actual!!) } } } diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt index 4bf0ceb51784..6e29e6932629 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt @@ -38,8 +38,9 @@ import com.android.systemui.animation.TypefaceVariantCacheImpl import com.android.systemui.customization.R import com.android.systemui.log.core.LogLevel import com.android.systemui.log.core.LogcatOnlyMessageBuffer -import com.android.systemui.log.core.Logger import com.android.systemui.log.core.MessageBuffer +import com.android.systemui.plugins.clocks.ClockLogger +import com.android.systemui.plugins.clocks.ClockLogger.Companion.escapeTime import java.io.PrintWriter import java.util.Calendar import java.util.Locale @@ -67,7 +68,7 @@ constructor( var messageBuffer: MessageBuffer get() = logger.buffer set(value) { - logger = Logger(value, TAG) + logger = ClockLogger(this, value, TAG) } var hasCustomPositionUpdatedAnimation: Boolean = false @@ -185,7 +186,9 @@ constructor( time.timeInMillis = timeOverrideInMillis ?: System.currentTimeMillis() contentDescription = DateFormat.format(descFormat, time) val formattedText = DateFormat.format(format, time) - logger.d({ "refreshTime: new formattedText=$str1" }) { str1 = formattedText?.toString() } + logger.d({ "refreshTime: new formattedText=${escapeTime(str1)}" }) { + str1 = formattedText?.toString() + } // Setting text actually triggers a layout pass in TextView (because the text view is set to // wrap_content width and TextView always relayouts for this). This avoids needless relayout @@ -195,7 +198,7 @@ constructor( } text = formattedText - logger.d({ "refreshTime: done setting new time text to: $str1" }) { + logger.d({ "refreshTime: done setting new time text to: ${escapeTime(str1)}" }) { str1 = formattedText?.toString() } @@ -225,7 +228,7 @@ constructor( @SuppressLint("DrawAllocation") override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { - logger.d("onMeasure") + logger.onMeasure(widthMeasureSpec, heightMeasureSpec) if (!isSingleLineInternal && MeasureSpec.getMode(heightMeasureSpec) == EXACTLY) { // Call straight into TextView.setTextSize to avoid setting lastUnconstrainedTextSize @@ -263,14 +266,14 @@ constructor( canvas.translate(parentWidth / 4f, 0f) } - logger.d({ "onDraw($str1)" }) { str1 = text.toString() } + logger.onDraw("$text") // intentionally doesn't call super.onDraw here or else the text will be rendered twice textAnimator?.draw(canvas) canvas.restore() } override fun invalidate() { - logger.d("invalidate") + logger.invalidate() super.invalidate() } @@ -280,7 +283,7 @@ constructor( lengthBefore: Int, lengthAfter: Int, ) { - logger.d({ "onTextChanged($str1)" }) { str1 = text.toString() } + logger.d({ "onTextChanged(${escapeTime(str1)})" }) { str1 = "$text" } super.onTextChanged(text, start, lengthBefore, lengthAfter) } @@ -370,7 +373,7 @@ constructor( return } - logger.d("animateCharge") + logger.animateCharge() val startAnimPhase2 = Runnable { setTextStyle( weight = if (isDozing()) dozingWeight else lockScreenWeight, @@ -394,7 +397,7 @@ constructor( } fun animateDoze(isDozing: Boolean, animate: Boolean) { - logger.d("animateDoze") + logger.animateDoze(isDozing, animate) setTextStyle( weight = if (isDozing) dozingWeight else lockScreenWeight, color = if (isDozing) dozingColor else lockScreenColor, @@ -484,7 +487,7 @@ constructor( isSingleLineInternal && !use24HourFormat -> Patterns.sClockView12 else -> DOUBLE_LINE_FORMAT_12_HOUR } - logger.d({ "refreshFormat($str1)" }) { str1 = format?.toString() } + logger.d({ "refreshFormat(${escapeTime(str1)})" }) { str1 = format?.toString() } descFormat = if (use24HourFormat) Patterns.sClockView24 else Patterns.sClockView12 refreshTime() @@ -634,7 +637,7 @@ constructor( companion object { private val TAG = AnimatableClockView::class.simpleName!! - private val DEFAULT_LOGGER = Logger(LogcatOnlyMessageBuffer(LogLevel.WARNING), TAG) + private val DEFAULT_LOGGER = ClockLogger(null, LogcatOnlyMessageBuffer(LogLevel.DEBUG), TAG) const val ANIMATION_DURATION_FOLD_TO_AOD: Int = 600 private const val DOUBLE_LINE_FORMAT_12_HOUR = "hh\nmm" diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/CanvasUtil.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/CanvasUtil.kt index dd1599e5259d..9857d7f3d69d 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/CanvasUtil.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/CanvasUtil.kt @@ -17,6 +17,8 @@ package com.android.systemui.shared.clocks import android.graphics.Canvas +import com.android.systemui.plugins.clocks.VPoint +import com.android.systemui.plugins.clocks.VPointF object CanvasUtil { fun Canvas.translate(pt: VPointF) = this.translate(pt.x, pt.y) diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ComposedDigitalLayerController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ComposedDigitalLayerController.kt index e9e61a718f08..37acbe261f76 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ComposedDigitalLayerController.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ComposedDigitalLayerController.kt @@ -161,15 +161,7 @@ class ComposedDigitalLayerController(private val clockCtx: ClockContext) : } override fun onThemeChanged(theme: ThemeConfig) { - val color = - when { - theme.seedColor != null -> theme.seedColor!! - theme.isDarkTheme -> - clockCtx.resources.getColor(android.R.color.system_accent1_100) - else -> clockCtx.resources.getColor(android.R.color.system_accent2_600) - } - - view.updateColor(color) + view.updateColor(theme.getDefaultColor(clockCtx.context)) } override fun onFontSettingChanged(fontSizePx: Float) { diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt index 365567b17ec0..bc4bdf4243cb 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt @@ -149,14 +149,7 @@ class DefaultClockController( override fun onThemeChanged(theme: ThemeConfig) { this@DefaultClockFaceController.theme = theme - val color = - when { - theme.seedColor != null -> theme.seedColor!! - theme.isDarkTheme -> - resources.getColor(android.R.color.system_accent1_100) - else -> resources.getColor(android.R.color.system_accent2_600) - } - + val color = theme.getDefaultColor(ctx) if (currentColor == color) { return } diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DigitTranslateAnimator.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DigitTranslateAnimator.kt index f5ccc52c8c6b..941cebfb4014 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DigitTranslateAnimator.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DigitTranslateAnimator.kt @@ -20,7 +20,8 @@ import android.animation.Animator import android.animation.AnimatorListenerAdapter import android.animation.TimeInterpolator import android.animation.ValueAnimator -import com.android.systemui.shared.clocks.VPointF.Companion.times +import com.android.systemui.plugins.clocks.VPointF +import com.android.systemui.plugins.clocks.VPointF.Companion.times class DigitTranslateAnimator(private val updateCallback: (VPointF) -> Unit) { var currentTranslation = VPointF.ZERO diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockLayerController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockLayerController.kt index 336c66eed889..9ac9e60f05fd 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockLayerController.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockLayerController.kt @@ -16,13 +16,13 @@ package com.android.systemui.shared.clocks -import android.graphics.RectF import android.view.View import androidx.annotation.VisibleForTesting import com.android.systemui.plugins.clocks.ClockAnimations import com.android.systemui.plugins.clocks.ClockEvents import com.android.systemui.plugins.clocks.ClockFaceConfig import com.android.systemui.plugins.clocks.ClockFaceEvents +import com.android.systemui.plugins.clocks.VRectF interface SimpleClockLayerController { val view: View @@ -32,5 +32,5 @@ interface SimpleClockLayerController { val config: ClockFaceConfig @VisibleForTesting var fakeTimeMills: Long? - var onViewBoundsChanged: ((RectF) -> Unit)? + var onViewBoundsChanged: ((VRectF) -> Unit)? } diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt index 97004ef6f9a9..1d963af3ad22 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt @@ -229,15 +229,7 @@ open class SimpleDigitalHandLayerController( } override fun onThemeChanged(theme: ThemeConfig) { - val color = - when { - theme.seedColor != null -> theme.seedColor!! - theme.isDarkTheme -> - clockCtx.resources.getColor(android.R.color.system_accent1_100) - else -> clockCtx.resources.getColor(android.R.color.system_accent2_600) - } - - view.updateColor(color) + view.updateColor(theme.getDefaultColor(clockCtx.context)) refreshTime() } diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ViewUtils.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ViewUtils.kt index 1e90a2370786..0740b0e504cb 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ViewUtils.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ViewUtils.kt @@ -18,8 +18,9 @@ package com.android.systemui.shared.clocks import android.graphics.Rect import android.view.View -import com.android.systemui.shared.clocks.VPoint.Companion.center -import com.android.systemui.shared.clocks.VPointF.Companion.center +import com.android.systemui.plugins.clocks.VPoint.Companion.center +import com.android.systemui.plugins.clocks.VPointF +import com.android.systemui.plugins.clocks.VPointF.Companion.center object ViewUtils { fun View.computeLayoutDiff(targetRegion: Rect, isLargeClock: Boolean): VPointF { diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt index 2dc3e2b7af73..ba32ab083063 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt @@ -17,7 +17,6 @@ package com.android.systemui.shared.clocks.view import android.graphics.Canvas -import android.graphics.RectF import android.icu.text.NumberFormat import android.util.MathUtils.constrainedMap import android.view.View @@ -29,14 +28,15 @@ import com.android.app.animation.Interpolators import com.android.systemui.customization.R import com.android.systemui.plugins.clocks.ClockFontAxisSetting import com.android.systemui.plugins.clocks.ClockLogger +import com.android.systemui.plugins.clocks.VPoint +import com.android.systemui.plugins.clocks.VPointF +import com.android.systemui.plugins.clocks.VPointF.Companion.max +import com.android.systemui.plugins.clocks.VPointF.Companion.times +import com.android.systemui.plugins.clocks.VRectF import com.android.systemui.shared.clocks.CanvasUtil.translate import com.android.systemui.shared.clocks.CanvasUtil.use import com.android.systemui.shared.clocks.ClockContext import com.android.systemui.shared.clocks.DigitTranslateAnimator -import com.android.systemui.shared.clocks.VPoint -import com.android.systemui.shared.clocks.VPointF -import com.android.systemui.shared.clocks.VPointF.Companion.max -import com.android.systemui.shared.clocks.VPointF.Companion.times import com.android.systemui.shared.clocks.ViewUtils.measuredSize import java.util.Locale import kotlin.collections.filterNotNull @@ -101,7 +101,7 @@ class FlexClockView(clockCtx: ClockContext) : ViewGroup(clockCtx.context) { updateLocale(Locale.getDefault()) } - var onViewBoundsChanged: ((RectF) -> Unit)? = null + var onViewBoundsChanged: ((VRectF) -> Unit)? = null private val digitOffsets = mutableMapOf<Int, Float>() protected fun calculateSize( @@ -189,13 +189,7 @@ class FlexClockView(clockCtx: ClockContext) : ViewGroup(clockCtx.context) { fun updateLocation() { val layoutBounds = this.layoutBounds ?: return - val bounds = - RectF( - layoutBounds.centerX() - measuredWidth / 2f, - layoutBounds.centerY() - measuredHeight / 2f, - layoutBounds.centerX() + measuredWidth / 2f, - layoutBounds.centerY() + measuredHeight / 2f, - ) + val bounds = VRectF.fromCenter(layoutBounds.center, this.measuredSize) setFrame( bounds.left.roundToInt(), bounds.top.roundToInt(), @@ -215,16 +209,11 @@ class FlexClockView(clockCtx: ClockContext) : ViewGroup(clockCtx.context) { onAnimateDoze = null } - private val layoutBounds = RectF() + private var layoutBounds = VRectF.ZERO override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { logger.onLayout(changed, left, top, right, bottom) - - layoutBounds.left = left.toFloat() - layoutBounds.top = top.toFloat() - layoutBounds.right = right.toFloat() - layoutBounds.bottom = bottom.toFloat() - + layoutBounds = VRectF(left.toFloat(), top.toFloat(), right.toFloat(), bottom.toFloat()) updateChildFrames(isLayout = true) } 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 fae17a5321ff..2af25fe339a2 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt @@ -23,7 +23,6 @@ import android.graphics.Paint import android.graphics.PorterDuff import android.graphics.PorterDuffXfermode import android.graphics.Rect -import android.graphics.RectF import android.os.VibrationEffect import android.text.Layout import android.text.TextPaint @@ -45,6 +44,11 @@ import com.android.systemui.plugins.clocks.ClockFontAxisSetting import com.android.systemui.plugins.clocks.ClockFontAxisSetting.Companion.replace import com.android.systemui.plugins.clocks.ClockFontAxisSetting.Companion.toFVar import com.android.systemui.plugins.clocks.ClockLogger +import com.android.systemui.plugins.clocks.VPoint +import com.android.systemui.plugins.clocks.VPointF +import com.android.systemui.plugins.clocks.VPointF.Companion.size +import com.android.systemui.plugins.clocks.VRectF +import com.android.systemui.shared.Flags.ambientAod import com.android.systemui.shared.clocks.CanvasUtil.translate import com.android.systemui.shared.clocks.CanvasUtil.use import com.android.systemui.shared.clocks.ClockContext @@ -52,9 +56,6 @@ import com.android.systemui.shared.clocks.DigitTranslateAnimator import com.android.systemui.shared.clocks.DimensionParser import com.android.systemui.shared.clocks.FLEX_CLOCK_ID import com.android.systemui.shared.clocks.FontTextStyle -import com.android.systemui.shared.clocks.VPoint -import com.android.systemui.shared.clocks.VPointF -import com.android.systemui.shared.clocks.VPointF.Companion.size import com.android.systemui.shared.clocks.ViewUtils.measuredSize import com.android.systemui.shared.clocks.ViewUtils.size import com.android.systemui.shared.clocks.toClockAxisSetting @@ -65,11 +66,11 @@ import kotlin.math.roundToInt private val TAG = SimpleDigitalClockTextView::class.simpleName!! -private fun Paint.getTextBounds(text: CharSequence, result: RectF = RectF()): RectF { - val rect = Rect() - this.getTextBounds(text, 0, text.length, rect) - result.set(rect) - return result +private val tempRect = Rect() + +private fun Paint.getTextBounds(text: CharSequence): VRectF { + this.getTextBounds(text, 0, text.length, tempRect) + return VRectF(tempRect) } enum class VerticalAlignment { @@ -142,7 +143,7 @@ open class SimpleDigitalClockTextView( fidgetFontVariation = buildFidgetVariation(lsFontAxes).toFVar() } - var onViewBoundsChanged: ((RectF) -> Unit)? = null + var onViewBoundsChanged: ((VRectF) -> Unit)? = null private val parser = DimensionParser(clockCtx.context) var maxSingleDigitHeight = -1f var maxSingleDigitWidth = -1f @@ -158,13 +159,13 @@ open class SimpleDigitalClockTextView( private val initThread = Thread.currentThread() // textBounds is the size of text in LS, which only measures current text in lockscreen style - var textBounds = RectF() + var textBounds = VRectF.ZERO // prevTextBounds and targetTextBounds are to deal with dozing animation between LS and AOD // especially for the textView which has different bounds during the animation // prevTextBounds holds the state we are transitioning from - private val prevTextBounds = RectF() + private var prevTextBounds = VRectF.ZERO // targetTextBounds holds the state we are interpolating to - private val targetTextBounds = RectF() + private var targetTextBounds = VRectF.ZERO protected val logger = ClockLogger(this, clockCtx.messageBuffer, this::class.simpleName!!) get() = field ?: ClockLogger.INIT_LOGGER @@ -214,8 +215,8 @@ open class SimpleDigitalClockTextView( lockScreenPaint.typeface = typefaceCache.getTypefaceForVariant(lsFontVariation) typeface = lockScreenPaint.typeface - lockScreenPaint.getTextBounds(text, textBounds) - targetTextBounds.set(textBounds) + textBounds = lockScreenPaint.getTextBounds(text) + targetTextBounds = textBounds textAnimator.setTextStyle(TextAnimator.Style(fVar = lsFontVariation)) measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED) @@ -286,7 +287,7 @@ open class SimpleDigitalClockTextView( canvas.use { digitTranslateAnimator?.apply { canvas.translate(currentTranslation) } canvas.translate(getDrawTranslation(interpBounds)) - if (isLayoutRtl()) canvas.translate(interpBounds.width() - textBounds.width(), 0f) + if (isLayoutRtl()) canvas.translate(interpBounds.width - textBounds.width, 0f) textAnimator.draw(canvas) } } @@ -301,16 +302,12 @@ open class SimpleDigitalClockTextView( super.setAlpha(alpha) } - private val layoutBounds = RectF() + private var layoutBounds = VRectF.ZERO override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { super.onLayout(changed, left, top, right, bottom) logger.onLayout(changed, left, top, right, bottom) - - layoutBounds.left = left.toFloat() - layoutBounds.top = top.toFloat() - layoutBounds.right = right.toFloat() - layoutBounds.bottom = bottom.toFloat() + layoutBounds = VRectF(left.toFloat(), top.toFloat(), right.toFloat(), bottom.toFloat()) } override fun invalidate() { @@ -326,11 +323,11 @@ open class SimpleDigitalClockTextView( fun animateDoze(isDozing: Boolean, isAnimated: Boolean) { if (!this::textAnimator.isInitialized) return - logger.animateDoze() + logger.animateDoze(isDozing, isAnimated) textAnimator.setTextStyle( TextAnimator.Style( fVar = if (isDozing) aodFontVariation else lsFontVariation, - color = if (isDozing) AOD_COLOR else lockscreenColor, + color = if (isDozing && !ambientAod()) AOD_COLOR else lockscreenColor, textSize = if (isDozing) aodFontSizePx else lockScreenPaint.textSize, ), TextAnimator.Animation( @@ -340,6 +337,11 @@ open class SimpleDigitalClockTextView( ), ) updateTextBoundsForTextAnimator() + + if (!isAnimated) { + requestLayout() + (parent as? FlexClockView)?.requestLayout() + } } fun animateCharge() { @@ -407,10 +409,10 @@ open class SimpleDigitalClockTextView( } fun refreshText() { - lockScreenPaint.getTextBounds(text, textBounds) - if (this::textAnimator.isInitialized) { - textAnimator.textInterpolator.targetPaint.getTextBounds(text, targetTextBounds) - } + textBounds = lockScreenPaint.getTextBounds(text) + targetTextBounds = + if (!this::textAnimator.isInitialized) textBounds + else textAnimator.textInterpolator.targetPaint.getTextBounds(text) if (layout == null) { requestLayout() @@ -431,23 +433,23 @@ open class SimpleDigitalClockTextView( } /** Returns the interpolated text bounding rect based on interpolation progress */ - private fun getInterpolatedTextBounds(progress: Float = getInterpolatedProgress()): RectF { + private fun getInterpolatedTextBounds(progress: Float = getInterpolatedProgress()): VRectF { if (progress <= 0f) { return prevTextBounds } else if (!textAnimator.isRunning || progress >= 1f) { return targetTextBounds } - return RectF().apply { - left = lerp(prevTextBounds.left, targetTextBounds.left, progress) - right = lerp(prevTextBounds.right, targetTextBounds.right, progress) - top = lerp(prevTextBounds.top, targetTextBounds.top, progress) - bottom = lerp(prevTextBounds.bottom, targetTextBounds.bottom, progress) - } + return VRectF( + left = lerp(prevTextBounds.left, targetTextBounds.left, progress), + right = lerp(prevTextBounds.right, targetTextBounds.right, progress), + top = lerp(prevTextBounds.top, targetTextBounds.top, progress), + bottom = lerp(prevTextBounds.bottom, targetTextBounds.bottom, progress), + ) } private fun computeMeasuredSize( - interpBounds: RectF, + interpBounds: VRectF, widthMeasureSpec: Int = measuredWidthAndState, heightMeasureSpec: Int = measuredHeightAndState, ): VPointF { @@ -460,11 +462,11 @@ open class SimpleDigitalClockTextView( return VPointF( when { mode.x == EXACTLY -> MeasureSpec.getSize(widthMeasureSpec).toFloat() - else -> interpBounds.width() + 2 * lockScreenPaint.strokeWidth + else -> interpBounds.width + 2 * lockScreenPaint.strokeWidth }, when { mode.y == EXACTLY -> MeasureSpec.getSize(heightMeasureSpec).toFloat() - else -> interpBounds.height() + 2 * lockScreenPaint.strokeWidth + else -> interpBounds.height + 2 * lockScreenPaint.strokeWidth }, ) } @@ -488,44 +490,23 @@ open class SimpleDigitalClockTextView( } /** Set the location of the view to match the interpolated text bounds */ - private fun setInterpolatedLocation(measureSize: VPointF): RectF { - val targetRect = RectF() - targetRect.apply { - when (xAlignment) { - XAlignment.LEFT -> { - left = layoutBounds.left - right = layoutBounds.left + measureSize.x - } - XAlignment.CENTER -> { - left = layoutBounds.centerX() - measureSize.x / 2f - right = layoutBounds.centerX() + measureSize.x / 2f - } - XAlignment.RIGHT -> { - left = layoutBounds.right - measureSize.x - right = layoutBounds.right - } - } - - when (verticalAlignment) { - VerticalAlignment.TOP -> { - top = layoutBounds.top - bottom = layoutBounds.top + measureSize.y - } - VerticalAlignment.CENTER -> { - top = layoutBounds.centerY() - measureSize.y / 2f - bottom = layoutBounds.centerY() + measureSize.y / 2f - } - VerticalAlignment.BOTTOM -> { - top = layoutBounds.bottom - measureSize.y - bottom = layoutBounds.bottom - } - VerticalAlignment.BASELINE -> { - top = layoutBounds.centerY() - measureSize.y / 2f - bottom = layoutBounds.centerY() + measureSize.y / 2f - } - } - } + private fun setInterpolatedLocation(measureSize: VPointF): VRectF { + val pos = + VPointF( + when (xAlignment) { + XAlignment.LEFT -> layoutBounds.left + XAlignment.CENTER -> layoutBounds.center.x - measureSize.x / 2f + XAlignment.RIGHT -> layoutBounds.right - measureSize.x + }, + when (verticalAlignment) { + VerticalAlignment.TOP -> layoutBounds.top + VerticalAlignment.CENTER -> layoutBounds.center.y - measureSize.y / 2f + VerticalAlignment.BOTTOM -> layoutBounds.bottom - measureSize.y + VerticalAlignment.BASELINE -> layoutBounds.center.y - measureSize.y / 2f + }, + ) + val targetRect = VRectF.fromTopLeft(pos, measureSize) setFrame( targetRect.left.roundToInt(), targetRect.top.roundToInt(), @@ -536,7 +517,7 @@ open class SimpleDigitalClockTextView( return targetRect } - private fun getDrawTranslation(interpBounds: RectF): VPointF { + private fun getDrawTranslation(interpBounds: VRectF): VPointF { val sizeDiff = this.measuredSize - interpBounds.size val alignment = VPointF( @@ -585,11 +566,11 @@ open class SimpleDigitalClockTextView( if (fontSizePx > 0) { setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSizePx) lockScreenPaint.textSize = textSize - lockScreenPaint.getTextBounds(text, textBounds) - targetTextBounds.set(textBounds) + textBounds = lockScreenPaint.getTextBounds(text) + targetTextBounds = textBounds } if (!constrainedByHeight) { - val lastUnconstrainedHeight = textBounds.height() + lockScreenPaint.strokeWidth * 2 + val lastUnconstrainedHeight = textBounds.height + lockScreenPaint.strokeWidth * 2 fontSizeAdjustFactor = lastUnconstrainedHeight / lastUnconstrainedTextSize } @@ -607,8 +588,8 @@ open class SimpleDigitalClockTextView( for (i in 0..9) { val rectForCalculate = lockScreenPaint.getTextBounds("$i") - maxSingleDigitHeight = max(maxSingleDigitHeight, rectForCalculate.height()) - maxSingleDigitWidth = max(maxSingleDigitWidth, rectForCalculate.width()) + maxSingleDigitHeight = max(maxSingleDigitHeight, rectForCalculate.height) + maxSingleDigitWidth = max(maxSingleDigitWidth, rectForCalculate.width) } maxSingleDigitWidth += 2 * lockScreenPaint.strokeWidth maxSingleDigitHeight += 2 * lockScreenPaint.strokeWidth @@ -636,8 +617,8 @@ open class SimpleDigitalClockTextView( * and targetPaint will store the state we transition to */ private fun updateTextBoundsForTextAnimator() { - textAnimator.textInterpolator.basePaint.getTextBounds(text, prevTextBounds) - textAnimator.textInterpolator.targetPaint.getTextBounds(text, targetTextBounds) + prevTextBounds = textAnimator.textInterpolator.basePaint.getTextBounds(text) + targetTextBounds = textAnimator.textInterpolator.targetPaint.getTextBounds(text) } /** diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java index e26e19d27417..29647cd082b1 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java @@ -16,12 +16,18 @@ package com.android.keyguard; +import static com.android.internal.widget.flags.Flags.FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.when; +import android.hardware.input.InputManager; +import android.platform.test.annotations.EnableFlags; import android.testing.TestableLooper.RunWithLooper; import android.view.View; import android.view.ViewGroup; @@ -90,6 +96,8 @@ public class KeyguardPinBasedInputViewControllerTest extends SysuiTestCase { @Mock private UserActivityNotifier mUserActivityNotifier; private NumPadKey[] mButtons = new NumPadKey[]{}; + @Mock + private InputManager mInputManager; private KeyguardPinBasedInputViewController mKeyguardPinViewController; @@ -118,12 +126,13 @@ public class KeyguardPinBasedInputViewControllerTest extends SysuiTestCase { new KeyguardKeyboardInteractor(new FakeKeyboardRepository()); FakeFeatureFlags featureFlags = new FakeFeatureFlags(); mSetFlagsRule.enableFlags(com.android.systemui.Flags.FLAG_REVAMPED_BOUNCER_MESSAGES); + when(mLockPatternUtils.isPinEnhancedPrivacyEnabled(anyInt())).thenReturn(false); mKeyguardPinViewController = new KeyguardPinBasedInputViewController(mPinBasedInputView, mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback, mKeyguardMessageAreaControllerFactory, mLatencyTracker, mEmergencyButtonController, mFalsingCollector, featureFlags, mSelectedUserInteractor, keyguardKeyboardInteractor, mBouncerHapticPlayer, - mUserActivityNotifier) { + mUserActivityNotifier, mInputManager) { @Override public void onResume(int reason) { super.onResume(reason); @@ -148,4 +157,112 @@ public class KeyguardPinBasedInputViewControllerTest extends SysuiTestCase { mKeyguardPinViewController.resetState(); verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_pin); } + + @Test + @EnableFlags(FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT) + public void updateAnimations_addDevice_notKeyboard() { + when(mLockPatternUtils.isPinEnhancedPrivacyEnabled(anyInt())).thenReturn(false); + verify(mPasswordEntry, times(1)).setShowPassword(true); + mKeyguardPinViewController.onViewAttached(); + mKeyguardPinViewController.onInputDeviceAdded(1); + verify(mPasswordEntry, times(1)).setShowPassword(true); + } + + @Test + @EnableFlags(FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT) + public void updateAnimations_addDevice_keyboard() { + when(mLockPatternUtils.isPinEnhancedPrivacyEnabled(anyInt())).thenReturn(true); + verify(mPasswordEntry, times(1)).setShowPassword(true); + mKeyguardPinViewController.onViewAttached(); + mKeyguardPinViewController.onInputDeviceAdded(1); + verify(mPasswordEntry, times(1)).setShowPassword(false); + } + + @Test + @EnableFlags(FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT) + public void updateAnimations_addDevice_multipleKeyboards() { + when(mLockPatternUtils.isPinEnhancedPrivacyEnabled(anyInt())).thenReturn(true); + verify(mPasswordEntry, times(1)).setShowPassword(true); + mKeyguardPinViewController.onViewAttached(); + mKeyguardPinViewController.onInputDeviceAdded(1); + verify(mPasswordEntry, times(1)).setShowPassword(false); + mKeyguardPinViewController.onInputDeviceAdded(1); + verify(mPasswordEntry, times(1)).setShowPassword(false); + } + + @Test + @EnableFlags(FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT) + public void updateAnimations_removeDevice_notKeyboard() { + when(mLockPatternUtils.isPinEnhancedPrivacyEnabled(anyInt())).thenReturn(false); + verify(mPasswordEntry, times(1)).setShowPassword(true); + mKeyguardPinViewController.onViewAttached(); + mKeyguardPinViewController.onInputDeviceRemoved(1); + verify(mPasswordEntry, times(1)).setShowPassword(true); + } + + @Test + @EnableFlags(FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT) + public void updateAnimations_removeDevice_keyboard() { + when(mLockPatternUtils.isPinEnhancedPrivacyEnabled(anyInt())).thenReturn(true, false); + verify(mPasswordEntry, times(1)).setShowPassword(true); + verify(mPasswordEntry, times(0)).setShowPassword(false); + mKeyguardPinViewController.onViewAttached(); + verify(mPasswordEntry, times(1)).setShowPassword(true); + verify(mPasswordEntry, times(1)).setShowPassword(false); + mKeyguardPinViewController.onInputDeviceRemoved(1); + verify(mPasswordEntry, times(2)).setShowPassword(true); + verify(mPasswordEntry, times(1)).setShowPassword(false); + } + + @Test + @EnableFlags(FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT) + public void updateAnimations_removeDevice_multipleKeyboards() { + when(mLockPatternUtils.isPinEnhancedPrivacyEnabled(anyInt())).thenReturn(true, true); + verify(mPasswordEntry, times(1)).setShowPassword(true); + verify(mPasswordEntry, times(0)).setShowPassword(false); + mKeyguardPinViewController.onViewAttached(); + verify(mPasswordEntry, times(1)).setShowPassword(true); + verify(mPasswordEntry, times(1)).setShowPassword(false); + mKeyguardPinViewController.onInputDeviceRemoved(1); + verify(mPasswordEntry, times(1)).setShowPassword(true); + verify(mPasswordEntry, times(1)).setShowPassword(false); + } + + @Test + @EnableFlags(FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT) + public void updateAnimations_updateDevice_notKeyboard() { + when(mLockPatternUtils.isPinEnhancedPrivacyEnabled(anyInt())).thenReturn(false); + verify(mPasswordEntry, times(1)).setShowPassword(true); + mKeyguardPinViewController.onViewAttached(); + mKeyguardPinViewController.onInputDeviceChanged(1); + verify(mPasswordEntry, times(1)).setShowPassword(true); + } + + @Test + @EnableFlags(FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT) + public void updateAnimations_updateDevice_keyboard() { + when(mLockPatternUtils.isPinEnhancedPrivacyEnabled(anyInt())).thenReturn(true, false); + verify(mPasswordEntry, times(1)).setShowPassword(true); + verify(mPasswordEntry, times(0)).setShowPassword(false); + mKeyguardPinViewController.onViewAttached(); + verify(mPasswordEntry, times(1)).setShowPassword(true); + verify(mPasswordEntry, times(1)).setShowPassword(false); + mKeyguardPinViewController.onInputDeviceChanged(1); + verify(mPasswordEntry, times(2)).setShowPassword(true); + verify(mPasswordEntry, times(1)).setShowPassword(false); + } + + @Test + @EnableFlags(FLAG_HIDE_LAST_CHAR_WITH_PHYSICAL_INPUT) + public void updateAnimations_updateDevice_multipleKeyboards() { + when(mLockPatternUtils.isPinEnhancedPrivacyEnabled(anyInt())).thenReturn(true, true); + verify(mPasswordEntry, times(1)).setShowPassword(true); + verify(mPasswordEntry, times(0)).setShowPassword(false); + mKeyguardPinViewController.onViewAttached(); + verify(mPasswordEntry, times(1)).setShowPassword(true); + verify(mPasswordEntry, times(1)).setShowPassword(false); + mKeyguardPinViewController.onInputDeviceChanged(1); + verify(mPasswordEntry, times(1)).setShowPassword(true); + verify(mPasswordEntry, times(1)).setShowPassword(false); + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt index 142a2868ec14..7fea06ec7f41 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt @@ -16,6 +16,7 @@ package com.android.keyguard +import android.hardware.input.InputManager import android.testing.TestableLooper import android.view.View import android.view.ViewGroup @@ -104,6 +105,7 @@ class KeyguardPinViewControllerTest : SysuiTestCase() { @Mock lateinit var enterButton: View @Mock lateinit var uiEventLogger: UiEventLogger @Mock lateinit var mUserActivityNotifier: UserActivityNotifier + @Mock lateinit var inputManager: InputManager @Captor lateinit var postureCallbackCaptor: ArgumentCaptor<DevicePostureController.Callback> @@ -154,6 +156,7 @@ class KeyguardPinViewControllerTest : SysuiTestCase() { keyguardKeyboardInteractor, kosmos.bouncerHapticPlayer, mUserActivityNotifier, + inputManager, ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt index c751a7db51dc..003669da498e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt @@ -16,6 +16,7 @@ package com.android.keyguard +import android.hardware.input.InputManager import android.telephony.TelephonyManager import android.testing.TestableLooper import android.view.LayoutInflater @@ -73,6 +74,7 @@ class KeyguardSimPinViewControllerTest : SysuiTestCase() { @Mock private lateinit var mUserActivityNotifier: UserActivityNotifier private val updateMonitorCallbackArgumentCaptor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java) + @Mock private lateinit var inputManager: InputManager private val kosmos = testKosmos() @@ -107,6 +109,7 @@ class KeyguardSimPinViewControllerTest : SysuiTestCase() { keyguardKeyboardInteractor, kosmos.bouncerHapticPlayer, mUserActivityNotifier, + inputManager, ) underTest.init() underTest.onViewAttached() diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt index c34682551eda..85cb388ace5c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt @@ -16,6 +16,7 @@ package com.android.keyguard +import android.hardware.input.InputManager import android.telephony.PinResult import android.telephony.TelephonyManager import android.testing.TestableLooper @@ -65,6 +66,7 @@ class KeyguardSimPukViewControllerTest : SysuiTestCase() { private lateinit var keyguardMessageAreaController: KeyguardMessageAreaController<BouncerKeyguardMessageArea> @Mock private lateinit var mUserActivityNotifier: UserActivityNotifier + @Mock private lateinit var inputManager: InputManager private val kosmos = testKosmos() @@ -102,6 +104,7 @@ class KeyguardSimPukViewControllerTest : SysuiTestCase() { keyguardKeyboardInteractor, kosmos.bouncerHapticPlayer, mUserActivityNotifier, + inputManager, ) underTest.init() } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ExpandHelperTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ExpandHelperTest.java index 7fb879c02778..f0c9141a76e6 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/ExpandHelperTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ExpandHelperTest.java @@ -32,7 +32,7 @@ import androidx.test.filters.SmallTest; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.animation.AnimatorTestRule; import com.android.systemui.flags.FakeFeatureFlagsClassic; -import com.android.systemui.statusbar.NotificationMediaManager; +import com.android.systemui.media.NotificationMediaManager; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationTestHelper; diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/KairosCoreStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/KairosCoreStartableTest.kt new file mode 100644 index 000000000000..4daf02332d41 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/KairosCoreStartableTest.kt @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.kairos.ExperimentalKairosApi +import com.android.systemui.kairos.KairosNetwork +import com.android.systemui.kairos.runKairosTest +import com.android.systemui.kairos.toColdConflatedFlow +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.kosmos.testScope +import com.android.systemui.kosmos.useUnconfinedTestDispatcher +import com.google.common.truth.Truth.assertThat +import kotlin.test.Test +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.launch +import org.junit.runner.RunWith + +@OptIn(ExperimentalKairosApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +class KairosCoreStartableTest : SysuiTestCase() { + + @Test + fun kairosNetwork_usedBeforeStarted() = + testKosmos().useUnconfinedTestDispatcher().runKairosTest { + lateinit var activatable: TestActivatable + val underTest = KairosCoreStartable(applicationCoroutineScope) { setOf(activatable) } + activatable = TestActivatable(underTest) + + // collect from the cold flow before starting the CoreStartable + var collectCount = 0 + testScope.backgroundScope.launch { activatable.coldFlow.collect { collectCount++ } } + + // start the CoreStartable + underTest.start() + + // verify emissions are received + activatable.emitEvent() + + assertThat(collectCount).isEqualTo(1) + } + + private class TestActivatable(network: KairosNetwork) : KairosBuilder by kairosBuilder() { + private val emitter = MutableSharedFlow<Unit>() + private val events = buildEvents { emitter.toEvents() } + + val coldFlow = events.toColdConflatedFlow(network) + + suspend fun emitEvent() = emitter.emit(Unit) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java index 0b13900d826b..7e4704a6179a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java @@ -71,6 +71,7 @@ import com.android.systemui.bluetooth.qsdialog.DeviceItem; import com.android.systemui.bluetooth.qsdialog.DeviceItemType; import com.android.systemui.model.SysUiState; import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.qs.shared.QSSettingsPackageRepository; import com.android.systemui.res.R; import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.statusbar.phone.SystemUIDialogManager; @@ -108,6 +109,7 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase { private static final String TEST_LABEL = "label"; private static final int TEST_PRESET_INDEX = 1; private static final String TEST_PRESET_NAME = "test_preset"; + private static final String SETTINGS_PACKAGE_NAME = "com.android.settings"; private final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock()); @Mock @@ -137,6 +139,8 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase { @Mock private HearingDevicesUiEventLogger mUiEventLogger; @Mock + private QSSettingsPackageRepository mQSSettingsPackageRepository; + @Mock private CachedBluetoothDevice mCachedDevice; @Mock private BluetoothDevice mDevice; @@ -164,6 +168,8 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase { when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(List.of(mCachedDevice)); when(mLocalBluetoothManager.getEventManager()).thenReturn(mBluetoothEventManager); when(mSysUiState.setFlag(anyLong(), anyBoolean())).thenReturn(mSysUiState); + when(mQSSettingsPackageRepository.getSettingsPackageName()) + .thenReturn(SETTINGS_PACKAGE_NAME); when(mDevice.getBondState()).thenReturn(BOND_BONDED); when(mDevice.isConnected()).thenReturn(true); when(mCachedDevice.getDevice()).thenReturn(mDevice); @@ -195,6 +201,7 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase { anyInt(), any()); assertThat(intentCaptor.getValue().getAction()).isEqualTo( Settings.ACTION_HEARING_DEVICE_PAIRING_SETTINGS); + assertThat(intentCaptor.getValue().getPackage()).isEqualTo(SETTINGS_PACKAGE_NAME); verify(mUiEventLogger).log(HearingDevicesUiEvent.HEARING_DEVICES_PAIR, TEST_LAUNCH_SOURCE_ID); } @@ -210,6 +217,7 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase { anyInt(), any()); assertThat(intentCaptor.getValue().getAction()).isEqualTo( HearingDevicesDialogDelegate.ACTION_BLUETOOTH_DEVICE_DETAILS); + assertThat(intentCaptor.getValue().getPackage()).isEqualTo(SETTINGS_PACKAGE_NAME); verify(mUiEventLogger).log(HearingDevicesUiEvent.HEARING_DEVICES_GEAR_CLICK, TEST_LAUNCH_SOURCE_ID); } @@ -392,7 +400,8 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase { mExecutor, mExecutor, mAudioManager, - mUiEventLogger + mUiEventLogger, + mQSSettingsPackageRepository ); mDialog = mDialogDelegate.createDialog(); } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/FontVariationUtilsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/FontVariationUtilsTest.kt index 8d3640d8d809..53b364c13063 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/FontVariationUtilsTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/FontVariationUtilsTest.kt @@ -41,21 +41,9 @@ class FontVariationUtilsTest : SysuiTestCase() { @Test fun testStyleValueUnchange_getBlankStr() { val fontVariationUtils = FontVariationUtils() - fontVariationUtils.updateFontVariation( - weight = 100, - width = 100, - opticalSize = 0, - roundness = 100, - ) - val updatedFvar1 = - fontVariationUtils.updateFontVariation( - weight = 100, - width = 100, - opticalSize = 0, - roundness = 100, - ) - Assert.assertEquals("", updatedFvar1) - val updatedFvar2 = fontVariationUtils.updateFontVariation() - Assert.assertEquals("", updatedFvar2) + Assert.assertEquals("", fontVariationUtils.updateFontVariation()) + val fVar = fontVariationUtils.updateFontVariation(weight = 100) + Assert.assertEquals(fVar, fontVariationUtils.updateFontVariation()) + Assert.assertEquals(fVar, fontVariationUtils.updateFontVariation(weight = 100)) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinInputViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinInputViewModelTest.kt index 25a287c4cfff..15a6de896e92 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinInputViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinInputViewModelTest.kt @@ -247,20 +247,20 @@ class PinInputViewModelTest : SysuiTestCase() { } private class PinInputSubject -private constructor(metadata: FailureMetadata, private val actual: PinInputViewModel) : +private constructor(metadata: FailureMetadata, private val actual: PinInputViewModel?) : Subject(metadata, actual) { fun matches(mnemonics: String) { val actualMnemonics = - actual.input - .map { entry -> + actual?.input + ?.map { entry -> when (entry) { is Digit -> entry.input.digitToChar() is ClearAll -> 'C' else -> throw IllegalArgumentException() } } - .joinToString(separator = "") + ?.joinToString(separator = "") if (mnemonics != actualMnemonics) { failWithActual( diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/view/TouchHandlingViewInteractionHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/view/TouchHandlingViewInteractionHandlerTest.kt index 0f400892f988..56b06de0a9ba 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/view/TouchHandlingViewInteractionHandlerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/view/TouchHandlingViewInteractionHandlerTest.kt @@ -17,14 +17,12 @@ package com.android.systemui.common.ui.view +import android.testing.TestableLooper +import android.view.MotionEvent import android.view.ViewConfiguration import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.common.ui.view.TouchHandlingViewInteractionHandler.MotionEventModel -import com.android.systemui.common.ui.view.TouchHandlingViewInteractionHandler.MotionEventModel.Down -import com.android.systemui.common.ui.view.TouchHandlingViewInteractionHandler.MotionEventModel.Move -import com.android.systemui.common.ui.view.TouchHandlingViewInteractionHandler.MotionEventModel.Up import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat @@ -33,18 +31,22 @@ import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock import org.mockito.Mockito.never +import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @SmallTest @RunWith(AndroidJUnit4::class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) class TouchHandlingViewInteractionHandlerTest : SysuiTestCase() { @Mock private lateinit var postDelayed: (Runnable, Long) -> DisposableHandle @Mock private lateinit var onLongPressDetected: (Int, Int) -> Unit @Mock private lateinit var onSingleTapDetected: (Int, Int) -> Unit + @Mock private lateinit var onDoubleTapDetected: () -> Unit private lateinit var underTest: TouchHandlingViewInteractionHandler @@ -61,14 +63,17 @@ class TouchHandlingViewInteractionHandlerTest : SysuiTestCase() { underTest = TouchHandlingViewInteractionHandler( + context = context, postDelayed = postDelayed, isAttachedToWindow = { isAttachedToWindow }, onLongPressDetected = onLongPressDetected, onSingleTapDetected = onSingleTapDetected, + onDoubleTapDetected = onDoubleTapDetected, longPressDuration = { ViewConfiguration.getLongPressTimeout().toLong() }, allowedTouchSlop = ViewConfiguration.getTouchSlop(), ) underTest.isLongPressHandlingEnabled = true + underTest.isDoubleTapHandlingEnabled = true } @Test @@ -76,63 +81,250 @@ class TouchHandlingViewInteractionHandlerTest : SysuiTestCase() { val downX = 123 val downY = 456 dispatchTouchEvents( - Down(x = downX, y = downY), - Move(distanceMoved = ViewConfiguration.getTouchSlop() - 0.1f), + MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 123f, 456f, 0), + MotionEvent.obtain( + 0L, + 0L, + MotionEvent.ACTION_MOVE, + 123f + ViewConfiguration.getTouchSlop() - 0.1f, + 456f, + 0, + ), ) delayedRunnable?.run() verify(onLongPressDetected).invoke(downX, downY) - verify(onSingleTapDetected, never()).invoke(any(), any()) + verify(onSingleTapDetected, never()).invoke(anyInt(), anyInt()) } @Test fun longPressButFeatureNotEnabled() = runTest { underTest.isLongPressHandlingEnabled = false - dispatchTouchEvents(Down(x = 123, y = 456)) + dispatchTouchEvents(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 123f, 456f, 0)) assertThat(delayedRunnable).isNull() - verify(onLongPressDetected, never()).invoke(any(), any()) - verify(onSingleTapDetected, never()).invoke(any(), any()) + verify(onLongPressDetected, never()).invoke(anyInt(), anyInt()) + verify(onSingleTapDetected, never()).invoke(anyInt(), anyInt()) } @Test fun longPressButViewNotAttached() = runTest { isAttachedToWindow = false - dispatchTouchEvents(Down(x = 123, y = 456)) + dispatchTouchEvents(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 123f, 456f, 0)) delayedRunnable?.run() - verify(onLongPressDetected, never()).invoke(any(), any()) - verify(onSingleTapDetected, never()).invoke(any(), any()) + verify(onLongPressDetected, never()).invoke(anyInt(), anyInt()) + verify(onSingleTapDetected, never()).invoke(anyInt(), anyInt()) } @Test fun draggedTooFarToBeConsideredAlongPress() = runTest { dispatchTouchEvents( - Down(x = 123, y = 456), - Move(distanceMoved = ViewConfiguration.getTouchSlop() + 0.1f), + MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 123F, 456F, 0), + // Drag action within touch slop + MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 123f, 456f, 0).apply { + addBatch(0L, 123f + ViewConfiguration.getTouchSlop() + 0.1f, 456f, 0f, 0f, 0) + }, ) assertThat(delayedRunnable).isNull() - verify(onLongPressDetected, never()).invoke(any(), any()) - verify(onSingleTapDetected, never()).invoke(any(), any()) + verify(onLongPressDetected, never()).invoke(anyInt(), anyInt()) + verify(onSingleTapDetected, never()).invoke(anyInt(), anyInt()) } @Test fun heldDownTooBrieflyToBeConsideredAlongPress() = runTest { dispatchTouchEvents( - Down(x = 123, y = 456), - Up( - distanceMoved = ViewConfiguration.getTouchSlop().toFloat(), - gestureDuration = ViewConfiguration.getLongPressTimeout() - 1L, + MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 123f, 456f, 0), + MotionEvent.obtain( + 0L, + ViewConfiguration.getLongPressTimeout() - 1L, + MotionEvent.ACTION_UP, + 123f, + 456F, + 0, ), ) assertThat(delayedRunnable).isNull() - verify(onLongPressDetected, never()).invoke(any(), any()) + verify(onLongPressDetected, never()).invoke(anyInt(), anyInt()) verify(onSingleTapDetected).invoke(123, 456) } - private fun dispatchTouchEvents(vararg models: MotionEventModel) { - models.forEach { model -> underTest.onTouchEvent(model) } + @Test + fun doubleTap() = runTest { + val secondTapTime = ViewConfiguration.getDoubleTapTimeout() - 1L + dispatchTouchEvents( + MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 123f, 456f, 0), + MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 123f, 456f, 0), + MotionEvent.obtain( + secondTapTime, + secondTapTime, + MotionEvent.ACTION_DOWN, + 123f, + 456f, + 0, + ), + MotionEvent.obtain(secondTapTime, secondTapTime, MotionEvent.ACTION_UP, 123f, 456f, 0), + ) + + verify(onDoubleTapDetected).invoke() + assertThat(delayedRunnable).isNull() + verify(onLongPressDetected, never()).invoke(anyInt(), anyInt()) + verify(onSingleTapDetected, times(2)).invoke(anyInt(), anyInt()) + } + + @Test + fun doubleTapButFeatureNotEnabled() = runTest { + underTest.isDoubleTapHandlingEnabled = false + + val secondTapTime = ViewConfiguration.getDoubleTapTimeout() - 1L + dispatchTouchEvents( + MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 123f, 456f, 0), + MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 123f, 456f, 0), + MotionEvent.obtain( + secondTapTime, + secondTapTime, + MotionEvent.ACTION_DOWN, + 123f, + 456f, + 0, + ), + MotionEvent.obtain(secondTapTime, secondTapTime, MotionEvent.ACTION_UP, 123f, 456f, 0), + ) + + verify(onDoubleTapDetected, never()).invoke() + assertThat(delayedRunnable).isNull() + verify(onLongPressDetected, never()).invoke(anyInt(), anyInt()) + verify(onSingleTapDetected, times(2)).invoke(anyInt(), anyInt()) + } + + @Test + fun tapIntoLongPress() = runTest { + val secondTapTime = ViewConfiguration.getDoubleTapTimeout() - 1L + dispatchTouchEvents( + MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 123f, 456f, 0), + MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 123f, 456f, 0), + MotionEvent.obtain( + secondTapTime, + secondTapTime, + MotionEvent.ACTION_DOWN, + 123f, + 456f, + 0, + ), + MotionEvent.obtain( + secondTapTime + ViewConfiguration.getLongPressTimeout() + 1L, + secondTapTime + ViewConfiguration.getLongPressTimeout() + 1L, + MotionEvent.ACTION_MOVE, + 123f + ViewConfiguration.getTouchSlop() - 0.1f, + 456f, + 0, + ), + ) + delayedRunnable?.run() + + verify(onDoubleTapDetected, never()).invoke() + verify(onSingleTapDetected).invoke(anyInt(), anyInt()) + verify(onLongPressDetected).invoke(anyInt(), anyInt()) + } + + @Test + fun tapIntoDownHoldTooBrieflyToBeConsideredLongPress() = runTest { + val secondTapTime = ViewConfiguration.getDoubleTapTimeout() - 1L + dispatchTouchEvents( + MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 123f, 456f, 0), + MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 123f, 456f, 0), + MotionEvent.obtain( + secondTapTime, + secondTapTime, + MotionEvent.ACTION_DOWN, + 123f, + 456f, + 0, + ), + MotionEvent.obtain( + secondTapTime + ViewConfiguration.getLongPressTimeout() + 1L, + secondTapTime + ViewConfiguration.getLongPressTimeout() + 1L, + MotionEvent.ACTION_UP, + 123f, + 456f, + 0, + ), + ) + delayedRunnable?.run() + + verify(onDoubleTapDetected, never()).invoke() + verify(onLongPressDetected, never()).invoke(anyInt(), anyInt()) + verify(onSingleTapDetected, times(2)).invoke(anyInt(), anyInt()) + } + + @Test + fun tapIntoDrag() = runTest { + val secondTapTime = ViewConfiguration.getDoubleTapTimeout() - 1L + dispatchTouchEvents( + MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 123f, 456f, 0), + MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 123f, 456f, 0), + MotionEvent.obtain( + secondTapTime, + secondTapTime, + MotionEvent.ACTION_DOWN, + 123f, + 456f, + 0, + ), + // Drag event within touch slop + MotionEvent.obtain(secondTapTime, secondTapTime, MotionEvent.ACTION_MOVE, 123f, 456f, 0) + .apply { + addBatch( + secondTapTime, + 123f + ViewConfiguration.getTouchSlop() + 0.1f, + 456f, + 0f, + 0f, + 0, + ) + }, + ) + delayedRunnable?.run() + + verify(onDoubleTapDetected, never()).invoke() + verify(onLongPressDetected, never()).invoke(anyInt(), anyInt()) + verify(onSingleTapDetected).invoke(anyInt(), anyInt()) + } + + @Test + fun doubleTapOutOfAllowableSlop() = runTest { + val secondTapTime = ViewConfiguration.getDoubleTapTimeout() - 1L + val scaledDoubleTapSlop = ViewConfiguration.get(context).scaledDoubleTapSlop + dispatchTouchEvents( + MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 123f, 456f, 0), + MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 123f, 456f, 0), + MotionEvent.obtain( + secondTapTime, + secondTapTime, + MotionEvent.ACTION_DOWN, + 123f + scaledDoubleTapSlop + 0.1f, + 456f + scaledDoubleTapSlop + 0.1f, + 0, + ), + MotionEvent.obtain( + secondTapTime, + secondTapTime, + MotionEvent.ACTION_UP, + 123f + scaledDoubleTapSlop + 0.1f, + 456f + scaledDoubleTapSlop + 0.1f, + 0, + ), + ) + + verify(onDoubleTapDetected, never()).invoke() + assertThat(delayedRunnable).isNull() + verify(onLongPressDetected, never()).invoke(anyInt(), anyInt()) + verify(onSingleTapDetected, times(2)).invoke(anyInt(), anyInt()) + } + + private fun dispatchTouchEvents(vararg events: MotionEvent) { + events.forEach { event -> underTest.onTouchEvent(event) } } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt index e0515000b232..454c15667f22 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt @@ -56,6 +56,7 @@ import com.android.systemui.statusbar.phone.dozeScrimController import com.android.systemui.statusbar.phone.screenOffAnimationController import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.advanceTimeBy @@ -105,7 +106,7 @@ class DeviceEntryHapticsInteractorTest : SysuiTestCase() { @Test fun nonPowerButtonFPS_vibrateSuccess() = testScope.runTest { - val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic) + val playSuccessHaptic by collectLastValue(underTest.playSuccessHapticOnDeviceEntry) enrollFingerprint(FingerprintSensorType.UDFPS_ULTRASONIC) runCurrent() enterDeviceFromFingerprintUnlockLegacy() @@ -116,7 +117,7 @@ class DeviceEntryHapticsInteractorTest : SysuiTestCase() { @Test fun powerButtonFPS_vibrateSuccess() = testScope.runTest { - val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic) + val playSuccessHaptic by collectLastValue(underTest.playSuccessHapticOnDeviceEntry) enrollFingerprint(FingerprintSensorType.POWER_BUTTON) kosmos.fakeKeyEventRepository.setPowerButtonDown(false) @@ -133,7 +134,7 @@ class DeviceEntryHapticsInteractorTest : SysuiTestCase() { @Test fun powerButtonFPS_powerDown_doNotVibrateSuccess() = testScope.runTest { - val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic) + val playSuccessHaptic by collectLastValue(underTest.playSuccessHapticOnDeviceEntry) enrollFingerprint(FingerprintSensorType.POWER_BUTTON) kosmos.fakeKeyEventRepository.setPowerButtonDown(true) // power button is currently DOWN @@ -150,7 +151,7 @@ class DeviceEntryHapticsInteractorTest : SysuiTestCase() { @Test fun powerButtonFPS_powerButtonRecentlyPressed_doNotVibrateSuccess() = testScope.runTest { - val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic) + val playSuccessHaptic by collectLastValue(underTest.playSuccessHapticOnDeviceEntry) enrollFingerprint(FingerprintSensorType.POWER_BUTTON) kosmos.fakeKeyEventRepository.setPowerButtonDown(false) @@ -174,14 +175,14 @@ class DeviceEntryHapticsInteractorTest : SysuiTestCase() { } @Test - fun nonPowerButtonFPS_coExFaceFailure_vibrateError() = + fun nonPowerButtonFPS_coExFaceFailure_doNotVibrateError() = testScope.runTest { val playErrorHaptic by collectLastValue(underTest.playErrorHaptic) enrollFingerprint(FingerprintSensorType.UDFPS_ULTRASONIC) enrollFace() runCurrent() faceFailure() - assertThat(playErrorHaptic).isNotNull() + assertThat(playErrorHaptic).isNull() } @Test @@ -211,7 +212,7 @@ class DeviceEntryHapticsInteractorTest : SysuiTestCase() { testScope.runTest { kosmos.configureKeyguardBypass(isBypassAvailable = false) underTest = kosmos.deviceEntryHapticsInteractor - val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic) + val playSuccessHaptic by collectLastValue(underTest.playSuccessHapticOnDeviceEntry) enrollFingerprint(FingerprintSensorType.UDFPS_ULTRASONIC) runCurrent() configureDeviceEntryFromBiometricSource(isFpUnlock = true) @@ -225,7 +226,7 @@ class DeviceEntryHapticsInteractorTest : SysuiTestCase() { testScope.runTest { kosmos.configureKeyguardBypass(isBypassAvailable = false) underTest = kosmos.deviceEntryHapticsInteractor - val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic) + val playSuccessHaptic by collectLastValue(underTest.playSuccessHapticOnDeviceEntry) enrollFingerprint(FingerprintSensorType.POWER_BUTTON) kosmos.fakeKeyEventRepository.setPowerButtonDown(false) @@ -246,18 +247,19 @@ class DeviceEntryHapticsInteractorTest : SysuiTestCase() { enrollFace() kosmos.configureKeyguardBypass(isBypassAvailable = true) underTest = kosmos.deviceEntryHapticsInteractor - val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic) + val playSuccessHaptic by collectLastValue(underTest.playSuccessHapticOnDeviceEntry) configureDeviceEntryFromBiometricSource(isFaceUnlock = true) verifyDeviceEntryFromFaceAuth() assertThat(playSuccessHaptic).isNotNull() } + @OptIn(ExperimentalCoroutinesApi::class) @EnableSceneContainer @Test - fun playSuccessHaptic_onFaceAuthSuccess_whenBypassDisabled_sceneContainer() = + fun skipSuccessHaptic_onFaceAuthSuccess_whenBypassDisabled_sceneContainer() = testScope.runTest { underTest = kosmos.deviceEntryHapticsInteractor - val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic) + val playSuccessHaptic by collectLastValue(underTest.playSuccessHapticOnDeviceEntry) enrollFace() kosmos.configureKeyguardBypass(isBypassAvailable = false) @@ -265,7 +267,7 @@ class DeviceEntryHapticsInteractorTest : SysuiTestCase() { configureDeviceEntryFromBiometricSource(isFaceUnlock = true, bypassEnabled = false) kosmos.fakeDeviceEntryFaceAuthRepository.isAuthenticated.value = true - assertThat(playSuccessHaptic).isNotNull() + assertThat(playSuccessHaptic).isNull() } @EnableSceneContainer @@ -274,7 +276,7 @@ class DeviceEntryHapticsInteractorTest : SysuiTestCase() { testScope.runTest { kosmos.configureKeyguardBypass(isBypassAvailable = false) underTest = kosmos.deviceEntryHapticsInteractor - val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic) + val playSuccessHaptic by collectLastValue(underTest.playSuccessHapticOnDeviceEntry) enrollFingerprint(FingerprintSensorType.POWER_BUTTON) // power button is currently DOWN kosmos.fakeKeyEventRepository.setPowerButtonDown(true) @@ -295,7 +297,7 @@ class DeviceEntryHapticsInteractorTest : SysuiTestCase() { testScope.runTest { kosmos.configureKeyguardBypass(isBypassAvailable = false) underTest = kosmos.deviceEntryHapticsInteractor - val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic) + val playSuccessHaptic by collectLastValue(underTest.playSuccessHapticOnDeviceEntry) enrollFingerprint(FingerprintSensorType.POWER_BUTTON) kosmos.fakeKeyEventRepository.setPowerButtonDown(false) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractorTest.kt index f37306276848..efc68f3b6884 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractorTest.kt @@ -57,4 +57,17 @@ class KeyEventInteractorTest : SysuiTestCase() { repository.setPowerButtonDown(true) assertThat(isPowerDown).isTrue() } + + @Test + fun testPowerButtonBeingLongPressedInteractor() = + runTest { + val isPowerButtonLongPressed by collectLastValue( + underTest.isPowerButtonLongPressed) + + repository.setPowerButtonLongPressed(false) + assertThat(isPowerButtonLongPressed).isFalse() + + repository.setPowerButtonLongPressed(true) + assertThat(isPowerButtonLongPressed).isTrue() + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java index 909acca12551..573216dd680a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java @@ -49,9 +49,9 @@ import androidx.test.filters.SmallTest; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.SystemUIInitializerImpl; import com.android.systemui.SysuiTestCase; +import com.android.systemui.media.NotificationMediaManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.settings.UserTracker; -import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.KeyguardBypassController; @@ -177,7 +177,8 @@ public class KeyguardSliceProviderTest extends SysuiTestCase { @Test public void schedulesAlarm12hBefore() { long in16Hours = System.currentTimeMillis() + TimeUnit.HOURS.toHours(16); - AlarmManager.AlarmClockInfo alarmClockInfo = new AlarmManager.AlarmClockInfo(in16Hours, null); + AlarmManager.AlarmClockInfo alarmClockInfo = new AlarmManager.AlarmClockInfo(in16Hours, + null); mProvider.onNextAlarmChanged(alarmClockInfo); long twelveHours = TimeUnit.HOURS.toMillis(KeyguardSliceProvider.ALARM_VISIBILITY_HOURS); @@ -189,7 +190,8 @@ public class KeyguardSliceProviderTest extends SysuiTestCase { @Test public void updatingNextAlarmInvalidatesSlice() { long in16Hours = System.currentTimeMillis() + TimeUnit.HOURS.toHours(8); - AlarmManager.AlarmClockInfo alarmClockInfo = new AlarmManager.AlarmClockInfo(in16Hours, null); + AlarmManager.AlarmClockInfo alarmClockInfo = new AlarmManager.AlarmClockInfo(in16Hours, + null); mProvider.onNextAlarmChanged(alarmClockInfo); verify(mContentResolver).notifyChange(eq(mProvider.getUri()), eq(null)); @@ -204,7 +206,7 @@ public class KeyguardSliceProviderTest extends SysuiTestCase { @Test public void addZenMode_addedToSlice() { ListBuilder listBuilder = spy(new ListBuilder(getContext(), mProvider.getUri(), - ListBuilder.INFINITY)); + ListBuilder.INFINITY)); mProvider.addZenModeLocked(listBuilder); verify(listBuilder, never()).addRow(any(ListBuilder.RowBuilder.class)); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyEventRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyEventRepositoryTest.kt index c7f1525e2946..9ab5f8948ecc 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyEventRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyEventRepositoryTest.kt @@ -25,6 +25,7 @@ import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyevent.data.repository.KeyEventRepositoryImpl import com.android.systemui.statusbar.CommandQueue import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest @@ -34,10 +35,10 @@ import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.Captor import org.mockito.Mock -import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations +@OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(AndroidJUnit4::class) class KeyEventRepositoryTest : SysuiTestCase() { @@ -62,6 +63,15 @@ class KeyEventRepositoryTest : SysuiTestCase() { } @Test + fun isPowerButtonBeingLongPressed_initialValueFalse() = + testScope.runTest { + val isPowerButtonLongPressed by collectLastValue( + underTest.isPowerButtonLongPressed) + runCurrent() + assertThat(isPowerButtonLongPressed).isFalse() + } + + @Test fun isPowerButtonDown_onChange() = testScope.runTest { val isPowerButtonDown by collectLastValue(underTest.isPowerButtonDown) @@ -77,4 +87,54 @@ class KeyEventRepositoryTest : SysuiTestCase() { ) assertThat(isPowerButtonDown).isFalse() } + + + @Test + fun isPowerButtonBeingLongPressed_onPowerButtonDown() = + testScope.runTest { + val isPowerButtonLongPressed by collectLastValue( + underTest.isPowerButtonLongPressed) + + runCurrent() + + verify(commandQueue).addCallback(commandQueueCallbacks.capture()) + + val keyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_POWER) + commandQueueCallbacks.value.handleSystemKey(keyEvent) + + assertThat(isPowerButtonLongPressed).isFalse() + } + + @Test + fun isPowerButtonBeingLongPressed_onPowerButtonUp() = + testScope.runTest { + val isPowerButtonLongPressed by collectLastValue( + underTest.isPowerButtonLongPressed) + + runCurrent() + + verify(commandQueue).addCallback(commandQueueCallbacks.capture()) + + val keyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_POWER) + commandQueueCallbacks.value.handleSystemKey(keyEvent) + + assertThat(isPowerButtonLongPressed).isFalse() + } + + @Test + fun isPowerButtonBeingLongPressed_onPowerButtonDown_longPressFlagSet() = + testScope.runTest { + val isPowerButtonBeingLongPressed by collectLastValue( + underTest.isPowerButtonLongPressed) + + runCurrent() + + verify(commandQueue).addCallback(commandQueueCallbacks.capture()) + + val keyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_POWER) + keyEvent.setFlags(KeyEvent.FLAG_LONG_PRESS) + commandQueueCallbacks.value.handleSystemKey(keyEvent) + + assertThat(isPowerButtonBeingLongPressed).isTrue() + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardStateCallbackInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardStateCallbackInteractorTest.kt index 2558d583b001..89a53f5722ac 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardStateCallbackInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardStateCallbackInteractorTest.kt @@ -49,6 +49,7 @@ import org.mockito.kotlin.verify @SmallTest @RunWith(AndroidJUnit4::class) +@EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR) class KeyguardStateCallbackInteractorTest : SysuiTestCase() { private val kosmos = testKosmos() @@ -81,7 +82,6 @@ class KeyguardStateCallbackInteractorTest : SysuiTestCase() { } @Test - @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR) fun test_lockscreenVisibility_notifyDismissSucceeded_ifNotVisible() = testScope.runTest { underTest.addCallback(callback) @@ -109,7 +109,6 @@ class KeyguardStateCallbackInteractorTest : SysuiTestCase() { } @Test - @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR) fun test_lockscreenVisibility_reportsKeyguardShowingChanged() = testScope.runTest { underTest.addCallback(callback) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractorTest.kt index e203a276a2f2..1dddfc1bba9c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractorTest.kt @@ -18,11 +18,17 @@ package com.android.systemui.keyguard.domain.interactor import android.content.Intent +import android.os.PowerManager +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.SetFlagsRule +import android.provider.Settings import android.view.accessibility.accessibilityManagerWrapper import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.logging.testing.UiEventLoggerFake import com.android.internal.logging.uiEventLogger +import com.android.systemui.Flags.FLAG_DOUBLE_TAP_TO_SLEEP import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor @@ -39,6 +45,8 @@ import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper import com.android.systemui.testKosmos import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.settings.data.repository.userAwareSecureSettingsRepository +import com.android.systemui.util.time.fakeSystemClock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.advanceTimeBy @@ -46,14 +54,19 @@ import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.After import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith +import org.mockito.Mock import org.mockito.ArgumentMatchers.anyInt +import org.mockito.ArgumentMatchers.anyLong +import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @SmallTest @RunWith(AndroidJUnit4::class) +@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class) class KeyguardTouchHandlingInteractorTest : SysuiTestCase() { private val kosmos = testKosmos().apply { @@ -61,17 +74,23 @@ class KeyguardTouchHandlingInteractorTest : SysuiTestCase() { this.uiEventLogger = mock<UiEventLoggerFake>() } + @get:Rule val setFlagsRule = SetFlagsRule() + private lateinit var underTest: KeyguardTouchHandlingInteractor private val logger = kosmos.uiEventLogger private val testScope = kosmos.testScope private val keyguardRepository = kosmos.fakeKeyguardRepository private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository + private val secureSettingsRepository = kosmos.userAwareSecureSettingsRepository + + @Mock private lateinit var powerManager: PowerManager @Before fun setUp() { MockitoAnnotations.initMocks(this) overrideResource(R.bool.long_press_keyguard_customize_lockscreen_enabled, true) + overrideResource(com.android.internal.R.bool.config_supportDoubleTapSleep, true) whenever(kosmos.accessibilityManagerWrapper.getRecommendedTimeoutMillis(anyInt(), anyInt())) .thenAnswer { it.arguments[0] } @@ -80,13 +99,13 @@ class KeyguardTouchHandlingInteractorTest : SysuiTestCase() { @After fun tearDown() { - mContext - .getOrCreateTestableResources() - .removeOverride(R.bool.long_press_keyguard_customize_lockscreen_enabled) + val testableResource = mContext.getOrCreateTestableResources() + testableResource.removeOverride(R.bool.long_press_keyguard_customize_lockscreen_enabled) + testableResource.removeOverride(com.android.internal.R.bool.config_supportDoubleTapSleep) } @Test - fun isEnabled() = + fun isLongPressEnabled() = testScope.runTest { val isEnabled = collectLastValue(underTest.isLongPressHandlingEnabled) KeyguardState.values().forEach { keyguardState -> @@ -101,7 +120,7 @@ class KeyguardTouchHandlingInteractorTest : SysuiTestCase() { } @Test - fun isEnabled_alwaysFalseWhenQuickSettingsAreVisible() = + fun isLongPressEnabled_alwaysFalseWhenQuickSettingsAreVisible() = testScope.runTest { val isEnabled = collectLastValue(underTest.isLongPressHandlingEnabled) KeyguardState.values().forEach { keyguardState -> @@ -112,7 +131,7 @@ class KeyguardTouchHandlingInteractorTest : SysuiTestCase() { } @Test - fun isEnabled_alwaysFalseWhenConfigEnabledBooleanIsFalse() = + fun isLongPressEnabled_alwaysFalseWhenConfigEnabledBooleanIsFalse() = testScope.runTest { overrideResource(R.bool.long_press_keyguard_customize_lockscreen_enabled, false) createUnderTest() @@ -294,6 +313,119 @@ class KeyguardTouchHandlingInteractorTest : SysuiTestCase() { assertThat(isMenuVisible).isFalse() } + @Test + @EnableFlags(FLAG_DOUBLE_TAP_TO_SLEEP) + fun isDoubleTapEnabled_flagEnabled_userSettingEnabled_onlyTrueInLockScreenState() { + testScope.runTest { + secureSettingsRepository.setBoolean(Settings.Secure.DOUBLE_TAP_TO_SLEEP, true) + + val isEnabled = collectLastValue(underTest.isDoubleTapHandlingEnabled) + KeyguardState.entries.forEach { keyguardState -> + setUpState(keyguardState = keyguardState) + + if (keyguardState == KeyguardState.LOCKSCREEN) { + assertThat(isEnabled()).isTrue() + } else { + assertThat(isEnabled()).isFalse() + } + } + } + } + + @Test + @EnableFlags(FLAG_DOUBLE_TAP_TO_SLEEP) + fun isDoubleTapEnabled_flagEnabled_userSettingDisabled_alwaysFalse() { + testScope.runTest { + secureSettingsRepository.setBoolean(Settings.Secure.DOUBLE_TAP_TO_SLEEP, false) + + val isEnabled = collectLastValue(underTest.isDoubleTapHandlingEnabled) + KeyguardState.entries.forEach { keyguardState -> + setUpState(keyguardState = keyguardState) + + assertThat(isEnabled()).isFalse() + } + } + } + + @Test + @DisableFlags(FLAG_DOUBLE_TAP_TO_SLEEP) + fun isDoubleTapEnabled_flagDisabled_userSettingEnabled_alwaysFalse() { + testScope.runTest { + secureSettingsRepository.setBoolean(Settings.Secure.DOUBLE_TAP_TO_SLEEP, true) + + val isEnabled = collectLastValue(underTest.isDoubleTapHandlingEnabled) + KeyguardState.entries.forEach { keyguardState -> + setUpState(keyguardState = keyguardState) + + assertThat(isEnabled()).isFalse() + } + } + } + + + + @Test + @EnableFlags(FLAG_DOUBLE_TAP_TO_SLEEP) + fun isDoubleTapEnabled_flagEnabledAndConfigDisabled_alwaysFalse() { + testScope.runTest { + secureSettingsRepository.setBoolean(Settings.Secure.DOUBLE_TAP_TO_SLEEP, true) + overrideResource(com.android.internal.R.bool.config_supportDoubleTapSleep, false) + createUnderTest() + + val isEnabled = collectLastValue(underTest.isDoubleTapHandlingEnabled) + KeyguardState.entries.forEach { keyguardState -> + setUpState(keyguardState = keyguardState) + + assertThat(isEnabled()).isFalse() + } + } + } + + @Test + @EnableFlags(FLAG_DOUBLE_TAP_TO_SLEEP) + fun isDoubleTapEnabled_quickSettingsVisible_alwaysFalse() { + testScope.runTest { + secureSettingsRepository.setBoolean(Settings.Secure.DOUBLE_TAP_TO_SLEEP, true) + + val isEnabled = collectLastValue(underTest.isDoubleTapHandlingEnabled) + KeyguardState.entries.forEach { keyguardState -> + setUpState(keyguardState = keyguardState, isQuickSettingsVisible = true) + + assertThat(isEnabled()).isFalse() + } + } + } + + @Test + @EnableFlags(FLAG_DOUBLE_TAP_TO_SLEEP) + fun onDoubleClick_doubleTapEnabled() { + testScope.runTest { + secureSettingsRepository.setBoolean(Settings.Secure.DOUBLE_TAP_TO_SLEEP, true) + val isEnabled by collectLastValue(underTest.isDoubleTapHandlingEnabled) + runCurrent() + + underTest.onDoubleClick() + + assertThat(isEnabled).isTrue() + verify(powerManager).goToSleep(anyLong()) + } + } + + @Test + @EnableFlags(FLAG_DOUBLE_TAP_TO_SLEEP) + fun onDoubleClick_doubleTapDisabled() { + testScope.runTest { + secureSettingsRepository.setBoolean(Settings.Secure.DOUBLE_TAP_TO_SLEEP, false) + val isEnabled by collectLastValue(underTest.isDoubleTapHandlingEnabled) + runCurrent() + + underTest.onDoubleClick() + + assertThat(isEnabled).isFalse() + verify(powerManager, never()).goToSleep(anyLong()) + } + } + private suspend fun createUnderTest(isRevampedWppFeatureEnabled: Boolean = true) { // This needs to be re-created for each test outside of kosmos since the flag values are // read during initialization to set up flows. Maybe there is a better way to handle that. @@ -309,6 +441,9 @@ class KeyguardTouchHandlingInteractorTest : SysuiTestCase() { accessibilityManager = kosmos.accessibilityManagerWrapper, pulsingGestureListener = kosmos.pulsingGestureListener, faceAuthInteractor = kosmos.deviceEntryFaceAuthInteractor, + secureSettingsRepository = secureSettingsRepository, + powerManager = powerManager, + systemClock = kosmos.fakeSystemClock, ) setUpState() } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToPrimaryBouncerTransitionViewModelTest.kt index 8533134fd94e..95a6e56717fa 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToPrimaryBouncerTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToPrimaryBouncerTransitionViewModelTest.kt @@ -21,15 +21,20 @@ import android.platform.test.flag.junit.FlagsParameterization import androidx.test.filters.SmallTest import com.android.systemui.Flags.FLAG_NOTIFICATION_SHADE_BLUR import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues import com.android.systemui.flags.BrokenWithSceneContainer +import com.android.systemui.flags.DisableSceneContainer import com.android.systemui.flags.andSceneContainer +import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.keyguard.ui.transitions.blurConfig import com.android.systemui.kosmos.testScope import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test @@ -43,6 +48,7 @@ class OccludedToPrimaryBouncerTransitionViewModelTest(flags: FlagsParameterizati SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope + private val repository = kosmos.fakeKeyguardTransitionRepository private lateinit var underTest: OccludedToPrimaryBouncerTransitionViewModel companion object { @@ -63,6 +69,25 @@ class OccludedToPrimaryBouncerTransitionViewModelTest(flags: FlagsParameterizati } @Test + @DisableSceneContainer + fun lockscreenAlphaImmediatelyToZero() = + testScope.runTest { + val alpha by collectLastValue(underTest.lockscreenAlpha) + + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + runCurrent() + assertThat(alpha).isEqualTo(0f) + + repository.sendTransitionStep(step(0.1f, TransitionState.RUNNING)) + runCurrent() + assertThat(alpha).isEqualTo(0f) + + repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) + runCurrent() + assertThat(alpha).isEqualTo(0f) + } + + @Test @BrokenWithSceneContainer(388068805) fun notificationsAreBlurredImmediatelyWhenBouncerIsOpenedAndShadeIsExpanded() = testScope.runTest { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/log/LogWtfHandlerRuleTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/log/LogWtfHandlerRuleTest.kt new file mode 100644 index 000000000000..d5d256e5cd97 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/log/LogWtfHandlerRuleTest.kt @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.log + +import android.util.Log +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.model.Statement +import org.mockito.kotlin.mock + +@RunWith(AndroidJUnit4::class) +@SmallTest +class LogWtfHandlerRuleTest : SysuiTestCase() { + + val underTest = LogWtfHandlerRule() + + @Test + fun passingTestWithoutWtf_shouldPass() { + val result = runTestCodeWithRule { + Log.e(TAG, "just an error", IndexOutOfBoundsException()) + } + assertThat(result.isSuccess).isTrue() + } + + @Test + fun passingTestWithWtf_shouldFail() { + val result = runTestCodeWithRule { + Log.wtf(TAG, "some terrible failure", IllegalStateException()) + } + assertThat(result.isFailure).isTrue() + val exception = result.exceptionOrNull() + assertThat(exception).isInstanceOf(AssertionError::class.java) + assertThat(exception?.cause).isInstanceOf(Log.TerribleFailure::class.java) + assertThat(exception?.cause?.cause).isInstanceOf(IllegalStateException::class.java) + } + + @Test + fun failingTestWithoutWtf_shouldFail() { + val result = runTestCodeWithRule { + Log.e(TAG, "just an error", IndexOutOfBoundsException()) + throw NullPointerException("some npe") + } + assertThat(result.isFailure).isTrue() + assertThat(result.exceptionOrNull()).isInstanceOf(NullPointerException::class.java) + } + + @Test + fun failingTestWithWtf_shouldFail() { + val result = runTestCodeWithRule { + Log.wtf(TAG, "some terrible failure", IllegalStateException()) + throw NullPointerException("some npe") + } + assertThat(result.isFailure).isTrue() + assertThat(result.exceptionOrNull()).isInstanceOf(NullPointerException::class.java) + val suppressedExceptions = result.exceptionOrNull()!!.suppressedExceptions + assertThat(suppressedExceptions).hasSize(1) + val suppressed = suppressedExceptions.first() + assertThat(suppressed).isInstanceOf(AssertionError::class.java) + assertThat(suppressed.cause).isInstanceOf(Log.TerribleFailure::class.java) + assertThat(suppressed.cause?.cause).isInstanceOf(IllegalStateException::class.java) + } + + @Test + fun passingTestWithExemptWtf_shouldPass() { + underTest.addFailureLogExemption { it.tag == TAG_EXPECTED } + val result = runTestCodeWithRule { + Log.wtf(TAG_EXPECTED, "some expected failure", IllegalStateException()) + } + assertThat(result.isSuccess).isTrue() + } + + @Test + fun failingTestWithExemptWtf_shouldFail() { + underTest.addFailureLogExemption { it.tag == TAG_EXPECTED } + val result = runTestCodeWithRule { + Log.wtf(TAG_EXPECTED, "some expected failure", IllegalStateException()) + throw NullPointerException("some npe") + } + assertThat(result.isFailure).isTrue() + assertThat(result.exceptionOrNull()).isInstanceOf(NullPointerException::class.java) + val suppressedExceptions = result.exceptionOrNull()!!.suppressedExceptions + assertThat(suppressedExceptions).isEmpty() + } + + @Test + fun passingTestWithOneExemptWtfOfTwo_shouldFail() { + underTest.addFailureLogExemption { it.tag == TAG_EXPECTED } + val result = runTestCodeWithRule { + Log.wtf(TAG_EXPECTED, "some expected failure", IllegalStateException()) + Log.wtf(TAG, "some terrible failure", IllegalStateException()) + } + assertThat(result.isFailure).isTrue() + val exception = result.exceptionOrNull() + assertThat(exception).isInstanceOf(AssertionError::class.java) + assertThat(exception?.cause).isInstanceOf(Log.TerribleFailure::class.java) + assertThat(exception?.cause?.cause).isInstanceOf(IllegalStateException::class.java) + } + + @Test + fun failingTestWithOneExemptWtfOfTwo_shouldFail() { + underTest.addFailureLogExemption { it.tag == TAG_EXPECTED } + val result = runTestCodeWithRule { + Log.wtf(TAG_EXPECTED, "some expected failure", IllegalStateException()) + Log.wtf(TAG, "some terrible failure", IllegalStateException()) + throw NullPointerException("some npe") + } + assertThat(result.isFailure).isTrue() + assertThat(result.exceptionOrNull()).isInstanceOf(NullPointerException::class.java) + val suppressedExceptions = result.exceptionOrNull()!!.suppressedExceptions + assertThat(suppressedExceptions).hasSize(1) + val suppressed = suppressedExceptions.first() + assertThat(suppressed).isInstanceOf(AssertionError::class.java) + assertThat(suppressed.cause).isInstanceOf(Log.TerribleFailure::class.java) + assertThat(suppressed.cause?.cause).isInstanceOf(IllegalStateException::class.java) + } + + private fun runTestCodeWithRule(testCode: () -> Unit): Result<Unit> { + val testCodeStatement = + object : Statement() { + override fun evaluate() { + testCode() + } + } + val wrappedTest = underTest.apply(testCodeStatement, mock()) + return try { + wrappedTest.evaluate() + Result.success(Unit) + } catch (e: Throwable) { + Result.failure(e) + } + } + + companion object { + const val TAG = "LogWtfHandlerRuleTest" + const val TAG_EXPECTED = "EXPECTED" + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/NotificationMediaManagerTest.kt index a7fe586cbfa5..10b00857e887 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/NotificationMediaManagerTest.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.statusbar +package com.android.systemui.media import android.media.MediaMetadata import android.media.session.MediaController diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/MediaOutputAdapterLegacyTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/MediaOutputAdapterLegacyTest.java index 2db2199602b8..9c4d93c17d00 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/MediaOutputAdapterLegacyTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/MediaOutputAdapterLegacyTest.java @@ -48,10 +48,12 @@ import androidx.test.filters.SmallTest; import com.android.media.flags.Flags; import com.android.settingslib.media.LocalMediaManager; import com.android.settingslib.media.MediaDevice; +import com.android.settingslib.utils.ThreadUtils; import com.android.systemui.SysuiTestCase; import com.android.systemui.res.R; import com.google.common.collect.ImmutableList; +import com.google.common.util.concurrent.ListeningExecutorService; import org.junit.Before; import org.junit.Test; @@ -61,6 +63,7 @@ import org.mockito.Captor; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.Executor; import java.util.stream.Collectors; @SmallTest @@ -95,6 +98,8 @@ public class MediaOutputAdapterLegacyTest extends SysuiTestCase { private List<MediaDevice> mMediaDevices = new ArrayList<>(); private List<MediaItem> mMediaItems = new ArrayList<>(); MediaOutputSeekbar mSpyMediaOutputSeekbar; + Executor mMainExecutor = mContext.getMainExecutor(); + ListeningExecutorService mBackgroundExecutor = ThreadUtils.getBackgroundExecutor(); @Before public void setUp() { @@ -108,6 +113,8 @@ public class MediaOutputAdapterLegacyTest extends SysuiTestCase { when(mMediaSwitchingController.getSessionVolumeMax()).thenReturn(TEST_MAX_VOLUME); when(mMediaSwitchingController.getSessionVolume()).thenReturn(TEST_CURRENT_VOLUME); when(mMediaSwitchingController.getSessionName()).thenReturn(TEST_SESSION_NAME); + when(mMediaSwitchingController.getColorSchemeLegacy()).thenReturn( + mock(MediaOutputColorSchemeLegacy.class)); when(mIconCompat.toIcon(mContext)).thenReturn(mIcon); when(mMediaDevice1.getName()).thenReturn(TEST_DEVICE_NAME_1); when(mMediaDevice1.getId()).thenReturn(TEST_DEVICE_ID_1); @@ -122,7 +129,8 @@ public class MediaOutputAdapterLegacyTest extends SysuiTestCase { mMediaItems.add(MediaItem.createDeviceMediaItem(mMediaDevice1, true)); mMediaItems.add(MediaItem.createDeviceMediaItem(mMediaDevice2, false)); - mMediaOutputAdapter = new MediaOutputAdapterLegacy(mMediaSwitchingController); + mMediaOutputAdapter = new MediaOutputAdapterLegacy(mMediaSwitchingController, mMainExecutor, + mBackgroundExecutor); mMediaOutputAdapter.updateItems(); mViewHolder = (MediaOutputAdapterLegacy.MediaDeviceViewHolderLegacy) mMediaOutputAdapter .onCreateViewHolder(new LinearLayout(mContext), 0); @@ -148,7 +156,8 @@ public class MediaOutputAdapterLegacyTest extends SysuiTestCase { @Test public void onBindViewHolder_bindPairNew_verifyView() { - mMediaOutputAdapter = new MediaOutputAdapterLegacy(mMediaSwitchingController); + mMediaOutputAdapter = new MediaOutputAdapterLegacy(mMediaSwitchingController, mMainExecutor, + mBackgroundExecutor); mMediaOutputAdapter.updateItems(); mViewHolder = (MediaOutputAdapterLegacy.MediaDeviceViewHolderLegacy) mMediaOutputAdapter .onCreateViewHolder(new LinearLayout(mContext), 0); @@ -173,7 +182,8 @@ public class MediaOutputAdapterLegacyTest extends SysuiTestCase { .map((item) -> item.getMediaDevice().get()) .collect(Collectors.toList())); when(mMediaSwitchingController.getSessionName()).thenReturn(TEST_SESSION_NAME); - mMediaOutputAdapter = new MediaOutputAdapterLegacy(mMediaSwitchingController); + mMediaOutputAdapter = new MediaOutputAdapterLegacy(mMediaSwitchingController, mMainExecutor, + mBackgroundExecutor); mMediaOutputAdapter.updateItems(); mViewHolder = (MediaOutputAdapterLegacy.MediaDeviceViewHolderLegacy) mMediaOutputAdapter .onCreateViewHolder(new LinearLayout(mContext), 0); @@ -195,7 +205,8 @@ public class MediaOutputAdapterLegacyTest extends SysuiTestCase { .map((item) -> item.getMediaDevice().get()) .collect(Collectors.toList())); when(mMediaSwitchingController.getSessionName()).thenReturn(null); - mMediaOutputAdapter = new MediaOutputAdapterLegacy(mMediaSwitchingController); + mMediaOutputAdapter = new MediaOutputAdapterLegacy(mMediaSwitchingController, mMainExecutor, + mBackgroundExecutor); mMediaOutputAdapter.updateItems(); mViewHolder = (MediaOutputAdapterLegacy.MediaDeviceViewHolderLegacy) mMediaOutputAdapter .onCreateViewHolder(new LinearLayout(mContext), 0); @@ -665,7 +676,8 @@ public class MediaOutputAdapterLegacyTest extends SysuiTestCase { @Test public void onItemClick_clickPairNew_verifyLaunchBluetoothPairing() { - mMediaOutputAdapter = new MediaOutputAdapterLegacy(mMediaSwitchingController); + mMediaOutputAdapter = new MediaOutputAdapterLegacy(mMediaSwitchingController, mMainExecutor, + mBackgroundExecutor); mMediaOutputAdapter.updateItems(); mViewHolder = (MediaOutputAdapterLegacy.MediaDeviceViewHolderLegacy) mMediaOutputAdapter .onCreateViewHolder(new LinearLayout(mContext), 0); @@ -683,7 +695,8 @@ public class MediaOutputAdapterLegacyTest extends SysuiTestCase { assertThat(mMediaDevice2.getState()).isEqualTo( LocalMediaManager.MediaDeviceState.STATE_DISCONNECTED); when(mMediaDevice2.getSelectionBehavior()).thenReturn(SELECTION_BEHAVIOR_TRANSFER); - mMediaOutputAdapter = new MediaOutputAdapterLegacy(mMediaSwitchingController); + mMediaOutputAdapter = new MediaOutputAdapterLegacy(mMediaSwitchingController, mMainExecutor, + mBackgroundExecutor); mMediaOutputAdapter.updateItems(); mViewHolder = (MediaOutputAdapterLegacy.MediaDeviceViewHolderLegacy) mMediaOutputAdapter .onCreateViewHolder(new LinearLayout(mContext), 0); @@ -701,7 +714,8 @@ public class MediaOutputAdapterLegacyTest extends SysuiTestCase { assertThat(mMediaDevice2.getState()).isEqualTo( LocalMediaManager.MediaDeviceState.STATE_DISCONNECTED); when(mMediaDevice2.getSelectionBehavior()).thenReturn(SELECTION_BEHAVIOR_TRANSFER); - mMediaOutputAdapter = new MediaOutputAdapterLegacy(mMediaSwitchingController); + mMediaOutputAdapter = new MediaOutputAdapterLegacy(mMediaSwitchingController, + mContext.getMainExecutor(), ThreadUtils.getBackgroundExecutor()); mMediaOutputAdapter.updateItems(); mViewHolder = (MediaOutputAdapterLegacy.MediaDeviceViewHolderLegacy) mMediaOutputAdapter .onCreateViewHolder(new LinearLayout(mContext), 0); @@ -723,7 +737,8 @@ public class MediaOutputAdapterLegacyTest extends SysuiTestCase { when(mMediaDevice2.getState()).thenReturn( LocalMediaManager.MediaDeviceState.STATE_DISCONNECTED); when(mMediaDevice2.getSelectionBehavior()).thenReturn(SELECTION_BEHAVIOR_GO_TO_APP); - mMediaOutputAdapter = new MediaOutputAdapterLegacy(mMediaSwitchingController); + mMediaOutputAdapter = new MediaOutputAdapterLegacy(mMediaSwitchingController, mMainExecutor, + mBackgroundExecutor); mMediaOutputAdapter.updateItems(); mViewHolder = (MediaOutputAdapterLegacy.MediaDeviceViewHolderLegacy) mMediaOutputAdapter .onCreateViewHolder(new LinearLayout(mContext), 0); @@ -778,6 +793,8 @@ public class MediaOutputAdapterLegacyTest extends SysuiTestCase { assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.VISIBLE); assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE); assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(TEST_DEVICE_NAME_2); + assertThat(mViewHolder.mTitleText.getAlpha()) + .isEqualTo(MediaOutputAdapterLegacy.DEVICE_ACTIVE_ALPHA); assertThat(mViewHolder.mContainerLayout.isFocusable()).isTrue(); mViewHolder.mContainerLayout.performClick(); @@ -799,6 +816,8 @@ public class MediaOutputAdapterLegacyTest extends SysuiTestCase { assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.VISIBLE); assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE); assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(TEST_DEVICE_NAME_2); + assertThat(mViewHolder.mTitleText.getAlpha()) + .isEqualTo(MediaOutputAdapterLegacy.DEVICE_ACTIVE_ALPHA); assertThat(mViewHolder.mContainerLayout.isFocusable()).isTrue(); mViewHolder.mContainerLayout.performClick(); @@ -820,6 +839,8 @@ public class MediaOutputAdapterLegacyTest extends SysuiTestCase { assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.VISIBLE); assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE); assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(TEST_DEVICE_NAME_2); + assertThat(mViewHolder.mTitleText.getAlpha()) + .isEqualTo(MediaOutputAdapterLegacy.DEVICE_ACTIVE_ALPHA); assertThat(mViewHolder.mContainerLayout.isFocusable()).isTrue(); mViewHolder.mContainerLayout.performClick(); @@ -841,6 +862,8 @@ public class MediaOutputAdapterLegacyTest extends SysuiTestCase { assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.VISIBLE); assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE); assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(TEST_DEVICE_NAME_2); + assertThat(mViewHolder.mTitleText.getAlpha()) + .isEqualTo(MediaOutputAdapterLegacy.DEVICE_ACTIVE_ALPHA); assertThat(mViewHolder.mContainerLayout.isFocusable()).isTrue(); mViewHolder.mContainerLayout.performClick(); @@ -862,6 +885,8 @@ public class MediaOutputAdapterLegacyTest extends SysuiTestCase { assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.VISIBLE); assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE); assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(TEST_DEVICE_NAME_2); + assertThat(mViewHolder.mTitleText.getAlpha()) + .isEqualTo(MediaOutputAdapterLegacy.DEVICE_ACTIVE_ALPHA); assertThat(mViewHolder.mContainerLayout.isFocusable()).isTrue(); mViewHolder.mContainerLayout.performClick(); @@ -883,6 +908,8 @@ public class MediaOutputAdapterLegacyTest extends SysuiTestCase { assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.VISIBLE); assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE); assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(TEST_DEVICE_NAME_2); + assertThat(mViewHolder.mTitleText.getAlpha()) + .isEqualTo(MediaOutputAdapterLegacy.DEVICE_DISABLED_ALPHA); assertThat(mViewHolder.mContainerLayout.isFocusable()).isTrue(); mViewHolder.mContainerLayout.performClick(); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/ui/viewmodel/TileRequestDialogViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/ui/viewmodel/TileRequestDialogViewModelTest.kt index 3029928f070f..cb13b118fd68 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/ui/viewmodel/TileRequestDialogViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/ui/viewmodel/TileRequestDialogViewModelTest.kt @@ -25,6 +25,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.app.iUriGrantsManager import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.ui.viewmodel.iconProvider import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.runCurrent import com.android.systemui.kosmos.runTest @@ -32,6 +33,8 @@ import com.android.systemui.kosmos.testScope import com.android.systemui.lifecycle.activateIn import com.android.systemui.plugins.qs.QSTile import com.android.systemui.qs.external.TileData +import com.android.systemui.qs.panels.ui.viewmodel.IconProvider +import com.android.systemui.qs.panels.ui.viewmodel.toIconProvider import com.android.systemui.qs.panels.ui.viewmodel.toUiState import com.android.systemui.qs.tileimpl.QSTileImpl import com.android.systemui.qs.tileimpl.QSTileImpl.ResourceIcon @@ -80,28 +83,32 @@ class TileRequestDialogViewModelTest : SysuiTestCase() { @Test fun uiState_beforeActivation_hasDefaultIcon_andCorrectData() = kosmos.runTest { - val expectedState = - baseResultLegacyState.apply { icon = defaultIcon }.toUiState(mainResources) + val state = baseResultLegacyState.apply { icon = defaultIcon } + + val expectedState = state.toUiState(mainResources) + val expectedIconProvider = state.toIconProvider() with(underTest.uiState) { expect.that(label).isEqualTo(TEST_LABEL) expect.that(secondaryLabel).isEmpty() - expect.that(state).isEqualTo(expectedState.state) + expect.that(this.state).isEqualTo(expectedState.state) expect.that(handlesLongClick).isFalse() expect.that(handlesSecondaryClick).isFalse() - expect.that(icon).isEqualTo(defaultIcon) expect.that(sideDrawable).isNull() expect.that(accessibilityUiState).isEqualTo(expectedState.accessibilityUiState) } + + expect.that(underTest.iconProvider).isEqualTo(expectedIconProvider) } @Test fun uiState_afterActivation_hasCorrectIcon_andCorrectData() = kosmos.runTest { - val expectedState = - baseResultLegacyState - .apply { icon = QSTileImpl.DrawableIcon(loadedDrawable) } - .toUiState(mainResources) + val state = + baseResultLegacyState.apply { icon = QSTileImpl.DrawableIcon(loadedDrawable) } + + val expectedState = state.toUiState(mainResources) + val expectedIconProvider = state.toIconProvider() underTest.activateIn(testScope) runCurrent() @@ -109,13 +116,13 @@ class TileRequestDialogViewModelTest : SysuiTestCase() { with(underTest.uiState) { expect.that(label).isEqualTo(TEST_LABEL) expect.that(secondaryLabel).isEmpty() - expect.that(state).isEqualTo(expectedState.state) + expect.that(this.state).isEqualTo(expectedState.state) expect.that(handlesLongClick).isFalse() expect.that(handlesSecondaryClick).isFalse() - expect.that(icon).isEqualTo(QSTileImpl.DrawableIcon(loadedDrawable)) expect.that(sideDrawable).isNull() expect.that(accessibilityUiState).isEqualTo(expectedState.accessibilityUiState) } + expect.that(underTest.iconProvider).isEqualTo(expectedIconProvider) } @Test @@ -135,7 +142,7 @@ class TileRequestDialogViewModelTest : SysuiTestCase() { underTest.activateIn(testScope) runCurrent() - assertThat(underTest.uiState.icon).isEqualTo(defaultIcon) + assertThat(underTest.iconProvider).isEqualTo(IconProvider.ConstantIcon(defaultIcon)) } companion object { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModelTest.kt index 68a591dd075f..1d42424bc6ed 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModelTest.kt @@ -16,11 +16,9 @@ package com.android.systemui.qs.panels.ui.viewmodel - import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testScope import com.android.systemui.qs.FakeQSTile import com.android.systemui.qs.pipeline.data.repository.tileSpecRepository @@ -48,45 +46,43 @@ class DetailsViewModelTest : SysuiTestCase() { } @Test - fun changeTileDetailsViewModel() = with(kosmos) { - testScope.runTest { - val specs = listOf( - spec, - specNoDetails, - ) - tileSpecRepository.setTiles(0, specs) - runCurrent() + fun changeTileDetailsViewModel() = + with(kosmos) { + testScope.runTest { + val specs = listOf(spec, specNoDetails) + tileSpecRepository.setTiles(0, specs) + runCurrent() - val tiles = currentTilesInteractor.currentTiles.value + val tiles = currentTilesInteractor.currentTiles.value - assertThat(currentTilesInteractor.currentTilesSpecs.size).isEqualTo(2) - assertThat(tiles[1].spec).isEqualTo(specNoDetails) - (tiles[1].tile as FakeQSTile).hasDetailsViewModel = false + assertThat(currentTilesInteractor.currentTilesSpecs.size).isEqualTo(2) + assertThat(tiles[1].spec).isEqualTo(specNoDetails) + (tiles[1].tile as FakeQSTile).hasDetailsViewModel = false - assertThat(underTest.activeTileDetails).isNull() + assertThat(underTest.activeTileDetails).isNull() - // Click on the tile who has the `spec`. - assertThat(underTest.onTileClicked(spec)).isTrue() - assertThat(underTest.activeTileDetails).isNotNull() - assertThat(underTest.activeTileDetails?.getTitle()).isEqualTo("internet") + // Click on the tile who has the `spec`. + assertThat(underTest.onTileClicked(spec)).isTrue() + assertThat(underTest.activeTileDetails).isNotNull() + assertThat(underTest.activeTileDetails?.title).isEqualTo("internet") - // Click on a tile who dose not have a valid spec. - assertThat(underTest.onTileClicked(null)).isFalse() - assertThat(underTest.activeTileDetails).isNull() + // Click on a tile who dose not have a valid spec. + assertThat(underTest.onTileClicked(null)).isFalse() + assertThat(underTest.activeTileDetails).isNull() - // Click again on the tile who has the `spec`. - assertThat(underTest.onTileClicked(spec)).isTrue() - assertThat(underTest.activeTileDetails).isNotNull() - assertThat(underTest.activeTileDetails?.getTitle()).isEqualTo("internet") + // Click again on the tile who has the `spec`. + assertThat(underTest.onTileClicked(spec)).isTrue() + assertThat(underTest.activeTileDetails).isNotNull() + assertThat(underTest.activeTileDetails?.title).isEqualTo("internet") - // Click on a tile who dose not have a detailed view. - assertThat(underTest.onTileClicked(specNoDetails)).isFalse() - assertThat(underTest.activeTileDetails).isNull() + // Click on a tile who dose not have a detailed view. + assertThat(underTest.onTileClicked(specNoDetails)).isFalse() + assertThat(underTest.activeTileDetails).isNull() - underTest.closeDetailedView() - assertThat(underTest.activeTileDetails).isNull() + underTest.closeDetailedView() + assertThat(underTest.activeTileDetails).isNull() - assertThat(underTest.onTileClicked(null)).isFalse() + assertThat(underTest.onTileClicked(null)).isFalse() + } } - } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/IconProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/IconProviderTest.kt new file mode 100644 index 000000000000..7257a89c214b --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/IconProviderTest.kt @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.ui.viewmodel + +import android.graphics.drawable.TestStubDrawable +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.plugins.qs.QSTile +import com.android.systemui.qs.tileimpl.QSTileImpl +import com.android.systemui.qs.tileimpl.QSTileImpl.ResourceIcon +import com.android.systemui.res.R +import com.google.common.truth.Truth.assertThat +import java.util.function.Supplier +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class IconProviderTest : SysuiTestCase() { + + @Test + fun iconAndSupplier_prefersIcon() { + val state = + QSTile.State().apply { + icon = ResourceIcon.get(R.drawable.android) + iconSupplier = Supplier { QSTileImpl.DrawableIcon(TestStubDrawable()) } + } + val iconProvider = state.toIconProvider() + + assertThat(iconProvider).isEqualTo(IconProvider.ConstantIcon(state.icon)) + } + + @Test + fun iconOnly_hasIcon() { + val state = QSTile.State().apply { icon = ResourceIcon.get(R.drawable.android) } + val iconProvider = state.toIconProvider() + + assertThat(iconProvider).isEqualTo(IconProvider.ConstantIcon(state.icon)) + } + + @Test + fun supplierOnly_hasIcon() { + val state = + QSTile.State().apply { + iconSupplier = Supplier { ResourceIcon.get(R.drawable.android) } + } + val iconProvider = state.toIconProvider() + + assertThat(iconProvider).isEqualTo(IconProvider.IconSupplier(state.iconSupplier)) + } + + @Test + fun noIconOrSupplier_iconNull() { + val state = QSTile.State() + val iconProvider = state.toIconProvider() + + assertThat(iconProvider).isEqualTo(IconProvider.Empty) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiStateTest.kt index 9c8e3225f3a4..b144f0678471 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiStateTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiStateTest.kt @@ -18,7 +18,6 @@ package com.android.systemui.qs.panels.ui.viewmodel import android.content.res.Resources import android.content.res.mainResources -import android.graphics.drawable.TestStubDrawable import android.service.quicksettings.Tile import android.widget.Button import android.widget.Switch @@ -27,12 +26,9 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.plugins.qs.QSTile -import com.android.systemui.qs.tileimpl.QSTileImpl -import com.android.systemui.qs.tileimpl.QSTileImpl.ResourceIcon import com.android.systemui.res.R import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat -import java.util.function.Supplier import org.junit.Test import org.junit.runner.RunWith @@ -267,45 +263,6 @@ class TileUiStateTest : SysuiTestCase() { .contains(resources.getString(R.string.tile_unavailable)) } - @Test - fun iconAndSupplier_prefersIcon() { - val state = - QSTile.State().apply { - icon = ResourceIcon.get(R.drawable.android) - iconSupplier = Supplier { QSTileImpl.DrawableIcon(TestStubDrawable()) } - } - val uiState = state.toUiState() - - assertThat(uiState.icon).isEqualTo(state.icon) - } - - @Test - fun iconOnly_hasIcon() { - val state = QSTile.State().apply { icon = ResourceIcon.get(R.drawable.android) } - val uiState = state.toUiState() - - assertThat(uiState.icon).isEqualTo(state.icon) - } - - @Test - fun supplierOnly_hasIcon() { - val state = - QSTile.State().apply { - iconSupplier = Supplier { ResourceIcon.get(R.drawable.android) } - } - val uiState = state.toUiState() - - assertThat(uiState.icon).isEqualTo(state.iconSupplier.get()) - } - - @Test - fun noIconOrSupplier_iconNull() { - val state = QSTile.State() - val uiState = state.toUiState() - - assertThat(uiState.icon).isNull() - } - private fun QSTile.State.toUiState() = toUiState(resources) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt index c3089761effc..5bde7ad27b7a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt @@ -691,11 +691,11 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() { var currentModel: TileDetailsViewModel? = null val setCurrentModel = { model: TileDetailsViewModel? -> currentModel = model } tiles!![0].tile.getDetailsViewModel(setCurrentModel) - assertThat(currentModel?.getTitle()).isEqualTo("a") + assertThat(currentModel?.title).isEqualTo("a") currentModel = null tiles!![1].tile.getDetailsViewModel(setCurrentModel) - assertThat(currentModel?.getTitle()).isEqualTo("b") + assertThat(currentModel?.title).isEqualTo("b") currentModel = null tiles!![2].tile.getDetailsViewModel(setCurrentModel) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/interactor/HearingDevicesTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/interactor/HearingDevicesTileUserActionInteractorTest.kt index 00ee1c36590c..1b497a2b36ed 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/interactor/HearingDevicesTileUserActionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/interactor/HearingDevicesTileUserActionInteractorTest.kt @@ -24,6 +24,7 @@ import com.android.systemui.accessibility.hearingaid.HearingDevicesDialogManager import com.android.systemui.accessibility.hearingaid.HearingDevicesUiEventLogger.Companion.LAUNCH_SOURCE_QS_TILE import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope +import com.android.systemui.qs.shared.QSSettingsPackageRepository import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx @@ -40,6 +41,7 @@ import org.mockito.junit.MockitoJUnit import org.mockito.junit.MockitoRule import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever @SmallTest @EnabledOnRavenwood @@ -53,14 +55,18 @@ class HearingDevicesTileUserActionInteractorTest : SysuiTestCase() { @Rule @JvmField val mockitoRule: MockitoRule = MockitoJUnit.rule() @Mock private lateinit var dialogManager: HearingDevicesDialogManager + @Mock private lateinit var settingsPackageRepository: QSSettingsPackageRepository @Before fun setUp() { + whenever(settingsPackageRepository.getSettingsPackageName()) + .thenReturn(SETTINGS_PACKAGE_NAME) underTest = HearingDevicesTileUserActionInteractor( testScope.coroutineContext, inputHandler, dialogManager, + settingsPackageRepository, ) } @@ -91,6 +97,11 @@ class HearingDevicesTileUserActionInteractorTest : SysuiTestCase() { QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput { assertThat(it.intent.action).isEqualTo(Settings.ACTION_HEARING_DEVICES_SETTINGS) + assertThat(it.intent.`package`).isEqualTo(SETTINGS_PACKAGE_NAME) } } + + companion object { + private const val SETTINGS_PACKAGE_NAME = "com.android.settings" + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt index 9adf24f32c0c..1743e056b65c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt @@ -863,7 +863,7 @@ class SceneContainerStartableTest : SysuiTestCase() { whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true) val currentSceneKey by collectLastValue(sceneInteractor.currentScene) val playSuccessHaptic by - collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic) + collectLastValue(deviceEntryHapticsInteractor.playSuccessHapticOnDeviceEntry) setupBiometricAuth(hasUdfps = true) assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) @@ -885,7 +885,7 @@ class SceneContainerStartableTest : SysuiTestCase() { whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true) val currentSceneKey by collectLastValue(sceneInteractor.currentScene) val playSuccessHaptic by - collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic) + collectLastValue(deviceEntryHapticsInteractor.playSuccessHapticOnDeviceEntry) setupBiometricAuth(hasUdfps = true) assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) @@ -907,7 +907,7 @@ class SceneContainerStartableTest : SysuiTestCase() { whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true) val currentSceneKey by collectLastValue(sceneInteractor.currentScene) val playSuccessHaptic by - collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic) + collectLastValue(deviceEntryHapticsInteractor.playSuccessHapticOnDeviceEntry) setupBiometricAuth(hasSfps = true) assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) @@ -930,7 +930,7 @@ class SceneContainerStartableTest : SysuiTestCase() { whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true) val currentSceneKey by collectLastValue(sceneInteractor.currentScene) val playSuccessHaptic by - collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic) + collectLastValue(deviceEntryHapticsInteractor.playSuccessHapticOnDeviceEntry) setupBiometricAuth(hasSfps = true) assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) @@ -1033,7 +1033,7 @@ class SceneContainerStartableTest : SysuiTestCase() { whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true) val currentSceneKey by collectLastValue(sceneInteractor.currentScene) val playSuccessHaptic by - collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic) + collectLastValue(deviceEntryHapticsInteractor.playSuccessHapticOnDeviceEntry) setupBiometricAuth(hasSfps = true) assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) @@ -1056,7 +1056,7 @@ class SceneContainerStartableTest : SysuiTestCase() { whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true) val currentSceneKey by collectLastValue(sceneInteractor.currentScene) val playSuccessHaptic by - collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic) + collectLastValue(deviceEntryHapticsInteractor.playSuccessHapticOnDeviceEntry) setupBiometricAuth(hasSfps = true) assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) @@ -1079,7 +1079,7 @@ class SceneContainerStartableTest : SysuiTestCase() { whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true) val currentSceneKey by collectLastValue(sceneInteractor.currentScene) val playSuccessHaptic by - collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic) + collectLastValue(deviceEntryHapticsInteractor.playSuccessHapticOnDeviceEntry) setupBiometricAuth(hasSfps = true) assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) @@ -1102,7 +1102,7 @@ class SceneContainerStartableTest : SysuiTestCase() { whenever(kosmos.keyguardUpdateMonitor.isDeviceInteractive).thenReturn(true) val currentSceneKey by collectLastValue(sceneInteractor.currentScene) val playSuccessHaptic by - collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic) + collectLastValue(deviceEntryHapticsInteractor.playSuccessHapticOnDeviceEntry) setupBiometricAuth(hasSfps = true) assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) @@ -1160,7 +1160,7 @@ class SceneContainerStartableTest : SysuiTestCase() { @Test @DisableFlags(Flags.FLAG_MSDL_FEEDBACK) - fun playsFaceErrorHaptics_nonSfps_coEx() = + fun skipsFaceErrorHaptics_nonSfps_coEx() = testScope.runTest { val currentSceneKey by collectLastValue(sceneInteractor.currentScene) val playErrorHaptic by collectLastValue(deviceEntryHapticsInteractor.playErrorHaptic) @@ -1172,15 +1172,14 @@ class SceneContainerStartableTest : SysuiTestCase() { underTest.start() updateFaceAuthStatus(isSuccess = false) - assertThat(playErrorHaptic).isNotNull() - assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) - verify(vibratorHelper).vibrateAuthError(anyString()) + assertThat(playErrorHaptic).isNull() + verify(vibratorHelper, never()).vibrateAuthError(anyString()) verify(vibratorHelper, never()).vibrateAuthSuccess(anyString()) } @Test @EnableFlags(Flags.FLAG_MSDL_FEEDBACK) - fun playsMSDLFaceErrorHaptics_nonSfps_coEx() = + fun skipsMSDLFaceErrorHaptics_nonSfps_coEx() = testScope.runTest { val currentSceneKey by collectLastValue(sceneInteractor.currentScene) val playErrorHaptic by collectLastValue(deviceEntryHapticsInteractor.playErrorHaptic) @@ -1192,10 +1191,9 @@ class SceneContainerStartableTest : SysuiTestCase() { underTest.start() updateFaceAuthStatus(isSuccess = false) - assertThat(playErrorHaptic).isNotNull() - assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) - assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.FAILURE) - assertThat(msdlPlayer.latestPropertiesPlayed).isEqualTo(authInteractionProperties) + assertThat(playErrorHaptic).isNull() + assertThat(msdlPlayer.latestTokenPlayed).isNull() + assertThat(msdlPlayer.latestPropertiesPlayed).isNull() } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java index 3407cd50e76f..4a304071ee97 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java @@ -41,7 +41,6 @@ import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.graphics.Rect; import android.platform.test.flag.junit.FlagsParameterization; -import android.provider.Settings; import android.testing.TestableLooper.RunWithLooper; import android.view.View; import android.view.WindowManager; @@ -71,7 +70,6 @@ import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.user.domain.interactor.SelectedUserInteractor; -import com.android.systemui.util.settings.FakeSettings; import com.google.common.util.concurrent.MoreExecutors; @@ -113,7 +111,6 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { @Captor private ArgumentCaptor<WindowManager.LayoutParams> mLayoutParameters; @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStateListener; - private FakeSettings mSecureSettings; private final Executor mMainExecutor = MoreExecutors.directExecutor(); private final Executor mBackgroundExecutor = MoreExecutors.directExecutor(); private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this); @@ -135,9 +132,6 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { public void setUp() { MockitoAnnotations.initMocks(this); - mSecureSettings = new FakeSettings(); - mSecureSettings.putInt(Settings.Secure.DISABLE_SECURE_WINDOWS, 0); - // Preferred refresh rate is equal to the first displayMode's refresh rate mPreferredRefreshRate = mContext.getDisplay().getSystemSupportedModes()[0].getRefreshRate(); overrideResource( @@ -171,7 +165,6 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { () -> mSelectedUserInteractor, mUserTracker, mKosmos.getNotificationShadeWindowModel(), - mSecureSettings, mKosmos::getCommunalInteractor, mKosmos.getShadeLayoutParams()); mNotificationShadeWindowController.setScrimsVisibilityListener((visibility) -> {}); @@ -355,19 +348,6 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { } @Test - public void setKeyguardShowingWithSecureWindowsDisabled_disablesSecureFlag() { - mSecureSettings.putInt(Settings.Secure.DISABLE_SECURE_WINDOWS, 1); - mNotificationShadeWindowController.setBouncerShowing(true); - - verify(mWindowManager).updateViewLayout(any(), mLayoutParameters.capture()); - assertThat((mLayoutParameters.getValue().flags & FLAG_SECURE) == 0).isTrue(); - assertThat( - (mLayoutParameters.getValue().inputFeatures & INPUT_FEATURE_SENSITIVE_FOR_PRIVACY) - != 0) - .isTrue(); - } - - @Test public void setKeyguardNotShowing_disablesSecureFlag() { mNotificationShadeWindowController.setBouncerShowing(false); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicyTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicyTest.kt index e43c46b36a06..dd0ba00994ce 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicyTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicyTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.shade.display +import android.platform.test.annotations.EnableFlags import android.view.Display import android.view.Display.TYPE_EXTERNAL import android.view.MotionEvent @@ -31,6 +32,7 @@ import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.shade.data.repository.statusBarTouchShadeDisplayPolicy import com.android.systemui.shade.domain.interactor.notificationElement import com.android.systemui.shade.domain.interactor.qsElement +import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlin.test.Test @@ -41,6 +43,7 @@ import org.mockito.kotlin.mock @SmallTest @RunWith(AndroidJUnit4::class) +@EnableFlags(ShadeWindowGoesAround.FLAG_NAME) class StatusBarTouchShadeDisplayPolicyTest : SysuiTestCase() { private val kosmos = testKosmos().useUnconfinedTestDispatcher() private val testScope = kosmos.testScope diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorTest.kt index 03f546b09faf..24593f4455e5 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorTest.kt @@ -18,6 +18,7 @@ package com.android.systemui.shade.domain.interactor import android.content.res.Configuration import android.content.res.mockResources +import android.platform.test.annotations.EnableFlags import android.view.Display import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest @@ -27,6 +28,7 @@ import com.android.systemui.kosmos.testScope import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.scene.ui.view.mockShadeRootView import com.android.systemui.shade.data.repository.fakeShadeDisplaysRepository +import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs import com.android.systemui.statusbar.notification.row.notificationRebindingTracker @@ -47,6 +49,7 @@ import org.mockito.kotlin.whenever @RunWith(AndroidJUnit4::class) @SmallTest +@EnableFlags(ShadeWindowGoesAround.FLAG_NAME) class ShadeDisplaysInteractorTest : SysuiTestCase() { private val kosmos = testKosmos().useUnconfinedTestDispatcher() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt index a832f486ef32..04eb709b8894 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt @@ -94,6 +94,36 @@ class ShadeHeaderViewModelTest : SysuiTestCase() { } @Test + fun showClock_wideLayout_returnsTrue() = + testScope.runTest { + kosmos.enableDualShade(wideLayout = true) + + setupDualShadeState(scene = Scenes.Lockscreen, overlay = Overlays.NotificationsShade) + assertThat(underTest.showClock).isTrue() + + setupDualShadeState(scene = Scenes.Lockscreen, overlay = Overlays.QuickSettingsShade) + assertThat(underTest.showClock).isTrue() + } + + @Test + fun showClock_narrowLayoutOnNotificationsShade_returnsFalse() = + testScope.runTest { + kosmos.enableDualShade(wideLayout = false) + setupDualShadeState(scene = Scenes.Lockscreen, overlay = Overlays.NotificationsShade) + + assertThat(underTest.showClock).isFalse() + } + + @Test + fun showClock_narrowLayoutOnQuickSettingsShade_returnsTrue() = + testScope.runTest { + kosmos.enableDualShade(wideLayout = false) + setupDualShadeState(scene = Scenes.Lockscreen, overlay = Overlays.QuickSettingsShade) + + assertThat(underTest.showClock).isTrue() + } + + @Test fun onShadeCarrierGroupClicked_launchesNetworkSettings() = testScope.runTest { val activityStarter = kosmos.activityStarter diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt index fad66581682f..0642467a001b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt @@ -19,6 +19,8 @@ package com.android.systemui.shared.clocks import android.content.res.Resources import android.graphics.Color import android.graphics.drawable.Drawable +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags import android.util.TypedValue import android.view.LayoutInflater import android.widget.FrameLayout @@ -29,6 +31,7 @@ import com.android.systemui.customization.R import com.android.systemui.plugins.clocks.ClockId import com.android.systemui.plugins.clocks.ClockSettings import com.android.systemui.plugins.clocks.ThemeConfig +import com.android.systemui.shared.Flags import com.android.systemui.shared.clocks.DefaultClockController.Companion.DOZE_COLOR import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.eq @@ -103,6 +106,26 @@ class DefaultClockProviderTest : SysuiTestCase() { } @Test + @DisableFlags(Flags.FLAG_AMBIENT_AOD) + fun defaultClock_initialize_flagOff() { + val clock = provider.createClock(DEFAULT_CLOCK_ID) + verify(mockSmallClockView).setColors(DOZE_COLOR, Color.MAGENTA) + verify(mockLargeClockView).setColors(DOZE_COLOR, Color.MAGENTA) + + clock.initialize(true, 0f, 0f, null) + + // This is the default darkTheme color + val expectedColor = context.resources.getColor(android.R.color.system_accent1_100) + verify(mockSmallClockView).setColors(DOZE_COLOR, expectedColor) + verify(mockLargeClockView).setColors(DOZE_COLOR, expectedColor) + verify(mockSmallClockView).onTimeZoneChanged(notNull()) + verify(mockLargeClockView).onTimeZoneChanged(notNull()) + verify(mockSmallClockView).refreshTime() + verify(mockLargeClockView).refreshTime() + } + + @Test + @EnableFlags(Flags.FLAG_AMBIENT_AOD) fun defaultClock_initialize() { val clock = provider.createClock(DEFAULT_CLOCK_ID) verify(mockSmallClockView).setColors(DOZE_COLOR, Color.MAGENTA) @@ -110,7 +133,7 @@ class DefaultClockProviderTest : SysuiTestCase() { clock.initialize(true, 0f, 0f, null) - val expectedColor = 0 + val expectedColor = Color.MAGENTA verify(mockSmallClockView).setColors(DOZE_COLOR, expectedColor) verify(mockLargeClockView).setColors(DOZE_COLOR, expectedColor) verify(mockSmallClockView).onTimeZoneChanged(notNull()) @@ -165,8 +188,10 @@ class DefaultClockProviderTest : SysuiTestCase() { } @Test - fun defaultClock_events_onThemeChanged_noSeed() { - val expectedColor = 0 + @DisableFlags(Flags.FLAG_AMBIENT_AOD) + fun defaultClock_events_onThemeChanged_noSeed_flagOff() { + // This is the default darkTheme color + val expectedColor = context.resources.getColor(android.R.color.system_accent1_100) val clock = provider.createClock(DEFAULT_CLOCK_ID) verify(mockSmallClockView).setColors(DOZE_COLOR, Color.MAGENTA) @@ -180,6 +205,22 @@ class DefaultClockProviderTest : SysuiTestCase() { } @Test + @EnableFlags(Flags.FLAG_AMBIENT_AOD) + fun defaultClock_events_onThemeChanged_noSeedn() { + val expectedColor = Color.TRANSPARENT + val clock = provider.createClock(DEFAULT_CLOCK_ID) + + verify(mockSmallClockView).setColors(DOZE_COLOR, Color.MAGENTA) + verify(mockLargeClockView).setColors(DOZE_COLOR, Color.MAGENTA) + + clock.smallClock.events.onThemeChanged(ThemeConfig(true, null)) + clock.largeClock.events.onThemeChanged(ThemeConfig(true, null)) + + verify(mockSmallClockView).setColors(DOZE_COLOR, Color.MAGENTA) + verify(mockLargeClockView).setColors(DOZE_COLOR, Color.MAGENTA) + } + + @Test fun defaultClock_events_onThemeChanged_newSeed() { val initSeedColor = 10 val newSeedColor = 20 diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java index 064fd485dab4..b80ff3466007 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java @@ -33,6 +33,7 @@ import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.systemui.SysuiTestCase; +import com.android.systemui.log.LogAssertKt; import com.android.systemui.plugins.Plugin; import com.android.systemui.plugins.PluginLifecycleManager; import com.android.systemui.plugins.PluginListener; @@ -138,11 +139,12 @@ public class PluginInstanceTest extends SysuiTestCase { mVersionCheckResult = false; assertFalse(mPluginInstance.hasError()); - mPluginInstanceFactory.create( mContext, mAppInfo, wrongVersionTestPluginComponentName, TestPlugin.class, mPluginListener); - mPluginInstance.onCreate(); + LogAssertKt.assertRunnableLogsWtf(()-> { + mPluginInstance.onCreate(); + }); assertTrue(mPluginInstance.hasError()); assertNull(mPluginInstance.getPlugin()); } @@ -193,8 +195,10 @@ public class PluginInstanceTest extends SysuiTestCase { mPluginInstance.onCreate(); assertFalse(mPluginInstance.hasError()); - Object result = mPluginInstance.getPlugin().methodThrowsError(); - assertNotNull(result); // Wrapper function should return non-null; + LogAssertKt.assertRunnableLogsWtf(()-> { + Object result = mPluginInstance.getPlugin().methodThrowsError(); + assertNotNull(result); // Wrapper function should return non-null; + }); assertTrue(mPluginInstance.hasError()); assertNull(mPluginInstance.getPlugin()); } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationGroupingUtilTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationGroupingUtilTest.kt new file mode 100644 index 000000000000..e04162bf990a --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationGroupingUtilTest.kt @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar + +import android.platform.test.flag.junit.FlagsParameterization +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.notification.row.NotificationTestHelper +import com.android.systemui.statusbar.notification.shared.NotificationBundleUi +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters + +@SmallTest +@RunWith(ParameterizedAndroidJunit4::class) +class NotificationGroupingUtilTest(flags: FlagsParameterization) : SysuiTestCase() { + + private lateinit var underTest: NotificationGroupingUtil + + private lateinit var testHelper: NotificationTestHelper + + companion object { + @JvmStatic + @Parameters(name = "{0}") + fun getParams(): List<FlagsParameterization> { + return FlagsParameterization.allCombinationsOf(NotificationBundleUi.FLAG_NAME) + } + } + + init { + mSetFlagsRule.setFlagsParameterization(flags) + } + + @Before + fun setup() { + testHelper = NotificationTestHelper(mContext, mDependency) + } + + @Test + fun showsTime() { + val row = testHelper.createRow() + + underTest = NotificationGroupingUtil(row) + assertThat(underTest.showsTime(row)).isTrue() + } +}
\ No newline at end of file diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java index c9d910c530ea..01046cd10d87 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java @@ -20,16 +20,29 @@ import static com.android.systemui.log.LogBufferHelperKt.logcatLogBuffer; import static junit.framework.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.ActivityOptions; import android.app.Notification; +import android.app.PendingIntent; import android.content.Context; +import android.content.Intent; import android.os.SystemClock; import android.os.UserHandle; +import android.platform.test.flag.junit.FlagsParameterization; import android.testing.TestableLooper; +import android.util.Pair; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.RemoteViews; -import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -42,19 +55,37 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.row.NotificationTestHelper; +import com.android.systemui.statusbar.notification.shared.NotificationBundleUi; import com.android.systemui.statusbar.policy.RemoteInputUriController; import com.android.systemui.util.kotlin.JavaAdapter; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +import platform.test.runner.parameterized.ParameterizedAndroidJunit4; +import platform.test.runner.parameterized.Parameters; + @SmallTest -@RunWith(AndroidJUnit4.class) +@RunWith(ParameterizedAndroidJunit4.class) @TestableLooper.RunWithLooper public class NotificationRemoteInputManagerTest extends SysuiTestCase { + + @Parameters(name = "{0}") + public static List<FlagsParameterization> getParams() { + return FlagsParameterization.allCombinationsOf(NotificationBundleUi.FLAG_NAME); + } + private static final String TEST_PACKAGE_NAME = "test"; private static final int TEST_UID = 0; @@ -69,14 +100,34 @@ public class NotificationRemoteInputManagerTest extends SysuiTestCase { @Mock private NotificationClickNotifier mClickNotifier; @Mock private NotificationLockscreenUserManager mLockscreenUserManager; @Mock private PowerInteractor mPowerInteractor; + @Mock + NotificationRemoteInputManager.RemoteInputListener mRemoteInputListener; + private ActionClickLogger mActionClickLogger; + @Captor + ArgumentCaptor<NotificationRemoteInputManager.ClickHandler> mClickHandlerArgumentCaptor; + private Context mSpyContext; + private NotificationTestHelper mTestHelper; private TestableNotificationRemoteInputManager mRemoteInputManager; private NotificationEntry mEntry; + public NotificationRemoteInputManagerTest(FlagsParameterization flags) { + super(); + mSetFlagsRule.setFlagsParameterization(flags); + } + @Before - public void setUp() { + public void setUp() throws Exception { MockitoAnnotations.initMocks(this); + mSpyContext = spy(mContext); + doNothing().when(mSpyContext).startIntentSender( + any(), any(), anyInt(), anyInt(), anyInt(), any()); + + + mTestHelper = new NotificationTestHelper(mSpyContext, mDependency); + mActionClickLogger = spy(new ActionClickLogger(logcatLogBuffer())); + mRemoteInputManager = new TestableNotificationRemoteInputManager(mContext, mock(NotifPipelineFlags.class), mLockscreenUserManager, @@ -87,9 +138,10 @@ public class NotificationRemoteInputManagerTest extends SysuiTestCase { mRemoteInputUriController, new RemoteInputControllerLogger(logcatLogBuffer()), mClickNotifier, - new ActionClickLogger(logcatLogBuffer()), + mActionClickLogger, mock(JavaAdapter.class), mock(ShadeInteractor.class)); + mRemoteInputManager.setRemoteInputListener(mRemoteInputListener); mEntry = new NotificationEntryBuilder() .setPkg(TEST_PACKAGE_NAME) .setOpPkg(TEST_PACKAGE_NAME) @@ -133,6 +185,70 @@ public class NotificationRemoteInputManagerTest extends SysuiTestCase { assertTrue(mRemoteInputManager.shouldKeepForSmartReplyHistory(mEntry)); } + @Test + public void testActionClick() throws Exception { + RemoteViews.RemoteResponse response = mock(RemoteViews.RemoteResponse.class); + when(response.getLaunchOptions(any())).thenReturn( + Pair.create(mock(Intent.class), mock(ActivityOptions.class))); + ExpandableNotificationRow row = getRowWithReplyAction(); + View actionView = ((LinearLayout) row.getPrivateLayout().getExpandedChild().findViewById( + com.android.internal.R.id.actions)).getChildAt(0); + Notification n = getNotification(row); + CountDownLatch latch = new CountDownLatch(1); + Consumer<NotificationEntry> consumer = notificationEntry -> latch.countDown(); + if (!NotificationBundleUi.isEnabled()) { + mRemoteInputManager.addActionPressListener(consumer); + } + + mRemoteInputManager.getRemoteViewsOnClickHandler().onInteraction( + actionView, + n.actions[0].actionIntent, + response); + + verify(mActionClickLogger).logInitialClick(row.getKey(), 0, n.actions[0].actionIntent); + verify(mClickNotifier).onNotificationActionClick( + eq(row.getKey()), eq(0), eq(n.actions[0]), any(), eq(false)); + verify(mCallback).handleRemoteViewClick(eq(actionView), eq(n.actions[0].actionIntent), + eq(false), eq(0), mClickHandlerArgumentCaptor.capture()); + + mClickHandlerArgumentCaptor.getValue().handleClick(); + verify(mActionClickLogger).logStartingIntentWithDefaultHandler( + row.getKey(), n.actions[0].actionIntent, 0); + + verify(mRemoteInputListener).releaseNotificationIfKeptForRemoteInputHistory(row.getKey()); + if (NotificationBundleUi.isEnabled()) { + verify(row.getEntryAdapter()).onNotificationActionClicked(); + } else { + latch.await(10, TimeUnit.MILLISECONDS); + } + } + + private Notification getNotification(ExpandableNotificationRow row) { + if (NotificationBundleUi.isEnabled()) { + return row.getEntryAdapter().getSbn().getNotification(); + } else { + return row.getEntry().getSbn().getNotification(); + } + } + + private ExpandableNotificationRow getRowWithReplyAction() throws Exception { + PendingIntent pi = PendingIntent.getBroadcast(getContext(), 0, new Intent("Action"), + PendingIntent.FLAG_IMMUTABLE); + Notification n = new Notification.Builder(mSpyContext, "") + .setSmallIcon(com.android.systemui.res.R.drawable.ic_person) + .addAction(new Notification.Action(com.android.systemui.res.R.drawable.ic_person, + "reply", pi)) + .build(); + ExpandableNotificationRow row = mTestHelper.createRow(n); + row.onNotificationUpdated(); + row.getPrivateLayout().setExpandedChild(Notification.Builder.recoverBuilder(mSpyContext, n) + .createBigContentView().apply( + mSpyContext, + row.getPrivateLayout(), + mRemoteInputManager.getRemoteViewsOnClickHandler())); + return row; + } + private class TestableNotificationRemoteInputManager extends NotificationRemoteInputManager { TestableNotificationRemoteInputManager( diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt index fda4ab005446..485b9febc284 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt @@ -19,43 +19,51 @@ package com.android.systemui.statusbar.chips.call.ui.viewmodel import android.app.PendingIntent import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.FlagsParameterization import android.view.View -import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.animation.Expandable import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription import com.android.systemui.common.shared.model.Icon -import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.collectLastValue import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.plugins.activityStarter import com.android.systemui.res.R import com.android.systemui.statusbar.StatusBarIconView +import com.android.systemui.statusbar.chips.StatusBarChipsReturnAnimations import com.android.systemui.statusbar.chips.ui.model.ColorsModel import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer import com.android.systemui.statusbar.core.StatusBarConnectedDisplays +import com.android.systemui.statusbar.core.StatusBarRootModernization import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel import com.android.systemui.statusbar.phone.ongoingcall.DisableChipsModernization import com.android.systemui.statusbar.phone.ongoingcall.EnableChipsModernization +import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallTestHelper.addOngoingCallState import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallTestHelper.removeOngoingCallState import com.android.systemui.testKosmos import com.android.systemui.util.time.fakeSystemClock import com.google.common.truth.Truth.assertThat import kotlin.test.Test -import kotlinx.coroutines.test.runTest import org.junit.runner.RunWith import org.mockito.kotlin.any import org.mockito.kotlin.mock import org.mockito.kotlin.verify import org.mockito.kotlin.whenever +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters @SmallTest -@RunWith(AndroidJUnit4::class) -class CallChipViewModelTest : SysuiTestCase() { +@RunWith(ParameterizedAndroidJunit4::class) +class CallChipViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { + init { + mSetFlagsRule.setFlagsParameterization(flags) + } + private val kosmos = testKosmos().useUnconfinedTestDispatcher() private val chipBackgroundView = mock<ChipBackgroundContainer>() @@ -71,7 +79,7 @@ class CallChipViewModelTest : SysuiTestCase() { private val mockExpandable: Expandable = mock<Expandable>().apply { whenever(dialogTransitionController(any())).thenReturn(mock()) } - private val underTest by lazy { kosmos.callChipViewModel } + private val Kosmos.underTest by Kosmos.Fixture { callChipViewModel } @Test fun chip_noCall_isHidden() = @@ -88,9 +96,10 @@ class CallChipViewModelTest : SysuiTestCase() { kosmos.runTest { val latest by collectLastValue(underTest.chip) - addOngoingCallState(startTimeMs = 0) + addOngoingCallState(startTimeMs = 0, isAppVisible = false) assertThat(latest).isInstanceOf(OngoingActivityChipModel.Active.IconOnly::class.java) + assertThat((latest as OngoingActivityChipModel.Active).isHidden).isFalse() } @Test @@ -98,9 +107,10 @@ class CallChipViewModelTest : SysuiTestCase() { kosmos.runTest { val latest by collectLastValue(underTest.chip) - addOngoingCallState(startTimeMs = -2) + addOngoingCallState(startTimeMs = -2, isAppVisible = false) assertThat(latest).isInstanceOf(OngoingActivityChipModel.Active.IconOnly::class.java) + assertThat((latest as OngoingActivityChipModel.Active).isHidden).isFalse() } @Test @@ -108,9 +118,82 @@ class CallChipViewModelTest : SysuiTestCase() { kosmos.runTest { val latest by collectLastValue(underTest.chip) - addOngoingCallState(startTimeMs = 345) + addOngoingCallState(startTimeMs = 345, isAppVisible = false) assertThat(latest).isInstanceOf(OngoingActivityChipModel.Active.Timer::class.java) + assertThat((latest as OngoingActivityChipModel.Active).isHidden).isFalse() + } + + @Test + @DisableFlags(StatusBarChipsReturnAnimations.FLAG_NAME) + fun chipLegacy_inCallWithVisibleApp_zeroStartTime_isHiddenAsInactive() = + kosmos.runTest { + val latest by collectLastValue(underTest.chip) + + addOngoingCallState(startTimeMs = 0, isAppVisible = true) + + assertThat(latest).isInstanceOf(OngoingActivityChipModel.Inactive::class.java) + } + + @Test + @EnableFlags(StatusBarChipsReturnAnimations.FLAG_NAME) + @EnableChipsModernization + fun chipWithReturnAnimation_inCallWithVisibleApp_zeroStartTime_isHiddenAsIconOnly() = + kosmos.runTest { + val latest by collectLastValue(underTest.chip) + + addOngoingCallState(startTimeMs = 0, isAppVisible = true) + + assertThat(latest).isInstanceOf(OngoingActivityChipModel.Active.IconOnly::class.java) + assertThat((latest as OngoingActivityChipModel.Active).isHidden).isTrue() + } + + @Test + @DisableFlags(StatusBarChipsReturnAnimations.FLAG_NAME) + fun chipLegacy_inCallWithVisibleApp_negativeStartTime_isHiddenAsInactive() = + kosmos.runTest { + val latest by collectLastValue(underTest.chip) + + addOngoingCallState(startTimeMs = -2, isAppVisible = true) + + assertThat(latest).isInstanceOf(OngoingActivityChipModel.Inactive::class.java) + } + + @Test + @EnableFlags(StatusBarChipsReturnAnimations.FLAG_NAME) + @EnableChipsModernization + fun chipWithReturnAnimation_inCallWithVisibleApp_negativeStartTime_isHiddenAsIconOnly() = + kosmos.runTest { + val latest by collectLastValue(underTest.chip) + + addOngoingCallState(startTimeMs = -2, isAppVisible = true) + + assertThat(latest).isInstanceOf(OngoingActivityChipModel.Active.IconOnly::class.java) + assertThat((latest as OngoingActivityChipModel.Active).isHidden).isTrue() + } + + @Test + @DisableFlags(StatusBarChipsReturnAnimations.FLAG_NAME) + fun chipLegacy_inCallWithVisibleApp_positiveStartTime_isHiddenAsInactive() = + kosmos.runTest { + val latest by collectLastValue(underTest.chip) + + addOngoingCallState(startTimeMs = 345, isAppVisible = true) + + assertThat(latest).isInstanceOf(OngoingActivityChipModel.Inactive::class.java) + } + + @Test + @EnableFlags(StatusBarChipsReturnAnimations.FLAG_NAME) + @EnableChipsModernization + fun chipWithReturnAnimation_inCallWithVisibleApp_positiveStartTime_isHiddenAsTimer() = + kosmos.runTest { + val latest by collectLastValue(underTest.chip) + + addOngoingCallState(startTimeMs = 345, isAppVisible = true) + + assertThat(latest).isInstanceOf(OngoingActivityChipModel.Active.Timer::class.java) + assertThat((latest as OngoingActivityChipModel.Active).isHidden).isTrue() } @Test @@ -419,5 +502,18 @@ class CallChipViewModelTest : SysuiTestCase() { private const val PROMOTED_BACKGROUND_COLOR = 65 private const val PROMOTED_PRIMARY_TEXT_COLOR = 98 + + @get:Parameters(name = "{0}") + @JvmStatic + val flags: List<FlagsParameterization> + get() = buildList { + addAll( + FlagsParameterization.allCombinationsOf( + StatusBarRootModernization.FLAG_NAME, + StatusBarChipsModernization.FLAG_NAME, + StatusBarChipsReturnAnimations.FLAG_NAME, + ) + ) + } } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt index 5d1950670777..7f8f5f43e775 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt @@ -27,6 +27,7 @@ import com.android.systemui.kosmos.collectLastValue import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.statusbar.StatusBarIconView +import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips import com.android.systemui.statusbar.core.StatusBarConnectedDisplays import com.android.systemui.statusbar.notification.data.model.activeNotificationModel import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel @@ -39,6 +40,7 @@ import org.mockito.kotlin.mock @SmallTest @RunWith(AndroidJUnit4::class) +@EnableFlags(StatusBarNotifChips.FLAG_NAME) class SingleNotificationChipInteractorTest : SysuiTestCase() { private val kosmos = testKosmos().useUnconfinedTestDispatcher() val factory = kosmos.singleNotificationChipInteractorFactory diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt index e2d1498270c8..e39fa7099953 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt @@ -137,7 +137,7 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() { kosmos.runTest { screenRecordState.value = ScreenRecordModel.Recording - addOngoingCallState() + addOngoingCallState(isAppVisible = false) val latest by collectLastValue(underTest.primaryChip) @@ -163,7 +163,7 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() { screenRecordState.value = ScreenRecordModel.DoingNothing mediaProjectionState.value = MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE) - addOngoingCallState() + addOngoingCallState(isAppVisible = false) val latest by collectLastValue(underTest.primaryChip) @@ -178,7 +178,7 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() { // MediaProjection covers both share-to-app and cast-to-other-device mediaProjectionState.value = MediaProjectionState.NotProjecting - addOngoingCallState(key = notificationKey) + addOngoingCallState(key = notificationKey, isAppVisible = false) val latest by collectLastValue(underTest.primaryChip) @@ -190,7 +190,7 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() { kosmos.runTest { // Start with just the lowest priority chip shown val callNotificationKey = "call" - addOngoingCallState(key = callNotificationKey) + addOngoingCallState(key = callNotificationKey, isAppVisible = false) // And everything else hidden mediaProjectionState.value = MediaProjectionState.NotProjecting screenRecordState.value = ScreenRecordModel.DoingNothing @@ -225,7 +225,7 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() { mediaProjectionState.value = MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE) val callNotificationKey = "call" - addOngoingCallState(key = callNotificationKey) + addOngoingCallState(key = callNotificationKey, isAppVisible = false) val latest by collectLastValue(underTest.primaryChip) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt index 96c4a59f752d..f06244f4f637 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt @@ -62,6 +62,7 @@ import com.android.systemui.statusbar.notification.data.repository.ActiveNotific import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository import com.android.systemui.statusbar.notification.data.repository.addNotif import com.android.systemui.statusbar.notification.data.repository.addNotifs +import com.android.systemui.statusbar.notification.data.repository.removeNotif import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel import com.android.systemui.statusbar.phone.SystemUIDialog @@ -235,7 +236,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { fun primaryChip_screenRecordShowAndCallShow_screenRecordShown() = kosmos.runTest { screenRecordState.value = ScreenRecordModel.Recording - addOngoingCallState("call") + addOngoingCallState("call", isAppVisible = false) val latest by collectLastValue(underTest.primaryChip) @@ -248,7 +249,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { kosmos.runTest { val callNotificationKey = "call" screenRecordState.value = ScreenRecordModel.Recording - addOngoingCallState(callNotificationKey) + addOngoingCallState(callNotificationKey, isAppVisible = false) val latest by collectLastValue(underTest.chipsLegacy) val unused by collectLastValue(underTest.chips) @@ -295,7 +296,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { @Test fun chipsLegacy_oneChip_notSquished() = kosmos.runTest { - addOngoingCallState() + addOngoingCallState(isAppVisible = false) val latest by collectLastValue(underTest.chipsLegacy) @@ -322,7 +323,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { fun chipsLegacy_twoTimerChips_isSmallPortrait_bothSquished() = kosmos.runTest { screenRecordState.value = ScreenRecordModel.Recording - addOngoingCallState(key = "call") + addOngoingCallState(key = "call", isAppVisible = false) val latest by collectLastValue(underTest.chipsLegacy) @@ -349,12 +350,42 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { .isInstanceOf(OngoingActivityChipModel.Active.IconOnly::class.java) } + @EnableChipsModernization + @Test + fun chips_threeChips_isSmallPortrait_allSquished() = + kosmos.runTest { + screenRecordState.value = ScreenRecordModel.Recording + addOngoingCallState(key = "call") + + val promotedContentBuilder = + PromotedNotificationContentModel.Builder("notif").apply { + this.shortCriticalText = "Some text here" + } + activeNotificationListRepository.addNotif( + activeNotificationModel( + key = "notif", + statusBarChipIcon = createStatusBarIconViewOrNull(), + promotedContent = promotedContentBuilder.build(), + ) + ) + + val latest by collectLastValue(underTest.chips) + + // Squished chips are icon only + assertThat(latest!!.active[0]) + .isInstanceOf(OngoingActivityChipModel.Active.IconOnly::class.java) + assertThat(latest!!.active[1]) + .isInstanceOf(OngoingActivityChipModel.Active.IconOnly::class.java) + assertThat(latest!!.active[2]) + .isInstanceOf(OngoingActivityChipModel.Active.IconOnly::class.java) + } + @DisableChipsModernization @Test fun chipsLegacy_countdownChipAndTimerChip_countdownNotSquished_butTimerSquished() = kosmos.runTest { screenRecordState.value = ScreenRecordModel.Starting(millisUntilStarted = 2000) - addOngoingCallState(key = "call") + addOngoingCallState(key = "call", isAppVisible = false) val latest by collectLastValue(underTest.chipsLegacy) @@ -400,7 +431,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { .isInstanceOf(OngoingActivityChipModel.Inactive::class.java) // WHEN there's 2 chips - addOngoingCallState(key = "call") + addOngoingCallState(key = "call", isAppVisible = false) // THEN they both become squished assertThat(latest!!.primary) @@ -456,7 +487,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { fun chipsLegacy_twoChips_isLandscape_notSquished() = kosmos.runTest { screenRecordState.value = ScreenRecordModel.Recording - addOngoingCallState(key = "call") + addOngoingCallState(key = "call", isAppVisible = false) // WHEN we're in landscape val config = @@ -502,7 +533,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { fun chipsLegacy_twoChips_isLargeScreen_notSquished() = kosmos.runTest { screenRecordState.value = ScreenRecordModel.Recording - addOngoingCallState(key = "call") + addOngoingCallState(key = "call", isAppVisible = false) // WHEN we're on a large screen kosmos.displayStateRepository.setIsLargeScreen(true) @@ -596,7 +627,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { screenRecordState.value = ScreenRecordModel.DoingNothing mediaProjectionState.value = MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE) - addOngoingCallState(key = "call") + addOngoingCallState(key = "call", isAppVisible = false) val latest by collectLastValue(underTest.primaryChip) @@ -611,7 +642,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { screenRecordState.value = ScreenRecordModel.DoingNothing mediaProjectionState.value = MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE) - addOngoingCallState(key = "call") + addOngoingCallState(key = "call", isAppVisible = false) val latest by collectLastValue(underTest.chipsLegacy) val unused by collectLastValue(underTest.chips) @@ -650,7 +681,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { mediaProjectionState.value = MediaProjectionState.NotProjecting val callNotificationKey = "call" - addOngoingCallState(key = callNotificationKey) + addOngoingCallState(key = callNotificationKey, isAppVisible = false) val latest by collectLastValue(underTest.primaryChip) @@ -666,7 +697,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { // MediaProjection covers both share-to-app and cast-to-other-device mediaProjectionState.value = MediaProjectionState.NotProjecting - addOngoingCallState(key = callNotificationKey) + addOngoingCallState(key = callNotificationKey, isAppVisible = false) val latest by collectLastValue(underTest.chipsLegacy) val unused by collectLastValue(underTest.chips) @@ -851,7 +882,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { @EnableChipsModernization @Test - fun chips_threePromotedNotifs_topTwoActiveThirdInOverflow() = + fun chips_fourPromotedNotifs_topThreeActiveFourthInOverflow() = kosmos.runTest { val latest by collectLastValue(underTest.chips) val unused by collectLastValue(underTest.chipsLegacy) @@ -859,6 +890,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { val firstIcon = createStatusBarIconViewOrNull() val secondIcon = createStatusBarIconViewOrNull() val thirdIcon = createStatusBarIconViewOrNull() + val fourthIcon = createStatusBarIconViewOrNull() setNotifs( listOf( activeNotificationModel( @@ -879,20 +911,27 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { promotedContent = PromotedNotificationContentModel.Builder("thirdNotif").build(), ), + activeNotificationModel( + key = "fourthNotif", + statusBarChipIcon = fourthIcon, + promotedContent = + PromotedNotificationContentModel.Builder("fourthNotif").build(), + ), ) ) - assertThat(latest!!.active.size).isEqualTo(2) + assertThat(latest!!.active.size).isEqualTo(3) assertIsNotifChip(latest!!.active[0], context, firstIcon, "firstNotif") assertIsNotifChip(latest!!.active[1], context, secondIcon, "secondNotif") + assertIsNotifChip(latest!!.active[2], context, thirdIcon, "thirdNotif") assertThat(latest!!.overflow.size).isEqualTo(1) - assertIsNotifChip(latest!!.overflow[0], context, thirdIcon, "thirdNotif") + assertIsNotifChip(latest!!.overflow[0], context, fourthIcon, "fourthNotif") assertThat(latest!!.inactive.size).isEqualTo(4) assertThat(unused).isEqualTo(MultipleOngoingActivityChipsModelLegacy()) } @Test - fun visibleChipKeys_threePromotedNotifs_topTwoInList() = + fun visibleChipKeys_fourPromotedNotifs_topThreeInList() = kosmos.runTest { val latest by collectLastValue(underTest.visibleChipKeys) @@ -916,10 +955,16 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { promotedContent = PromotedNotificationContentModel.Builder("thirdNotif").build(), ), + activeNotificationModel( + key = "fourthNotif", + statusBarChipIcon = createStatusBarIconViewOrNull(), + promotedContent = + PromotedNotificationContentModel.Builder("fourthNotif").build(), + ), ) ) - assertThat(latest).containsExactly("firstNotif", "secondNotif").inOrder() + assertThat(latest).containsExactly("firstNotif", "secondNotif", "thirdNotif").inOrder() } @DisableChipsModernization @@ -930,7 +975,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { val unused by collectLastValue(underTest.chips) val callNotificationKey = "call" - addOngoingCallState(callNotificationKey) + addOngoingCallState(callNotificationKey, isAppVisible = false) val firstIcon = createStatusBarIconViewOrNull() activeNotificationListRepository.addNotifs( @@ -957,7 +1002,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { @EnableChipsModernization @Test - fun chips_callAndPromotedNotifs_callAndFirstNotifActiveSecondNotifInOverflow() = + fun chips_callAndPromotedNotifs_callAndFirstTwoNotifsActive_thirdNotifInOverflow() = kosmos.runTest { val latest by collectLastValue(underTest.chips) val unused by collectLastValue(underTest.chipsLegacy) @@ -965,6 +1010,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { val callNotificationKey = "call" val firstIcon = createStatusBarIconViewOrNull() val secondIcon = createStatusBarIconViewOrNull() + val thirdIcon = createStatusBarIconViewOrNull() addOngoingCallState(key = callNotificationKey) activeNotificationListRepository.addNotifs( listOf( @@ -980,27 +1026,34 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { promotedContent = PromotedNotificationContentModel.Builder("secondNotif").build(), ), + activeNotificationModel( + key = "thirdNotif", + statusBarChipIcon = thirdIcon, + promotedContent = + PromotedNotificationContentModel.Builder("thirdNotif").build(), + ), ) ) - assertThat(latest!!.active.size).isEqualTo(2) + assertThat(latest!!.active.size).isEqualTo(3) assertIsCallChip(latest!!.active[0], callNotificationKey, context) assertIsNotifChip(latest!!.active[1], context, firstIcon, "firstNotif") + assertIsNotifChip(latest!!.active[2], context, secondIcon, "secondNotif") assertThat(latest!!.overflow.size).isEqualTo(1) - assertIsNotifChip(latest!!.overflow[0], context, secondIcon, "secondNotif") + assertIsNotifChip(latest!!.overflow[0], context, thirdIcon, "thirdNotif") assertThat(latest!!.inactive.size).isEqualTo(3) assertThat(unused).isEqualTo(MultipleOngoingActivityChipsModelLegacy()) } @DisableChipsModernization @Test - fun chipsLegacy_screenRecordAndCallAndPromotedNotifs_notifsNotShown() = + fun chipsLegacy_screenRecordAndCallAndPromotedNotif_notifNotShown() = kosmos.runTest { val callNotificationKey = "call" val latest by collectLastValue(underTest.chipsLegacy) val unused by collectLastValue(underTest.chips) - addOngoingCallState(callNotificationKey) + addOngoingCallState(callNotificationKey, isAppVisible = false) screenRecordState.value = ScreenRecordModel.Recording activeNotificationListRepository.addNotif( activeNotificationModel( @@ -1016,7 +1069,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { } @Test - fun visibleChipKeys_screenRecordAndCallAndPromotedNotifs_topTwoInList() = + fun visibleChipKeys_screenRecordAndCallAndPromotedNotifs_topThreeInList() = kosmos.runTest { val latest by collectLastValue(underTest.visibleChipKeys) @@ -1025,20 +1078,27 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { screenRecordState.value = ScreenRecordModel.Recording activeNotificationListRepository.addNotif( activeNotificationModel( - key = "notif", + key = "notif1", statusBarChipIcon = createStatusBarIconViewOrNull(), - promotedContent = PromotedNotificationContentModel.Builder("notif").build(), + promotedContent = PromotedNotificationContentModel.Builder("notif1").build(), + ) + ) + activeNotificationListRepository.addNotif( + activeNotificationModel( + key = "notif2", + statusBarChipIcon = createStatusBarIconViewOrNull(), + promotedContent = PromotedNotificationContentModel.Builder("notif2").build(), ) ) assertThat(latest) - .containsExactly(ScreenRecordChipViewModel.KEY, callNotificationKey) + .containsExactly(ScreenRecordChipViewModel.KEY, callNotificationKey, "notif1") .inOrder() } @EnableChipsModernization @Test - fun chips_screenRecordAndCallAndPromotedNotif_notifInOverflow() = + fun chips_screenRecordAndCallAndPromotedNotifs_secondNotifInOverflow() = kosmos.runTest { val latest by collectLastValue(underTest.chips) val unused by collectLastValue(underTest.chipsLegacy) @@ -1055,11 +1115,22 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { ) addOngoingCallState(key = callNotificationKey) - assertThat(latest!!.active.size).isEqualTo(2) + // This is the overflow notif + val notifIcon2 = createStatusBarIconViewOrNull() + activeNotificationListRepository.addNotif( + activeNotificationModel( + key = "notif2", + statusBarChipIcon = notifIcon2, + promotedContent = PromotedNotificationContentModel.Builder("notif2").build(), + ) + ) + + assertThat(latest!!.active.size).isEqualTo(3) assertIsScreenRecordChip(latest!!.active[0]) assertIsCallChip(latest!!.active[1], callNotificationKey, context) + assertIsNotifChip(latest!!.active[2], context, notifIcon, "notif") assertThat(latest!!.overflow.size).isEqualTo(1) - assertIsNotifChip(latest!!.overflow[0], context, notifIcon, "notif") + assertIsNotifChip(latest!!.overflow[0], context, notifIcon2, "notif2") assertThat(latest!!.inactive.size).isEqualTo(2) assertThat(unused).isEqualTo(MultipleOngoingActivityChipsModelLegacy()) } @@ -1089,7 +1160,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { assertIsNotifChip(latest, context, notifIcon, "notif") // WHEN the higher priority call chip is added - addOngoingCallState(callNotificationKey) + addOngoingCallState(callNotificationKey, isAppVisible = false) // THEN the higher priority call chip is used assertIsCallChip(latest, callNotificationKey, context) @@ -1120,7 +1191,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { screenRecordState.value = ScreenRecordModel.Recording mediaProjectionState.value = MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE) - addOngoingCallState(callNotificationKey) + addOngoingCallState(callNotificationKey, isAppVisible = false) val notifIcon = createStatusBarIconViewOrNull() activeNotificationListRepository.addNotif( activeNotificationModel( @@ -1182,7 +1253,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { assertThat(unused).isEqualTo(MultipleOngoingActivityChipsModel()) // WHEN the higher priority call chip is added - addOngoingCallState(callNotificationKey) + addOngoingCallState(callNotificationKey, isAppVisible = false) // THEN the higher priority call chip is used as primary and notif is demoted to // secondary @@ -1234,15 +1305,16 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { @Test fun chips_movesChipsAroundAccordingToPriority() = kosmos.runTest { + systemClock.setCurrentTimeMillis(10_000) val callNotificationKey = "call" // Start with just the lowest priority chip active - val notifIcon = createStatusBarIconViewOrNull() + val notif1Icon = createStatusBarIconViewOrNull() setNotifs( listOf( activeNotificationModel( - key = "notif", - statusBarChipIcon = notifIcon, - promotedContent = PromotedNotificationContentModel.Builder("notif").build(), + key = "notif1", + statusBarChipIcon = notif1Icon, + promotedContent = PromotedNotificationContentModel.Builder("notif1").build(), ) ) ) @@ -1254,7 +1326,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { val unused by collectLastValue(underTest.chipsLegacy) assertThat(latest!!.active.size).isEqualTo(1) - assertIsNotifChip(latest!!.active[0], context, notifIcon, "notif") + assertIsNotifChip(latest!!.active[0], context, notif1Icon, "notif1") assertThat(latest!!.overflow).isEmpty() assertThat(latest!!.inactive.size).isEqualTo(4) assertThat(unused).isEqualTo(MultipleOngoingActivityChipsModelLegacy()) @@ -1262,10 +1334,10 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { // WHEN the higher priority call chip is added addOngoingCallState(key = callNotificationKey) - // THEN the higher priority call chip and notif are active in that order + // THEN the higher priority call chip and notif1 are active in that order assertThat(latest!!.active.size).isEqualTo(2) assertIsCallChip(latest!!.active[0], callNotificationKey, context) - assertIsNotifChip(latest!!.active[1], context, notifIcon, "notif") + assertIsNotifChip(latest!!.active[1], context, notif1Icon, "notif1") assertThat(latest!!.overflow).isEmpty() assertThat(latest!!.inactive.size).isEqualTo(3) assertThat(unused).isEqualTo(MultipleOngoingActivityChipsModelLegacy()) @@ -1278,56 +1350,63 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { createTask(taskId = 1), ) - // THEN the higher priority media projection chip and call are active in that order, and - // notif is demoted to overflow - assertThat(latest!!.active.size).isEqualTo(2) + // THEN media projection, then call, then notif1 are active + assertThat(latest!!.active.size).isEqualTo(3) assertIsShareToAppChip(latest!!.active[0]) assertIsCallChip(latest!!.active[1], callNotificationKey, context) - assertThat(latest!!.overflow.size).isEqualTo(1) - assertIsNotifChip(latest!!.overflow[0], context, notifIcon, "notif") + assertIsNotifChip(latest!!.active[2], context, notif1Icon, "notif1") + assertThat(latest!!.overflow).isEmpty() assertThat(latest!!.inactive.size).isEqualTo(2) assertThat(unused).isEqualTo(MultipleOngoingActivityChipsModelLegacy()) - // WHEN the higher priority screen record chip is added + // WHEN the screen record chip is added, which replaces media projection screenRecordState.value = ScreenRecordModel.Recording + // AND another notification is added + systemClock.advanceTime(2_000) + val notif2Icon = createStatusBarIconViewOrNull() + activeNotificationListRepository.addNotif( + activeNotificationModel( + key = "notif2", + statusBarChipIcon = notif2Icon, + promotedContent = PromotedNotificationContentModel.Builder("notif2").build(), + ) + ) - // THEN the higher priority screen record chip and call are active in that order, and - // media projection and notif are demoted in overflow - assertThat(latest!!.active.size).isEqualTo(2) + // THEN screen record, then call, then notif2 are active + assertThat(latest!!.active.size).isEqualTo(3) assertIsScreenRecordChip(latest!!.active[0]) assertIsCallChip(latest!!.active[1], callNotificationKey, context) + assertIsNotifChip(latest!!.active[2], context, notif2Icon, "notif2") + + // AND notif1 and media projection is demoted in overflow assertThat(latest!!.overflow.size).isEqualTo(2) assertIsShareToAppChip(latest!!.overflow[0]) - assertIsNotifChip(latest!!.overflow[1], context, notifIcon, "notif") + assertIsNotifChip(latest!!.overflow[1], context, notif1Icon, "notif1") assertThat(latest!!.inactive.size).isEqualTo(1) assertThat(unused).isEqualTo(MultipleOngoingActivityChipsModelLegacy()) - // WHEN screen record and call is dropped + // WHEN screen record and call are dropped screenRecordState.value = ScreenRecordModel.DoingNothing - setNotifs( - listOf( - activeNotificationModel( - key = "notif", - statusBarChipIcon = notifIcon, - promotedContent = PromotedNotificationContentModel.Builder("notif").build(), - ) - ) - ) + removeOngoingCallState(callNotificationKey) - // THEN media projection and notif remain - assertThat(latest!!.active.size).isEqualTo(2) + // THEN media projection, notif2, and notif1 remain + assertThat(latest!!.active.size).isEqualTo(3) assertIsShareToAppChip(latest!!.active[0]) - assertIsNotifChip(latest!!.active[1], context, notifIcon, "notif") + assertIsNotifChip(latest!!.active[1], context, notif2Icon, "notif2") + assertIsNotifChip(latest!!.active[2], context, notif1Icon, "notif1") assertThat(latest!!.overflow).isEmpty() assertThat(latest!!.inactive.size).isEqualTo(3) assertThat(unused).isEqualTo(MultipleOngoingActivityChipsModelLegacy()) // WHEN media projection is dropped mediaProjectionState.value = MediaProjectionState.NotProjecting + // AND notif2 is dropped + systemClock.advanceTime(2_000) + activeNotificationListRepository.removeNotif("notif2") - // THEN only notif is active + // THEN only notif1 is active assertThat(latest!!.active.size).isEqualTo(1) - assertIsNotifChip(latest!!.active[0], context, notifIcon, "notif") + assertIsNotifChip(latest!!.active[0], context, notif1Icon, "notif1") assertThat(latest!!.overflow).isEmpty() assertThat(latest!!.inactive.size).isEqualTo(4) assertThat(unused).isEqualTo(MultipleOngoingActivityChipsModelLegacy()) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationCloseButtonTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationCloseButtonTest.kt new file mode 100644 index 000000000000..e4b2e6c9b359 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationCloseButtonTest.kt @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification + +import android.app.Notification +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags +import android.testing.TestableLooper +import android.view.MotionEvent +import android.view.View +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.Flags +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.notification.row.NotificationTestHelper +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.any +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.stub +import com.android.systemui.res.R +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow +import org.junit.Before + +private fun getCloseButton(row: ExpandableNotificationRow): View { + val contractedView = row.showingLayout?.contractedChild!! + return contractedView.findViewById(com.android.internal.R.id.close_button) +} + +@SmallTest +@RunWith(AndroidJUnit4::class) +class NotificationCloseButtonTest : SysuiTestCase() { + private lateinit var helper: NotificationTestHelper + + @Before + fun setUp() { + helper = NotificationTestHelper( + mContext, + mDependency, + TestableLooper.get(this) + ) + } + + @Test + @DisableFlags(Flags.FLAG_NOTIFICATION_ADD_X_ON_HOVER_TO_DISMISS) + fun verifyWhenFeatureDisabled() { + // Enable the notification row to dismiss. + helper.dismissibilityProvider.stub { + on { isDismissable(any()) } doReturn true + } + + // By default, the close button should be gone. + val row = createNotificationRow() + val closeButton = getCloseButton(row) + assertThat(closeButton).isNotNull() + assertThat(closeButton.visibility).isEqualTo(View.GONE) + + val hoverEnterEvent = MotionEvent.obtain( + 0/*downTime=*/, + 0/*eventTime=*/, + MotionEvent.ACTION_HOVER_ENTER, + 0f/*x=*/, + 0f/*y=*/, + 0/*metaState*/ + ) + + // The close button should not show if the feature is disabled. + row.onInterceptHoverEvent(hoverEnterEvent) + assertThat(closeButton.visibility).isEqualTo(View.GONE) + } + + @Test + @EnableFlags(Flags.FLAG_NOTIFICATION_ADD_X_ON_HOVER_TO_DISMISS) + fun verifyOnDismissableNotification() { + // Enable the notification row to dismiss. + helper.dismissibilityProvider.stub { + on { isDismissable(any()) } doReturn true + } + + // By default, the close button should be gone. + val row = createNotificationRow() + val closeButton = getCloseButton(row) + assertThat(closeButton).isNotNull() + assertThat(closeButton.visibility).isEqualTo(View.GONE) + + val hoverEnterEvent = MotionEvent.obtain( + 0/*downTime=*/, + 0/*eventTime=*/, + MotionEvent.ACTION_HOVER_ENTER, + 0f/*x=*/, + 0f/*y=*/, + 0/*metaState*/ + ) + + // When the row is hovered, the close button should show. + row.onInterceptHoverEvent(hoverEnterEvent) + assertThat(closeButton.visibility).isEqualTo(View.VISIBLE) + + val hoverExitEvent = MotionEvent.obtain( + 0/*downTime=*/, + 0/*eventTime=*/, + MotionEvent.ACTION_HOVER_EXIT, + 0f/*x=*/, + 0f/*y=*/, + 0/*metaState*/ + ) + + // When hover exits the row, the close button should be gone again. + row.onInterceptHoverEvent(hoverExitEvent) + assertThat(closeButton.visibility).isEqualTo(View.GONE) + } + + @Test + @EnableFlags(Flags.FLAG_NOTIFICATION_ADD_X_ON_HOVER_TO_DISMISS) + fun verifyOnUndismissableNotification() { + // By default, the close button should be gone. + val row = createNotificationRow() + val closeButton = getCloseButton(row) + assertThat(closeButton).isNotNull() + assertThat(closeButton.visibility).isEqualTo(View.GONE) + + val hoverEnterEvent = MotionEvent.obtain( + 0/*downTime=*/, + 0/*eventTime=*/, + MotionEvent.ACTION_HOVER_ENTER, + 0f/*x=*/, + 0f/*y=*/, + 0/*metaState*/ + ) + + // Because the host notification cannot be dismissed, the close button should not show. + row.onInterceptHoverEvent(hoverEnterEvent) + assertThat(closeButton.visibility).isEqualTo(View.GONE) + } + + private fun createNotificationRow(): ExpandableNotificationRow { + val notification = Notification.Builder(context, "channel") + .setContentTitle("title") + .setContentText("text") + .setSmallIcon(R.drawable.ic_person) + .build() + + return helper.createRow(notification) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapterTest.kt index b6889afa4e8a..faafa073be4c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapterTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapterTest.kt @@ -29,8 +29,10 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.res.R import com.android.systemui.statusbar.RankingBuilder +import com.android.systemui.statusbar.notification.mockNotificationActivityStarter import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.entryAdapterFactory +import com.android.systemui.statusbar.notification.row.mockNotificationActionClickManager import com.android.systemui.statusbar.notification.shared.NotificationBundleUi import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING import com.android.systemui.testKosmos @@ -355,16 +357,27 @@ class NotificationEntryAdapterTest : SysuiTestCase() { val notification: Notification = Notification.Builder(mContext, "").setSmallIcon(R.drawable.ic_person).build() - val entry = - NotificationEntryBuilder() - .setNotification(notification) - .setImportance(NotificationManager.IMPORTANCE_MIN) - .build() + val entry = NotificationEntryBuilder().setNotification(notification).build() underTest = factory.create(entry) as NotificationEntryAdapter underTest.onNotificationBubbleIconClicked() - verify((factory as? EntryAdapterFactoryImpl)?.getNotificationActivityStarter()) - ?.onNotificationBubbleIconClicked(entry) + verify(kosmos.mockNotificationActivityStarter).onNotificationBubbleIconClicked(entry) + } + + @Test + @EnableFlags(NotificationBundleUi.FLAG_NAME) + fun onNotificationActionClicked() { + val notification: Notification = + Notification.Builder(mContext, "") + .setSmallIcon(R.drawable.ic_person) + .addAction(Mockito.mock(Notification.Action::class.java)) + .build() + + val entry = NotificationEntryBuilder().setNotification(notification).build() + + underTest = factory.create(entry) as NotificationEntryAdapter + underTest.onNotificationActionClicked() + verify(kosmos.mockNotificationActionClickManager).onNotificationActionClicked(entry) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/BundleCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/BundleCoordinatorTest.kt index 8a9720ea3cb0..732180810880 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/BundleCoordinatorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/BundleCoordinatorTest.kt @@ -34,6 +34,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.MockitoAnnotations +import kotlin.test.assertEquals @SmallTest @RunWith(AndroidJUnit4::class) @@ -87,6 +88,18 @@ class BundleCoordinatorTest : SysuiTestCase() { isFalse() } + @Test + fun testBundler_getBundleIdOrNull_returnBundleId() { + val classifiedEntry = makeEntryOfChannelType(PROMOTIONS_ID) + assertEquals(coordinator.bundler.getBundleIdOrNull(classifiedEntry), PROMOTIONS_ID) + } + + @Test + fun testBundler_getBundleIdOrNull_returnNull() { + val unclassifiedEntry = makeEntryOfChannelType("not system channel") + assertEquals(coordinator.bundler.getBundleIdOrNull(unclassifiedEntry), null) + } + private fun makeEntryOfChannelType( type: String, buildBlock: NotificationEntryBuilder.() -> Unit = {} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt index 30983550f0f9..44d88c31c5f1 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt @@ -50,6 +50,8 @@ import com.android.systemui.statusbar.notification.interruption.NotificationInte import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderWrapper.DecisionImpl import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderWrapper.FullScreenIntentDecisionImpl import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider +import com.android.systemui.statusbar.notification.row.mockNotificationActionClickManager +import com.android.systemui.statusbar.notification.shared.NotificationBundleUi import com.android.systemui.statusbar.phone.NotificationGroupTestHelper import com.android.systemui.testKosmos import com.android.systemui.util.concurrency.FakeExecutor @@ -138,6 +140,7 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { headsUpViewBinder, visualInterruptionDecisionProvider, remoteInputManager, + kosmos.mockNotificationActionClickManager, launchFullScreenIntentProvider, flags, statusBarNotificationChipsInteractor, @@ -161,8 +164,14 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { verify(notifPipeline).addOnBeforeFinalizeFilterListener(capture()) } onHeadsUpChangedListener = withArgCaptor { verify(headsUpManager).addListener(capture()) } - actionPressListener = withArgCaptor { - verify(remoteInputManager).addActionPressListener(capture()) + actionPressListener = if (NotificationBundleUi.isEnabled) { + withArgCaptor { + verify(kosmos.mockNotificationActionClickManager).addActionClickListener(capture()) + } + } else { + withArgCaptor { + verify(remoteInputManager).addActionPressListener(capture()) + } } given(headsUpManager.allEntries).willAnswer { huns.stream() } given(headsUpManager.isHeadsUpEntry(anyString())).willAnswer { invocation -> @@ -260,7 +269,7 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { addHUN(entry) actionPressListener.accept(entry) - verify(headsUpManager, times(1)).setUserActionMayIndirectlyRemove(entry) + verify(headsUpManager, times(1)).setUserActionMayIndirectlyRemove(entry.key) whenever(headsUpManager.canRemoveImmediately(anyString())).thenReturn(true) assertFalse(notifLifetimeExtender.maybeExtendLifetime(entry, 0)) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorTest.kt index a90539413adb..e28e587d2cdc 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorTest.kt @@ -28,6 +28,7 @@ import com.android.systemui.kosmos.testScope import com.android.systemui.plugins.statusbar.statusBarStateController import com.android.systemui.shade.shadeTestUtil import com.android.systemui.statusbar.SysuiStatusBarStateController +import com.android.systemui.statusbar.notification.collection.BundleEntry import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.NotificationEntry @@ -280,6 +281,8 @@ class LockScreenMinimalismCoordinatorTest : SysuiTestCase() { val group = GroupEntryBuilder().setSummary(parent).addChild(child1).addChild(child2).build() val listEntryList = listOf(group, solo1, solo2) val notificationEntryList = listOf(solo1, solo2, parent, child1, child2) + val bundle = BundleEntry("bundleKey") + val bundleList = listOf(bundle) runCoordinatorTest { // All entries are added (and now unseen) @@ -300,6 +303,11 @@ class LockScreenMinimalismCoordinatorTest : SysuiTestCase() { assertThatTopOngoingKey().isEqualTo(null) assertThatTopUnseenKey().isEqualTo(solo1.key) + // TEST: bundle is not picked + onBeforeTransformGroupsListener.onBeforeTransformGroups(bundleList) + assertThatTopOngoingKey().isEqualTo(null) + assertThatTopUnseenKey().isEqualTo(null) + // TEST: if top-ranked unseen is colorized, fall back to #2 ranked unseen solo1.setColorizedFgs(true) onBeforeTransformGroupsListener.onBeforeTransformGroups(listEntryList) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplTest.kt index e22acd53e584..8560b66d961f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplTest.kt @@ -37,6 +37,7 @@ import com.android.systemui.flags.andSceneContainer import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.testScope import com.android.systemui.kosmos.useUnconfinedTestDispatcher +import com.android.systemui.log.assertLogsWtfs import com.android.systemui.res.R import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.shade.shadeTestUtil @@ -205,8 +206,7 @@ class HeadsUpManagerImplTest(flags: FlagsParameterization) : SysuiTestCase() { fun pinnedHeadsUpStatuses_pinnedByUser_butFlagOff_returnsNotPinned() { val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) entry.row = testHelper.createRow() - underTest.showNotification(entry, isPinnedByUser = true) - + assertLogsWtfs { underTest.showNotification(entry, isPinnedByUser = true) } assertThat(underTest.hasPinnedHeadsUp()).isFalse() assertThat(underTest.pinnedHeadsUpStatus()).isEqualTo(PinnedStatus.NotPinned) } @@ -1071,7 +1071,7 @@ class HeadsUpManagerImplTest(flags: FlagsParameterization) : SysuiTestCase() { assertThat(underTest.canRemoveImmediately(notifEntry.key)).isFalse() - underTest.setUserActionMayIndirectlyRemove(notifEntry) + underTest.setUserActionMayIndirectlyRemove(notifEntry.key) assertThat(underTest.canRemoveImmediately(notifEntry.key)).isTrue() } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java index 8e7733bb23ca..e6b2c2541447 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java @@ -29,6 +29,7 @@ import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -60,11 +61,11 @@ import com.android.systemui.TestableDependency; import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.flags.FakeFeatureFlagsClassic; import com.android.systemui.flags.FeatureFlagsClassic; +import com.android.systemui.media.NotificationMediaManager; import com.android.systemui.media.controls.util.MediaFeatureFlag; import com.android.systemui.media.dialog.MediaOutputDialogManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.res.R; -import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.SmartReplyController; @@ -363,6 +364,7 @@ public class NotificationTestHelper { .setUid(UID) .setInitialPid(2000) .setNotification(summary) + .setUser(USER_HANDLE) .setParent(GroupEntry.ROOT_ENTRY) .build(); GroupEntryBuilder groupEntry = new GroupEntryBuilder() @@ -743,11 +745,12 @@ public class NotificationTestHelper { mock(MetricsLogger.class), mock(PeopleNotificationIdentifier.class), mock(NotificationIconStyleProvider.class), - mock(VisualStabilityCoordinator.class) + mock(VisualStabilityCoordinator.class), + mock(NotificationActionClickManager.class) ).create(entry); row.initialize( - entryAdapter, + spy(entryAdapter), entry, mock(RemoteInputViewSubcomponent.Factory.class), APP_NAME, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt index f6c031f54818..8e3bdc48398f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt @@ -219,7 +219,6 @@ class StackStateAnimatorTest : SysuiTestCase() { ) } - @DisableFlags(Flags.FLAG_PHYSICAL_NOTIFICATION_MOVEMENT) @Test @EnableFlags(NotificationsHunSharedAnimationValues.FLAG_NAME) fun startAnimationForEvents_headsUpFromBottom_startsHeadsUpAppearAnim_flagOn() { @@ -246,7 +245,7 @@ class StackStateAnimatorTest : SysuiTestCase() { } @Test - @DisableFlags(NotificationsHunSharedAnimationValues.FLAG_NAME) + @DisableFlags(NotificationsHunSharedAnimationValues.FLAG_NAME, Flags.FLAG_PHYSICAL_NOTIFICATION_MOVEMENT) fun startAnimationForEvents_startsHeadsUpDisappearAnim_flagOff() { val disappearDuration = ANIMATION_DURATION_HEADS_UP_DISAPPEAR.toLong() val event = AnimationEvent(view, AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR) @@ -277,6 +276,7 @@ class StackStateAnimatorTest : SysuiTestCase() { @Test @EnableFlags(NotificationsHunSharedAnimationValues.FLAG_NAME) + @DisableFlags(Flags.FLAG_PHYSICAL_NOTIFICATION_MOVEMENT) fun startAnimationForEvents_startsHeadsUpDisappearAnim_flagOn() { val disappearDuration = ANIMATION_DURATION_HEADS_UP_DISAPPEAR.toLong() val event = AnimationEvent(view, AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt index 14e7cdc50227..3b836b774788 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt @@ -16,11 +16,11 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel -import android.testing.TestableLooper.RunWithLooper import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.kosmos.testScope import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds @@ -32,7 +32,7 @@ import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) -@RunWithLooper +@EnableSceneContainer class NotificationsPlaceholderViewModelTest : SysuiTestCase() { private val kosmos = testKosmos() private val underTest by lazy { kosmos.notificationsPlaceholderViewModel } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java index e2330f448a1b..1ea41de63e64 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java @@ -61,8 +61,8 @@ import com.android.systemui.keyguard.shared.model.KeyguardState; import com.android.systemui.keyguard.shared.model.TransitionState; import com.android.systemui.keyguard.shared.model.TransitionStep; import com.android.systemui.log.SessionTracker; +import com.android.systemui.media.NotificationMediaManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.policy.KeyguardStateController; diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorTest.kt index bab349aa7a74..f4204af7829b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorTest.kt @@ -82,6 +82,7 @@ class OngoingCallInteractorTest : SysuiTestCase() { statusBarChipIconView = testIconView, contentIntent = testIntent, promotedContent = testPromotedContent, + isAppVisible = false, ) // Verify model is InCall and has the correct icon, intent, and promoted content. @@ -92,12 +93,12 @@ class OngoingCallInteractorTest : SysuiTestCase() { assertThat(model.intent).isSameInstanceAs(testIntent) assertThat(model.notificationKey).isEqualTo(key) assertThat(model.promotedContent).isSameInstanceAs(testPromotedContent) + assertThat(model.isAppVisible).isFalse() } @Test fun ongoingCallNotification_setsAllFields_withAppVisible() = kosmos.runTest { - kosmos.activityManagerRepository.fake.startingIsAppVisibleValue = true val latest by collectLastValue(underTest.ongoingCallState) // Set up notification with icon view and intent @@ -112,17 +113,19 @@ class OngoingCallInteractorTest : SysuiTestCase() { statusBarChipIconView = testIconView, contentIntent = testIntent, promotedContent = testPromotedContent, + isAppVisible = true, ) - // Verify model is InCallWithVisibleApp and has the correct icon, intent, and promoted - // content. - assertThat(latest).isInstanceOf(OngoingCallModel.InCallWithVisibleApp::class.java) - val model = latest as OngoingCallModel.InCallWithVisibleApp + // Verify model is InCall with visible app and has the correct icon, intent, and + // promoted content. + assertThat(latest).isInstanceOf(OngoingCallModel.InCall::class.java) + val model = latest as OngoingCallModel.InCall assertThat(model.startTimeMs).isEqualTo(startTimeMs) assertThat(model.notificationIconView).isSameInstanceAs(testIconView) assertThat(model.intent).isSameInstanceAs(testIntent) assertThat(model.notificationKey).isEqualTo(key) assertThat(model.promotedContent).isSameInstanceAs(testPromotedContent) + assertThat(model.isAppVisible).isTrue() } @Test @@ -139,23 +142,23 @@ class OngoingCallInteractorTest : SysuiTestCase() { @Test fun ongoingCallNotification_appVisibleInitially_emitsInCallWithVisibleApp() = kosmos.runTest { - kosmos.activityManagerRepository.fake.startingIsAppVisibleValue = true val latest by collectLastValue(underTest.ongoingCallState) - addOngoingCallState(uid = UID) + addOngoingCallState(uid = UID, isAppVisible = true) - assertThat(latest).isInstanceOf(OngoingCallModel.InCallWithVisibleApp::class.java) + assertThat(latest).isInstanceOf(OngoingCallModel.InCall::class.java) + assertThat((latest as OngoingCallModel.InCall).isAppVisible).isTrue() } @Test fun ongoingCallNotification_appNotVisibleInitially_emitsInCall() = kosmos.runTest { - kosmos.activityManagerRepository.fake.startingIsAppVisibleValue = false val latest by collectLastValue(underTest.ongoingCallState) - addOngoingCallState(uid = UID) + addOngoingCallState(uid = UID, isAppVisible = false) assertThat(latest).isInstanceOf(OngoingCallModel.InCall::class.java) + assertThat((latest as OngoingCallModel.InCall).isAppVisible).isFalse() } @Test @@ -164,17 +167,19 @@ class OngoingCallInteractorTest : SysuiTestCase() { val latest by collectLastValue(underTest.ongoingCallState) // Start with notification and app not visible - kosmos.activityManagerRepository.fake.startingIsAppVisibleValue = false - addOngoingCallState(uid = UID) + addOngoingCallState(uid = UID, isAppVisible = false) assertThat(latest).isInstanceOf(OngoingCallModel.InCall::class.java) + assertThat((latest as OngoingCallModel.InCall).isAppVisible).isFalse() // App becomes visible kosmos.activityManagerRepository.fake.setIsAppVisible(UID, true) - assertThat(latest).isInstanceOf(OngoingCallModel.InCallWithVisibleApp::class.java) + assertThat(latest).isInstanceOf(OngoingCallModel.InCall::class.java) + assertThat((latest as OngoingCallModel.InCall).isAppVisible).isTrue() // App becomes invisible again kosmos.activityManagerRepository.fake.setIsAppVisible(UID, false) assertThat(latest).isInstanceOf(OngoingCallModel.InCall::class.java) + assertThat((latest as OngoingCallModel.InCall).isAppVisible).isFalse() } @Test @@ -238,18 +243,17 @@ class OngoingCallInteractorTest : SysuiTestCase() { .ongoingProcessRequiresStatusBarVisible ) - kosmos.activityManagerRepository.fake.startingIsAppVisibleValue = false - - addOngoingCallState(uid = UID) + addOngoingCallState(uid = UID, isAppVisible = false) assertThat(ongoingCallState).isInstanceOf(OngoingCallModel.InCall::class.java) + assertThat((ongoingCallState as OngoingCallModel.InCall).isAppVisible).isFalse() assertThat(requiresStatusBarVisibleInRepository).isTrue() assertThat(requiresStatusBarVisibleInWindowController).isTrue() kosmos.activityManagerRepository.fake.setIsAppVisible(UID, true) - assertThat(ongoingCallState) - .isInstanceOf(OngoingCallModel.InCallWithVisibleApp::class.java) + assertThat(ongoingCallState).isInstanceOf(OngoingCallModel.InCall::class.java) + assertThat((ongoingCallState as OngoingCallModel.InCall).isAppVisible).isTrue() assertThat(requiresStatusBarVisibleInRepository).isFalse() assertThat(requiresStatusBarVisibleInWindowController).isFalse() } @@ -265,6 +269,7 @@ class OngoingCallInteractorTest : SysuiTestCase() { addOngoingCallState() assertThat(ongoingCallState).isInstanceOf(OngoingCallModel.InCall::class.java) + assertThat((ongoingCallState as OngoingCallModel.InCall).isAppVisible).isFalse() verify(kosmos.swipeStatusBarAwayGestureHandler, never()) .addOnGestureDetectedCallback(any(), any()) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorKairosTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorKairosTest.kt index c89dc5722c7a..33689931e6ed 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorKairosTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorKairosTest.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,789 +26,701 @@ import com.android.settingslib.mobile.MobileIconCarrierIdOverrides import com.android.settingslib.mobile.MobileIconCarrierIdOverridesImpl import com.android.settingslib.mobile.TelephonyIcons import com.android.systemui.SysuiTestCase -import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.flags.Flags.FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS +import com.android.systemui.flags.fake +import com.android.systemui.flags.featureFlagsClassic +import com.android.systemui.kairos.ActivatedKairosFixture +import com.android.systemui.kairos.ExperimentalKairosApi +import com.android.systemui.kairos.KairosTestScope +import com.android.systemui.kairos.MutableState +import com.android.systemui.kairos.kairos +import com.android.systemui.kairos.map +import com.android.systemui.kairos.runKairosTest +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.log.table.logcatTableLogBuffer import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.CarrierMergedNetworkType import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.OverrideNetworkType -import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository +import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepositoryKairos +import com.android.systemui.statusbar.pipeline.mobile.data.repository.mobileMappingsProxy import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor.Companion.FIVE_G_OVERRIDE import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor.Companion.FOUR_G import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor.Companion.THREE_G import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel -import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy import com.android.systemui.testKosmos -import com.android.systemui.util.mockito.any -import com.android.systemui.util.mockito.mock -import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.test.TestScope -import kotlinx.coroutines.test.UnconfinedTestDispatcher -import kotlinx.coroutines.test.runTest -import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.anyString +import org.mockito.kotlin.any +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +@OptIn(ExperimentalKairosApi::class) @SmallTest @RunWith(AndroidJUnit4::class) class MobileIconInteractorKairosTest : SysuiTestCase() { - private val kosmos = testKosmos() + private val kosmos = + testKosmos().apply { + useUnconfinedTestDispatcher() + featureFlagsClassic.fake.apply { setDefault(FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS) } + } - private lateinit var underTest: MobileIconInteractorKairos - private val mobileMappingsProxy = FakeMobileMappingsProxy() - private val mobileIconsInteractor = FakeMobileIconsInteractor(mobileMappingsProxy, mock()) + private val Kosmos.tableLogBuffer by Fixture { + logcatTableLogBuffer(this, "MobileIconInteractorKairosTest") + } - private val connectionRepository = - FakeMobileConnectionRepository( - SUB_1_ID, - logcatTableLogBuffer(kosmos, "MobileIconInteractorTest"), - ) + private var Kosmos.overrides: MobileIconCarrierIdOverrides by Fixture { + MobileIconCarrierIdOverridesImpl() + } - private val testDispatcher = UnconfinedTestDispatcher() - private val testScope = TestScope(testDispatcher) + private val Kosmos.defaultSubscriptionHasDataEnabled by Fixture { MutableState(kairos, true) } - @Before - fun setUp() { - underTest = createInteractor() + private val Kosmos.alwaysShowDataRatIcon by Fixture { MutableState(kairos, false) } - mobileIconsInteractor.activeDataConnectionHasDataEnabled.value = true - connectionRepository.isInService.value = true - } + private val Kosmos.alwaysUseCdmaLevel by Fixture { MutableState(kairos, false) } - @Test - fun gsm_usesGsmLevel() = - testScope.runTest { - connectionRepository.isGsm.value = true - connectionRepository.primaryLevel.value = GSM_LEVEL - connectionRepository.cdmaLevel.value = CDMA_LEVEL + private val Kosmos.isSingleCarrier by Fixture { MutableState(kairos, true) } - var latest: Int? = null - val job = underTest.signalLevelIcon.onEach { latest = it.level }.launchIn(this) + private val Kosmos.mobileIsDefault by Fixture { MutableState(kairos, false) } - assertThat(latest).isEqualTo(GSM_LEVEL) + private val Kosmos.defaultMobileIconMapping by Fixture { + MutableState(kairos, fakeMobileIconsInteractor.TEST_MAPPING) + } - job.cancel() - } + private val Kosmos.defaultMobileIconGroup by Fixture { MutableState(kairos, TelephonyIcons.G) } - @Test - fun gsm_alwaysShowCdmaTrue_stillUsesGsmLevel() = - testScope.runTest { - connectionRepository.isGsm.value = true - connectionRepository.primaryLevel.value = GSM_LEVEL - connectionRepository.cdmaLevel.value = CDMA_LEVEL - mobileIconsInteractor.alwaysUseCdmaLevel.value = true + private val Kosmos.isDefaultConnectionFailed by Fixture { MutableState(kairos, false) } - var latest: Int? = null - val job = underTest.signalLevelIcon.onEach { latest = it.level }.launchIn(this) + private val Kosmos.isForceHidden by Fixture { MutableState(kairos, false) } - assertThat(latest).isEqualTo(GSM_LEVEL) + private val Kosmos.underTest by ActivatedKairosFixture { + MobileIconInteractorKairosImpl( + defaultSubscriptionHasDataEnabled, + alwaysShowDataRatIcon, + alwaysUseCdmaLevel, + isSingleCarrier, + mobileIsDefault, + defaultMobileIconMapping, + defaultMobileIconGroup, + isDefaultConnectionFailed, + isForceHidden, + connectionRepository = connectionRepo, + context = context, + carrierIdOverrides = overrides, + ) + } - job.cancel() + private val Kosmos.connectionRepo by Fixture { + FakeMobileConnectionRepositoryKairos(SUB_1_ID, kairos, tableLogBuffer).apply { + dataEnabled.setValue(true) + isInService.setValue(true) } + } + + private fun runTest(block: suspend KairosTestScope.() -> Unit) = + kosmos.run { runKairosTest { block() } } @Test - fun notGsm_level_default_unknown() = - testScope.runTest { - connectionRepository.isGsm.value = false + fun gsm_usesGsmLevel() = runTest { + connectionRepo.isGsm.setValue(true) + connectionRepo.primaryLevel.setValue(GSM_LEVEL) + connectionRepo.cdmaLevel.setValue(CDMA_LEVEL) - var latest: Int? = null - val job = underTest.signalLevelIcon.onEach { latest = it.level }.launchIn(this) + val latest by underTest.signalLevelIcon.collectLastValue() - assertThat(latest).isEqualTo(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN) - job.cancel() - } + assertThat(latest?.level).isEqualTo(GSM_LEVEL) + } @Test - fun notGsm_alwaysShowCdmaTrue_usesCdmaLevel() = - testScope.runTest { - connectionRepository.isGsm.value = false - connectionRepository.primaryLevel.value = GSM_LEVEL - connectionRepository.cdmaLevel.value = CDMA_LEVEL - mobileIconsInteractor.alwaysUseCdmaLevel.value = true + fun gsm_alwaysShowCdmaTrue_stillUsesGsmLevel() = runTest { + connectionRepo.isGsm.setValue(true) + connectionRepo.primaryLevel.setValue(GSM_LEVEL) + connectionRepo.cdmaLevel.setValue(CDMA_LEVEL) + // mobileIconsInteractor.alwaysUseCdmaLevel.setValue(true) + alwaysUseCdmaLevel.setValue(true) - var latest: Int? = null - val job = underTest.signalLevelIcon.onEach { latest = it.level }.launchIn(this) + val latest by underTest.signalLevelIcon.collectLastValue() - assertThat(latest).isEqualTo(CDMA_LEVEL) - - job.cancel() - } + assertThat(latest?.level).isEqualTo(GSM_LEVEL) + } @Test - fun notGsm_alwaysShowCdmaFalse_usesPrimaryLevel() = - testScope.runTest { - connectionRepository.isGsm.value = false - connectionRepository.primaryLevel.value = GSM_LEVEL - connectionRepository.cdmaLevel.value = CDMA_LEVEL - mobileIconsInteractor.alwaysUseCdmaLevel.value = false - - var latest: Int? = null - val job = underTest.signalLevelIcon.onEach { latest = it.level }.launchIn(this) + fun notGsm_level_default_unknown() = runTest { + connectionRepo.isGsm.setValue(false) - assertThat(latest).isEqualTo(GSM_LEVEL) + val latest by underTest.signalLevelIcon.collectLastValue() - job.cancel() - } + assertThat(latest?.level).isEqualTo(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN) + } @Test - fun numberOfLevels_comesFromRepo_whenApplicable() = - testScope.runTest { - var latest: Int? = null - val job = - underTest.signalLevelIcon - .onEach { latest = (it as? SignalIconModel.Cellular)?.numberOfLevels } - .launchIn(this) - - connectionRepository.numberOfLevels.value = 5 - assertThat(latest).isEqualTo(5) + fun notGsm_alwaysShowCdmaTrue_usesCdmaLevel() = runTest { + connectionRepo.isGsm.setValue(false) + connectionRepo.primaryLevel.setValue(GSM_LEVEL) + connectionRepo.cdmaLevel.setValue(CDMA_LEVEL) + // mobileIconsInteractor.alwaysUseCdmaLevel.setValue(true) + alwaysUseCdmaLevel.setValue(true) - connectionRepository.numberOfLevels.value = 4 - assertThat(latest).isEqualTo(4) + val latest by underTest.signalLevelIcon.collectLastValue() - job.cancel() - } + assertThat(latest?.level).isEqualTo(CDMA_LEVEL) + } @Test - fun inflateSignalStrength_arbitrarilyAddsOneToTheReportedLevel() = - testScope.runTest { - connectionRepository.inflateSignalStrength.value = false - val latest by collectLastValue(underTest.signalLevelIcon) + fun notGsm_alwaysShowCdmaFalse_usesPrimaryLevel() = runTest { + connectionRepo.isGsm.setValue(false) + connectionRepo.primaryLevel.setValue(GSM_LEVEL) + connectionRepo.cdmaLevel.setValue(CDMA_LEVEL) + // mobileIconsInteractor.alwaysUseCdmaLevel.setValue(false) + alwaysUseCdmaLevel.setValue(false) - connectionRepository.primaryLevel.value = 4 - assertThat(latest!!.level).isEqualTo(4) + val latest by underTest.signalLevelIcon.collectLastValue() - connectionRepository.inflateSignalStrength.value = true - connectionRepository.primaryLevel.value = 4 - - // when INFLATE_SIGNAL_STRENGTH is true, we add 1 to the reported signal level - assertThat(latest!!.level).isEqualTo(5) - } + assertThat(latest?.level).isEqualTo(GSM_LEVEL) + } @Test - fun networkSlice_configOn_hasPrioritizedCaps_showsSlice() = - testScope.runTest { - connectionRepository.allowNetworkSliceIndicator.value = true - val latest by collectLastValue(underTest.showSliceAttribution) + fun numberOfLevels_comesFromRepo_whenApplicable() = runTest { + val latest by + underTest.signalLevelIcon + .map { (it as? SignalIconModel.Cellular)?.numberOfLevels } + .collectLastValue() - connectionRepository.hasPrioritizedNetworkCapabilities.value = true + connectionRepo.numberOfLevels.setValue(5) + assertThat(latest).isEqualTo(5) - assertThat(latest).isTrue() - } + connectionRepo.numberOfLevels.setValue(4) + assertThat(latest).isEqualTo(4) + } @Test - fun networkSlice_configOn_noPrioritizedCaps_noSlice() = - testScope.runTest { - connectionRepository.allowNetworkSliceIndicator.value = true - val latest by collectLastValue(underTest.showSliceAttribution) + fun inflateSignalStrength_arbitrarilyAddsOneToTheReportedLevel() = runTest { + connectionRepo.inflateSignalStrength.setValue(false) + val latest by underTest.signalLevelIcon.collectLastValue() - connectionRepository.hasPrioritizedNetworkCapabilities.value = false + connectionRepo.primaryLevel.setValue(4) + assertThat(latest!!.level).isEqualTo(4) - assertThat(latest).isFalse() - } + connectionRepo.inflateSignalStrength.setValue(true) + connectionRepo.primaryLevel.setValue(4) + + // when INFLATE_SIGNAL_STRENGTH is true, we add 1 to the reported signal level + assertThat(latest!!.level).isEqualTo(5) + } @Test - fun networkSlice_configOff_hasPrioritizedCaps_noSlice() = - testScope.runTest { - connectionRepository.allowNetworkSliceIndicator.value = false - val latest by collectLastValue(underTest.showSliceAttribution) + fun networkSlice_configOn_hasPrioritizedCaps_showsSlice() = runTest { + connectionRepo.allowNetworkSliceIndicator.setValue(true) + val latest by underTest.showSliceAttribution.collectLastValue() - connectionRepository.hasPrioritizedNetworkCapabilities.value = true + connectionRepo.hasPrioritizedNetworkCapabilities.setValue(true) - assertThat(latest).isFalse() - } + assertThat(latest).isTrue() + } @Test - fun networkSlice_configOff_noPrioritizedCaps_noSlice() = - testScope.runTest { - connectionRepository.allowNetworkSliceIndicator.value = false - val latest by collectLastValue(underTest.showSliceAttribution) + fun networkSlice_configOn_noPrioritizedCaps_noSlice() = runTest { + connectionRepo.allowNetworkSliceIndicator.setValue(true) + val latest by underTest.showSliceAttribution.collectLastValue() - connectionRepository.hasPrioritizedNetworkCapabilities.value = false + connectionRepo.hasPrioritizedNetworkCapabilities.setValue(false) - assertThat(latest).isFalse() - } + assertThat(latest).isFalse() + } @Test - fun iconGroup_three_g() = - testScope.runTest { - connectionRepository.resolvedNetworkType.value = - DefaultNetworkType(mobileMappingsProxy.toIconKey(THREE_G)) - - var latest: NetworkTypeIconModel? = null - val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this) + fun networkSlice_configOff_hasPrioritizedCaps_noSlice() = runTest { + connectionRepo.allowNetworkSliceIndicator.setValue(false) + val latest by underTest.showSliceAttribution.collectLastValue() - assertThat(latest).isEqualTo(NetworkTypeIconModel.DefaultIcon(TelephonyIcons.THREE_G)) + connectionRepo.hasPrioritizedNetworkCapabilities.setValue(true) - job.cancel() - } + assertThat(latest).isFalse() + } @Test - fun iconGroup_updates_on_change() = - testScope.runTest { - connectionRepository.resolvedNetworkType.value = - DefaultNetworkType(mobileMappingsProxy.toIconKey(THREE_G)) + fun networkSlice_configOff_noPrioritizedCaps_noSlice() = runTest { + connectionRepo.allowNetworkSliceIndicator.setValue(false) + val latest by underTest.showSliceAttribution.collectLastValue() - var latest: NetworkTypeIconModel? = null - val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this) + connectionRepo.hasPrioritizedNetworkCapabilities.setValue(false) - connectionRepository.resolvedNetworkType.value = - DefaultNetworkType(mobileMappingsProxy.toIconKey(FOUR_G)) - - assertThat(latest).isEqualTo(NetworkTypeIconModel.DefaultIcon(TelephonyIcons.FOUR_G)) - - job.cancel() - } + assertThat(latest).isFalse() + } @Test - fun iconGroup_5g_override_type() = - testScope.runTest { - connectionRepository.resolvedNetworkType.value = - OverrideNetworkType(mobileMappingsProxy.toIconKeyOverride(FIVE_G_OVERRIDE)) - - var latest: NetworkTypeIconModel? = null - val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this) + fun iconGroup_three_g() = runTest { + connectionRepo.resolvedNetworkType.setValue( + DefaultNetworkType(mobileMappingsProxy.toIconKey(THREE_G)) + ) - assertThat(latest).isEqualTo(NetworkTypeIconModel.DefaultIcon(TelephonyIcons.NR_5G)) + val latest by underTest.networkTypeIconGroup.collectLastValue() - job.cancel() - } + assertThat(latest).isEqualTo(NetworkTypeIconModel.DefaultIcon(TelephonyIcons.THREE_G)) + } @Test - fun iconGroup_default_if_no_lookup() = - testScope.runTest { - connectionRepository.resolvedNetworkType.value = - DefaultNetworkType(mobileMappingsProxy.toIconKey(NETWORK_TYPE_UNKNOWN)) + fun iconGroup_updates_on_change() = runTest { + connectionRepo.resolvedNetworkType.setValue( + DefaultNetworkType(mobileMappingsProxy.toIconKey(THREE_G)) + ) - var latest: NetworkTypeIconModel? = null - val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this) + val latest by underTest.networkTypeIconGroup.collectLastValue() - assertThat(latest) - .isEqualTo(NetworkTypeIconModel.DefaultIcon(FakeMobileIconsInteractor.DEFAULT_ICON)) + connectionRepo.resolvedNetworkType.setValue( + DefaultNetworkType(mobileMappingsProxy.toIconKey(FOUR_G)) + ) - job.cancel() - } + assertThat(latest).isEqualTo(NetworkTypeIconModel.DefaultIcon(TelephonyIcons.FOUR_G)) + } @Test - fun iconGroup_carrierMerged_usesOverride() = - testScope.runTest { - connectionRepository.resolvedNetworkType.value = CarrierMergedNetworkType - - var latest: NetworkTypeIconModel? = null - val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this) + fun iconGroup_5g_override_type() = runTest { + connectionRepo.resolvedNetworkType.setValue( + OverrideNetworkType(mobileMappingsProxy.toIconKeyOverride(FIVE_G_OVERRIDE)) + ) - assertThat(latest) - .isEqualTo( - NetworkTypeIconModel.DefaultIcon(CarrierMergedNetworkType.iconGroupOverride) - ) + val latest by underTest.networkTypeIconGroup.collectLastValue() - job.cancel() - } + assertThat(latest).isEqualTo(NetworkTypeIconModel.DefaultIcon(TelephonyIcons.NR_5G)) + } @Test - fun overrideIcon_usesCarrierIdOverride() = - testScope.runTest { - val overrides = - mock<MobileIconCarrierIdOverrides>().also { - whenever(it.carrierIdEntryExists(anyInt())).thenReturn(true) - whenever(it.getOverrideFor(anyInt(), anyString(), any())).thenReturn(1234) - } + fun iconGroup_default_if_no_lookup() = runTest { + connectionRepo.resolvedNetworkType.setValue( + DefaultNetworkType(mobileMappingsProxy.toIconKey(NETWORK_TYPE_UNKNOWN)) + ) - underTest = createInteractor(overrides) + val latest by underTest.networkTypeIconGroup.collectLastValue() - connectionRepository.resolvedNetworkType.value = - DefaultNetworkType(mobileMappingsProxy.toIconKey(THREE_G)) + assertThat(latest) + .isEqualTo(NetworkTypeIconModel.DefaultIcon(FakeMobileIconsInteractor.DEFAULT_ICON)) + } - var latest: NetworkTypeIconModel? = null - val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this) + @Test + fun iconGroup_carrierMerged_usesOverride() = runTest { + connectionRepo.resolvedNetworkType.setValue(CarrierMergedNetworkType) - assertThat(latest) - .isEqualTo(NetworkTypeIconModel.OverriddenIcon(TelephonyIcons.THREE_G, 1234)) + val latest by underTest.networkTypeIconGroup.collectLastValue() - job.cancel() - } + assertThat(latest) + .isEqualTo(NetworkTypeIconModel.DefaultIcon(CarrierMergedNetworkType.iconGroupOverride)) + } @Test - fun alwaysShowDataRatIcon_matchesParent() = - testScope.runTest { - var latest: Boolean? = null - val job = underTest.alwaysShowDataRatIcon.onEach { latest = it }.launchIn(this) + fun overrideIcon_usesCarrierIdOverride() = runTest { + overrides = + mock<MobileIconCarrierIdOverrides> { + on { carrierIdEntryExists(anyInt()) } doReturn true + on { getOverrideFor(anyInt(), anyString(), any()) } doReturn 1234 + } - mobileIconsInteractor.alwaysShowDataRatIcon.value = true - assertThat(latest).isTrue() + connectionRepo.resolvedNetworkType.setValue( + DefaultNetworkType(mobileMappingsProxy.toIconKey(THREE_G)) + ) - mobileIconsInteractor.alwaysShowDataRatIcon.value = false - assertThat(latest).isFalse() + val latest by underTest.networkTypeIconGroup.collectLastValue() - job.cancel() - } + assertThat(latest) + .isEqualTo(NetworkTypeIconModel.OverriddenIcon(TelephonyIcons.THREE_G, 1234)) + } @Test - fun dataState_connected() = - testScope.runTest { - var latest: Boolean? = null - val job = underTest.isDataConnected.onEach { latest = it }.launchIn(this) + fun alwaysShowDataRatIcon_matchesParent() = runTest { + val latest by underTest.alwaysShowDataRatIcon.collectLastValue() - connectionRepository.dataConnectionState.value = DataConnectionState.Connected + // mobileIconsInteractor.alwaysShowDataRatIcon.setValue(true) + alwaysShowDataRatIcon.setValue(true) - assertThat(latest).isTrue() + assertThat(latest).isTrue() - job.cancel() - } + // mobileIconsInteractor.alwaysShowDataRatIcon.setValue(false) + alwaysShowDataRatIcon.setValue(false) - @Test - fun dataState_notConnected() = - testScope.runTest { - var latest: Boolean? = null - val job = underTest.isDataConnected.onEach { latest = it }.launchIn(this) + assertThat(latest).isFalse() + } - connectionRepository.dataConnectionState.value = DataConnectionState.Disconnected + @Test + fun dataState_connected() = runTest { + val latest by underTest.isDataConnected.collectLastValue() - assertThat(latest).isFalse() + connectionRepo.dataConnectionState.setValue(DataConnectionState.Connected) - job.cancel() - } + assertThat(latest).isTrue() + } @Test - fun isInService_usesRepositoryValue() = - testScope.runTest { - var latest: Boolean? = null - val job = underTest.isInService.onEach { latest = it }.launchIn(this) + fun dataState_notConnected() = runTest { + val latest by underTest.isDataConnected.collectLastValue() - connectionRepository.isInService.value = true + connectionRepo.dataConnectionState.setValue(DataConnectionState.Disconnected) - assertThat(latest).isTrue() + assertThat(latest).isFalse() + } - connectionRepository.isInService.value = false + @Test + fun isInService_usesRepositoryValue() = runTest { + val latest by underTest.isInService.collectLastValue() - assertThat(latest).isFalse() + connectionRepo.isInService.setValue(true) - job.cancel() - } + assertThat(latest).isTrue() - @Test - fun roaming_isGsm_usesConnectionModel() = - testScope.runTest { - var latest: Boolean? = null - val job = underTest.isRoaming.onEach { latest = it }.launchIn(this) + connectionRepo.isInService.setValue(false) - connectionRepository.cdmaRoaming.value = true - connectionRepository.isGsm.value = true - connectionRepository.isRoaming.value = false + assertThat(latest).isFalse() + } - assertThat(latest).isFalse() + @Test + fun roaming_isGsm_usesConnectionModel() = runTest { + val latest by underTest.isRoaming.collectLastValue() - connectionRepository.isRoaming.value = true + connectionRepo.cdmaRoaming.setValue(true) + connectionRepo.isGsm.setValue(true) + connectionRepo.isRoaming.setValue(false) - assertThat(latest).isTrue() + assertThat(latest).isFalse() - job.cancel() - } + connectionRepo.isRoaming.setValue(true) - @Test - fun roaming_isCdma_usesCdmaRoamingBit() = - testScope.runTest { - var latest: Boolean? = null - val job = underTest.isRoaming.onEach { latest = it }.launchIn(this) + assertThat(latest).isTrue() + } - connectionRepository.cdmaRoaming.value = false - connectionRepository.isGsm.value = false - connectionRepository.isRoaming.value = true + @Test + fun roaming_isCdma_usesCdmaRoamingBit() = runTest { + val latest by underTest.isRoaming.collectLastValue() - assertThat(latest).isFalse() + connectionRepo.cdmaRoaming.setValue(false) + connectionRepo.isGsm.setValue(false) + connectionRepo.isRoaming.setValue(true) - connectionRepository.cdmaRoaming.value = true - connectionRepository.isGsm.value = false - connectionRepository.isRoaming.value = false + assertThat(latest).isFalse() - assertThat(latest).isTrue() + connectionRepo.cdmaRoaming.setValue(true) + connectionRepo.isGsm.setValue(false) + connectionRepo.isRoaming.setValue(false) - job.cancel() - } + assertThat(latest).isTrue() + } @Test - fun roaming_falseWhileCarrierNetworkChangeActive() = - testScope.runTest { - var latest: Boolean? = null - val job = underTest.isRoaming.onEach { latest = it }.launchIn(this) + fun roaming_falseWhileCarrierNetworkChangeActive() = runTest { + val latest by underTest.isRoaming.collectLastValue() - connectionRepository.cdmaRoaming.value = true - connectionRepository.isGsm.value = false - connectionRepository.isRoaming.value = true - connectionRepository.carrierNetworkChangeActive.value = true + connectionRepo.cdmaRoaming.setValue(true) + connectionRepo.isGsm.setValue(false) + connectionRepo.isRoaming.setValue(true) + connectionRepo.carrierNetworkChangeActive.setValue(true) - assertThat(latest).isFalse() + assertThat(latest).isFalse() - connectionRepository.cdmaRoaming.value = true - connectionRepository.isGsm.value = true + connectionRepo.cdmaRoaming.setValue(true) + connectionRepo.isGsm.setValue(true) - assertThat(latest).isFalse() - - job.cancel() - } + assertThat(latest).isFalse() + } @Test - fun networkName_usesOperatorAlphaShortWhenNonNullAndRepoIsDefault() = - testScope.runTest { - var latest: NetworkNameModel? = null - val job = underTest.networkName.onEach { latest = it }.launchIn(this) - - val testOperatorName = "operatorAlphaShort" + fun networkName_usesOperatorAlphaShortWhenNonNullAndRepoIsDefault() = runTest { + val latest by underTest.networkName.collectLastValue() - // Default network name, operator name is non-null, uses the operator name - connectionRepository.networkName.value = DEFAULT_NAME_MODEL - connectionRepository.operatorAlphaShort.value = testOperatorName + val testOperatorName = "operatorAlphaShort" - assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived(testOperatorName)) + // Default network name, operator name is non-null, uses the operator name + connectionRepo.networkName.setValue(DEFAULT_NAME_MODEL) + connectionRepo.operatorAlphaShort.setValue(testOperatorName) - // Default network name, operator name is null, uses the default - connectionRepository.operatorAlphaShort.value = null + assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived(testOperatorName)) - assertThat(latest).isEqualTo(DEFAULT_NAME_MODEL) + // Default network name, operator name is null, uses the default + connectionRepo.operatorAlphaShort.setValue(null) - // Derived network name, operator name non-null, uses the derived name - connectionRepository.networkName.value = DERIVED_NAME_MODEL - connectionRepository.operatorAlphaShort.value = testOperatorName + assertThat(latest).isEqualTo(DEFAULT_NAME_MODEL) - assertThat(latest).isEqualTo(DERIVED_NAME_MODEL) + // Derived network name, operator name non-null, uses the derived name + connectionRepo.networkName.setValue(DERIVED_NAME_MODEL) + connectionRepo.operatorAlphaShort.setValue(testOperatorName) - job.cancel() - } + assertThat(latest).isEqualTo(DERIVED_NAME_MODEL) + } @Test - fun networkNameForSubId_usesOperatorAlphaShortWhenNonNullAndRepoIsDefault() = - testScope.runTest { - var latest: String? = null - val job = underTest.carrierName.onEach { latest = it }.launchIn(this) - - val testOperatorName = "operatorAlphaShort" + fun networkNameForSubId_usesOperatorAlphaShortWhenNonNullAndRepoIsDefault() = runTest { + val latest by underTest.carrierName.collectLastValue() - // Default network name, operator name is non-null, uses the operator name - connectionRepository.carrierName.value = DEFAULT_NAME_MODEL - connectionRepository.operatorAlphaShort.value = testOperatorName + val testOperatorName = "operatorAlphaShort" - assertThat(latest).isEqualTo(testOperatorName) + // Default network name, operator name is non-null, uses the operator name + connectionRepo.carrierName.setValue(DEFAULT_NAME_MODEL) + connectionRepo.operatorAlphaShort.setValue(testOperatorName) - // Default network name, operator name is null, uses the default - connectionRepository.operatorAlphaShort.value = null + assertThat(latest).isEqualTo(testOperatorName) - assertThat(latest).isEqualTo(DEFAULT_NAME) + // Default network name, operator name is null, uses the default + connectionRepo.operatorAlphaShort.setValue(null) - // Derived network name, operator name non-null, uses the derived name - connectionRepository.carrierName.value = - NetworkNameModel.SubscriptionDerived(DERIVED_NAME) - connectionRepository.operatorAlphaShort.value = testOperatorName + assertThat(latest).isEqualTo(DEFAULT_NAME) - assertThat(latest).isEqualTo(DERIVED_NAME) + // Derived network name, operator name non-null, uses the derived name + connectionRepo.carrierName.setValue(NetworkNameModel.SubscriptionDerived(DERIVED_NAME)) + connectionRepo.operatorAlphaShort.setValue(testOperatorName) - job.cancel() - } + assertThat(latest).isEqualTo(DERIVED_NAME) + } @Test - fun isSingleCarrier_matchesParent() = - testScope.runTest { - var latest: Boolean? = null - val job = underTest.isSingleCarrier.onEach { latest = it }.launchIn(this) + fun isSingleCarrier_matchesParent() = runTest { + val latest by underTest.isSingleCarrier.collectLastValue() - mobileIconsInteractor.isSingleCarrier.value = true - assertThat(latest).isTrue() + // mobileIconsInteractor.isSingleCarrier.setValue(true) + isSingleCarrier.setValue(true) + assertThat(latest).isTrue() - mobileIconsInteractor.isSingleCarrier.value = false - assertThat(latest).isFalse() - - job.cancel() - } + // mobileIconsInteractor.isSingleCarrier.setValue(false) + isSingleCarrier.setValue(false) + assertThat(latest).isFalse() + } @Test - fun isForceHidden_matchesParent() = - testScope.runTest { - var latest: Boolean? = null - val job = underTest.isForceHidden.onEach { latest = it }.launchIn(this) - - mobileIconsInteractor.isForceHidden.value = true - assertThat(latest).isTrue() + fun isForceHidden_matchesParent() = runTest { + val latest by underTest.isForceHidden.collectLastValue() - mobileIconsInteractor.isForceHidden.value = false - assertThat(latest).isFalse() + // mobileIconsInteractor.isForceHidden.setValue(true) + isForceHidden.setValue(true) + assertThat(latest).isTrue() - job.cancel() - } + // mobileIconsInteractor.isForceHidden.setValue(false) + isForceHidden.setValue(false) + assertThat(latest).isFalse() + } @Test - fun isAllowedDuringAirplaneMode_matchesRepo() = - testScope.runTest { - val latest by collectLastValue(underTest.isAllowedDuringAirplaneMode) + fun isAllowedDuringAirplaneMode_matchesRepo() = runTest { + val latest by underTest.isAllowedDuringAirplaneMode.collectLastValue() - connectionRepository.isAllowedDuringAirplaneMode.value = true - assertThat(latest).isTrue() + connectionRepo.isAllowedDuringAirplaneMode.setValue(true) + assertThat(latest).isTrue() - connectionRepository.isAllowedDuringAirplaneMode.value = false - assertThat(latest).isFalse() - } + connectionRepo.isAllowedDuringAirplaneMode.setValue(false) + assertThat(latest).isFalse() + } @Test - fun cellBasedIconId_correctLevel_notCutout() = - testScope.runTest { - connectionRepository.isNonTerrestrial.value = false - connectionRepository.isInService.value = true - connectionRepository.primaryLevel.value = 1 - connectionRepository.setDataEnabled(false) - connectionRepository.isNonTerrestrial.value = false + fun cellBasedIconId_correctLevel_notCutout() = runTest { + connectionRepo.isNonTerrestrial.setValue(false) + connectionRepo.isInService.setValue(true) + connectionRepo.primaryLevel.setValue(1) + connectionRepo.dataEnabled.setValue(true) + connectionRepo.isNonTerrestrial.setValue(false) - var latest: SignalIconModel.Cellular? = null - val job = - underTest.signalLevelIcon - .onEach { latest = it as? SignalIconModel.Cellular } - .launchIn(this) + val latest by + underTest.signalLevelIcon.map { it as? SignalIconModel.Cellular }.collectLastValue() - assertThat(latest?.level).isEqualTo(1) - assertThat(latest?.showExclamationMark).isFalse() + assertThat(latest?.level).isEqualTo(1) - job.cancel() - } + // TODO: need to provision MobileIconsInteractorKairos#isDefaultConnectionFailed + + // defaultSubscriptionHasDataEnabled? + assertThat(latest?.showExclamationMark).isEqualTo(false) + } @Test - fun icon_usesLevelFromInteractor() = - testScope.runTest { - connectionRepository.isNonTerrestrial.value = false - connectionRepository.isInService.value = true + fun icon_usesLevelFromInteractor() = runTest { + connectionRepo.isNonTerrestrial.setValue(false) + connectionRepo.isInService.setValue(true) - var latest: SignalIconModel? = null - val job = underTest.signalLevelIcon.onEach { latest = it }.launchIn(this) + val latest by underTest.signalLevelIcon.collectLastValue() - connectionRepository.primaryLevel.value = 3 - assertThat(latest!!.level).isEqualTo(3) + connectionRepo.primaryLevel.setValue(3) + assertThat(latest!!.level).isEqualTo(3) - connectionRepository.primaryLevel.value = 1 - assertThat(latest!!.level).isEqualTo(1) - - job.cancel() - } + connectionRepo.primaryLevel.setValue(1) + assertThat(latest!!.level).isEqualTo(1) + } @Test - fun cellBasedIcon_usesNumberOfLevelsFromInteractor() = - testScope.runTest { - connectionRepository.isNonTerrestrial.value = false - - var latest: SignalIconModel.Cellular? = null - val job = - underTest.signalLevelIcon - .onEach { latest = it as? SignalIconModel.Cellular } - .launchIn(this) + fun cellBasedIcon_usesNumberOfLevelsFromInteractor() = runTest { + connectionRepo.isNonTerrestrial.setValue(false) - connectionRepository.numberOfLevels.value = 5 - assertThat(latest!!.numberOfLevels).isEqualTo(5) + val latest by + underTest.signalLevelIcon.map { it as? SignalIconModel.Cellular }.collectLastValue() - connectionRepository.numberOfLevels.value = 2 - assertThat(latest!!.numberOfLevels).isEqualTo(2) + connectionRepo.numberOfLevels.setValue(5) + assertThat(latest!!.numberOfLevels).isEqualTo(5) - job.cancel() - } + connectionRepo.numberOfLevels.setValue(2) + assertThat(latest!!.numberOfLevels).isEqualTo(2) + } @Test - fun cellBasedIcon_defaultDataDisabled_showExclamationTrue() = - testScope.runTest { - connectionRepository.isNonTerrestrial.value = false - mobileIconsInteractor.activeDataConnectionHasDataEnabled.value = false - - var latest: SignalIconModel.Cellular? = null - val job = - underTest.signalLevelIcon - .onEach { latest = it as? SignalIconModel.Cellular } - .launchIn(this) + fun cellBasedIcon_defaultDataDisabled_showExclamationTrue() = runTest { + connectionRepo.isNonTerrestrial.setValue(false) + connectionRepo.dataEnabled.setValue(false) + defaultSubscriptionHasDataEnabled.setValue(false) - assertThat(latest!!.showExclamationMark).isTrue() + val latest by underTest.signalLevelIcon.collectLastValue() - job.cancel() - } + assertThat((latest!! as SignalIconModel.Cellular).showExclamationMark).isTrue() + } @Test - fun cellBasedIcon_defaultConnectionFailed_showExclamationTrue() = - testScope.runTest { - connectionRepository.isNonTerrestrial.value = false - mobileIconsInteractor.isDefaultConnectionFailed.value = true + fun cellBasedIcon_defaultConnectionFailed_showExclamationTrue() = runTest { + connectionRepo.isNonTerrestrial.setValue(false) + // mobileIconsInteractor.isDefaultConnectionFailed.setValue(true) + isDefaultConnectionFailed.setValue(true) - var latest: SignalIconModel.Cellular? = null - val job = - underTest.signalLevelIcon - .onEach { latest = it as? SignalIconModel.Cellular } - .launchIn(this) + val latest by underTest.signalLevelIcon.collectLastValue() - assertThat(latest!!.showExclamationMark).isTrue() - - job.cancel() - } + assertThat((latest!! as SignalIconModel.Cellular).showExclamationMark).isTrue() + } @Test - fun cellBasedIcon_enabledAndNotFailed_showExclamationFalse() = - testScope.runTest { - connectionRepository.isNonTerrestrial.value = false - connectionRepository.isInService.value = true - mobileIconsInteractor.activeDataConnectionHasDataEnabled.value = true - mobileIconsInteractor.isDefaultConnectionFailed.value = false - - var latest: SignalIconModel.Cellular? = null - val job = - underTest.signalLevelIcon - .onEach { latest = it as? SignalIconModel.Cellular } - .launchIn(this) + fun cellBasedIcon_enabledAndNotFailed_showExclamationFalse() = runTest { + connectionRepo.isNonTerrestrial.setValue(false) + connectionRepo.isInService.setValue(true) + connectionRepo.dataEnabled.setValue(true) + // mobileIconsInteractor.isDefaultConnectionFailed.setValue(false) + isDefaultConnectionFailed.setValue(false) - assertThat(latest!!.showExclamationMark).isFalse() + val latest by underTest.signalLevelIcon.collectLastValue() - job.cancel() - } + assertThat((latest!! as SignalIconModel.Cellular).showExclamationMark).isFalse() + } @Test - fun cellBasedIcon_usesEmptyState_whenNotInService() = - testScope.runTest { - var latest: SignalIconModel.Cellular? = null - val job = - underTest.signalLevelIcon - .onEach { latest = it as? SignalIconModel.Cellular } - .launchIn(this) + fun cellBasedIcon_usesEmptyState_whenNotInService() = runTest { + val latest by + underTest.signalLevelIcon.map { it as SignalIconModel.Cellular }.collectLastValue() - connectionRepository.isNonTerrestrial.value = false - connectionRepository.isInService.value = false + connectionRepo.isNonTerrestrial.setValue(false) + connectionRepo.isInService.setValue(false) - assertThat(latest?.level).isEqualTo(0) - assertThat(latest?.showExclamationMark).isTrue() + assertThat(latest?.level).isEqualTo(0) + assertThat(latest?.showExclamationMark).isTrue() - // Changing the level doesn't overwrite the disabled state - connectionRepository.primaryLevel.value = 2 - assertThat(latest?.level).isEqualTo(0) - assertThat(latest?.showExclamationMark).isTrue() + // Changing the level doesn't overwrite the disabled state + connectionRepo.primaryLevel.setValue(2) + assertThat(latest?.level).isEqualTo(0) + assertThat(latest?.showExclamationMark).isTrue() - // Once back in service, the regular icon appears - connectionRepository.isInService.value = true - assertThat(latest?.level).isEqualTo(2) - assertThat(latest?.showExclamationMark).isFalse() - - job.cancel() - } + // Once back in service, the regular icon appears + connectionRepo.isInService.setValue(true) + assertThat(latest?.level).isEqualTo(2) + assertThat(latest?.showExclamationMark).isFalse() + } @Test - fun cellBasedIcon_usesCarrierNetworkState_whenInCarrierNetworkChangeMode() = - testScope.runTest { - var latest: SignalIconModel.Cellular? = null - val job = - underTest.signalLevelIcon - .onEach { latest = it as? SignalIconModel.Cellular? } - .launchIn(this) - - connectionRepository.isNonTerrestrial.value = false - connectionRepository.isInService.value = true - connectionRepository.carrierNetworkChangeActive.value = true - connectionRepository.primaryLevel.value = 1 - connectionRepository.cdmaLevel.value = 1 + fun cellBasedIcon_usesCarrierNetworkState_whenInCarrierNetworkChangeMode() = runTest { + val latest by + underTest.signalLevelIcon.map { it as SignalIconModel.Cellular }.collectLastValue() - assertThat(latest!!.level).isEqualTo(1) - assertThat(latest!!.carrierNetworkChange).isTrue() + connectionRepo.isNonTerrestrial.setValue(false) + connectionRepo.isInService.setValue(true) + connectionRepo.carrierNetworkChangeActive.setValue(true) + connectionRepo.primaryLevel.setValue(1) + connectionRepo.cdmaLevel.setValue(1) - // SignalIconModel respects the current level - connectionRepository.primaryLevel.value = 2 + assertThat(latest!!.level).isEqualTo(1) + assertThat(latest!!.carrierNetworkChange).isTrue() - assertThat(latest!!.level).isEqualTo(2) - assertThat(latest!!.carrierNetworkChange).isTrue() + // SignalIconModel respects the current level + connectionRepo.primaryLevel.setValue(2) - job.cancel() - } + assertThat(latest!!.level).isEqualTo(2) + assertThat(latest!!.carrierNetworkChange).isTrue() + } @Test - fun satBasedIcon_isUsedWhenNonTerrestrial() = - testScope.runTest { - val latest by collectLastValue(underTest.signalLevelIcon) + fun satBasedIcon_isUsedWhenNonTerrestrial() = runTest { + val latest by underTest.signalLevelIcon.collectLastValue() - // Start off using cellular - assertThat(latest).isInstanceOf(SignalIconModel.Cellular::class.java) + // Start off using cellular + assertThat(latest).isInstanceOf(SignalIconModel.Cellular::class.java) - connectionRepository.isNonTerrestrial.value = true + connectionRepo.isNonTerrestrial.setValue(true) - assertThat(latest).isInstanceOf(SignalIconModel.Satellite::class.java) - } + assertThat(latest).isInstanceOf(SignalIconModel.Satellite::class.java) + } @DisableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN) @Test // See b/346904529 for more context - fun satBasedIcon_doesNotInflateSignalStrength_flagOff() = - testScope.runTest { - val latest by collectLastValue(underTest.signalLevelIcon) + fun satBasedIcon_doesNotInflateSignalStrength_flagOff() = runTest { + val latest by underTest.signalLevelIcon.collectLastValue() - // GIVEN a satellite connection - connectionRepository.isNonTerrestrial.value = true - // GIVEN this carrier has set INFLATE_SIGNAL_STRENGTH - connectionRepository.inflateSignalStrength.value = true + // GIVEN a satellite connection + connectionRepo.isNonTerrestrial.setValue(true) + // GIVEN this carrier has set INFLATE_SIGNAL_STRENGTH + connectionRepo.inflateSignalStrength.setValue(true) - connectionRepository.primaryLevel.value = 4 - assertThat(latest!!.level).isEqualTo(4) + connectionRepo.primaryLevel.setValue(4) + assertThat(latest!!.level).isEqualTo(4) - connectionRepository.inflateSignalStrength.value = true - connectionRepository.primaryLevel.value = 4 + connectionRepo.inflateSignalStrength.setValue(true) + connectionRepo.primaryLevel.setValue(4) - // Icon level is unaffected - assertThat(latest!!.level).isEqualTo(4) - } + // Icon level is unaffected + assertThat(latest!!.level).isEqualTo(4) + } @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN) @Test // See b/346904529 for more context - fun satBasedIcon_doesNotInflateSignalStrength_flagOn() = - testScope.runTest { - val latest by collectLastValue(underTest.signalLevelIcon) + fun satBasedIcon_doesNotInflateSignalStrength_flagOn() = runTest { + val latest by underTest.signalLevelIcon.collectLastValue() - // GIVEN a satellite connection - connectionRepository.isNonTerrestrial.value = true - // GIVEN this carrier has set INFLATE_SIGNAL_STRENGTH - connectionRepository.inflateSignalStrength.value = true + // GIVEN a satellite connection + connectionRepo.isNonTerrestrial.setValue(true) + // GIVEN this carrier has set INFLATE_SIGNAL_STRENGTH + connectionRepo.inflateSignalStrength.setValue(true) - connectionRepository.satelliteLevel.value = 4 - assertThat(latest!!.level).isEqualTo(4) + connectionRepo.satelliteLevel.setValue(4) + assertThat(latest!!.level).isEqualTo(4) - connectionRepository.inflateSignalStrength.value = true - connectionRepository.primaryLevel.value = 4 + connectionRepo.inflateSignalStrength.setValue(true) + connectionRepo.primaryLevel.setValue(4) - // Icon level is unaffected - assertThat(latest!!.level).isEqualTo(4) - } + // Icon level is unaffected + assertThat(latest!!.level).isEqualTo(4) + } @DisableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN) @Test - fun satBasedIcon_usesPrimaryLevel_flagOff() = - testScope.runTest { - val latest by collectLastValue(underTest.signalLevelIcon) + fun satBasedIcon_usesPrimaryLevel_flagOff() = runTest { + val latest by underTest.signalLevelIcon.collectLastValue() - // GIVEN a satellite connection - connectionRepository.isNonTerrestrial.value = true + // GIVEN a satellite connection + connectionRepo.isNonTerrestrial.setValue(true) - // GIVEN primary level is set - connectionRepository.primaryLevel.value = 4 - connectionRepository.satelliteLevel.value = 0 + // GIVEN primary level is set + connectionRepo.primaryLevel.setValue(4) + connectionRepo.satelliteLevel.setValue(0) - // THEN icon uses the primary level because the flag is off - assertThat(latest!!.level).isEqualTo(4) - } + // THEN icon uses the primary level because the flag is off + assertThat(latest!!.level).isEqualTo(4) + } @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN) @Test - fun satBasedIcon_usesSatelliteLevel_flagOn() = - testScope.runTest { - val latest by collectLastValue(underTest.signalLevelIcon) + fun satBasedIcon_usesSatelliteLevel_flagOn() = runTest { + val latest by underTest.signalLevelIcon.collectLastValue() - // GIVEN a satellite connection - connectionRepository.isNonTerrestrial.value = true + // GIVEN a satellite connection + connectionRepo.isNonTerrestrial.setValue(true) - // GIVEN satellite level is set - connectionRepository.satelliteLevel.value = 4 - connectionRepository.primaryLevel.value = 0 + // GIVEN satellite level is set + connectionRepo.satelliteLevel.setValue(4) + connectionRepo.primaryLevel.setValue(0) - // THEN icon uses the satellite level because the flag is on - assertThat(latest!!.level).isEqualTo(4) - } + // THEN icon uses the satellite level because the flag is on + assertThat(latest!!.level).isEqualTo(4) + } /** * Context (b/377518113), this test will not be needed after FLAG_CARRIER_ROAMING_NB_IOT_NTN is @@ -816,43 +728,23 @@ class MobileIconInteractorKairosTest : SysuiTestCase() { */ @DisableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN) @Test - fun satBasedIcon_reportsLevelZeroWhenOutOfService() = - testScope.runTest { - val latest by collectLastValue(underTest.signalLevelIcon) - - // GIVEN a satellite connection - connectionRepository.isNonTerrestrial.value = true - // GIVEN this carrier has set INFLATE_SIGNAL_STRENGTH - connectionRepository.inflateSignalStrength.value = true + fun satBasedIcon_reportsLevelZeroWhenOutOfService() = runTest { + val latest by underTest.signalLevelIcon.collectLastValue() - connectionRepository.primaryLevel.value = 4 - assertThat(latest!!.level).isEqualTo(4) + // GIVEN a satellite connection + connectionRepo.isNonTerrestrial.setValue(true) + // GIVEN this carrier has set INFLATE_SIGNAL_STRENGTH + connectionRepo.inflateSignalStrength.setValue(true) - connectionRepository.isInService.value = false - connectionRepository.primaryLevel.value = 4 + connectionRepo.primaryLevel.setValue(4) + assertThat(latest!!.level).isEqualTo(4) - // THEN level reports 0, by policy - assertThat(latest!!.level).isEqualTo(0) - } + connectionRepo.isInService.setValue(false) + connectionRepo.primaryLevel.setValue(4) - private fun createInteractor( - overrides: MobileIconCarrierIdOverrides = MobileIconCarrierIdOverridesImpl() - ) = - MobileIconInteractorKairosImpl( - testScope.backgroundScope, - mobileIconsInteractor.activeDataConnectionHasDataEnabled, - mobileIconsInteractor.alwaysShowDataRatIcon, - mobileIconsInteractor.alwaysUseCdmaLevel, - mobileIconsInteractor.isSingleCarrier, - mobileIconsInteractor.mobileIsDefault, - mobileIconsInteractor.defaultMobileIconMapping, - mobileIconsInteractor.defaultMobileIconGroup, - mobileIconsInteractor.isDefaultConnectionFailed, - mobileIconsInteractor.isForceHidden, - connectionRepository, - context, - overrides, - ) + // THEN level reports 0, by policy + assertThat(latest!!.level).isEqualTo(0) + } companion object { private const val GSM_LEVEL = 1 diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKairosTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKairosTest.kt index a9360d139a3d..4f6439d2d0b2 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKairosTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKairosTest.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,186 +28,164 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.flags.Flags import com.android.systemui.flags.fake import com.android.systemui.flags.featureFlagsClassic +import com.android.systemui.kairos.ExperimentalKairosApi +import com.android.systemui.kairos.KairosTestScope +import com.android.systemui.kairos.runKairosTest import com.android.systemui.kosmos.Kosmos -import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.kosmos.collectLastValue import com.android.systemui.kosmos.runCurrent import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.testScope +import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.statusbar.core.NewStatusBarIcons import com.android.systemui.statusbar.core.StatusBarRootModernization import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel -import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.fake -import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository -import com.android.systemui.statusbar.pipeline.mobile.data.repository.mobileConnectionsRepository -import com.android.systemui.statusbar.pipeline.mobile.data.repository.mobileConnectionsRepositoryLogbufferName +import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepositoryKairos +import com.android.systemui.statusbar.pipeline.mobile.data.repository.mobileConnectionsRepositoryKairos import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot import com.android.systemui.statusbar.pipeline.shared.data.repository.connectivityRepository import com.android.systemui.statusbar.pipeline.shared.data.repository.fake -import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository import com.android.systemui.testKosmos -import com.android.systemui.util.CarrierConfigTracker +import com.android.systemui.util.carrierConfigTracker import com.google.common.truth.Truth.assertThat import java.util.UUID import kotlinx.coroutines.test.advanceTimeBy import org.junit.Test import org.junit.runner.RunWith -import org.mockito.kotlin.mock import org.mockito.kotlin.whenever +@OptIn(ExperimentalKairosApi::class) @SmallTest @RunWith(AndroidJUnit4::class) class MobileIconsInteractorKairosTest : SysuiTestCase() { - private val kosmos by lazy { + + private val kosmos = testKosmos().apply { - mobileConnectionsRepositoryLogbufferName = "MobileIconsInteractorTest" - mobileConnectionsRepository.fake.run { - setMobileConnectionRepositoryMap( - mapOf( - SUB_1_ID to FakeMobileConnectionRepository(SUB_1_ID, mock()), - SUB_2_ID to FakeMobileConnectionRepository(SUB_2_ID, mock()), - SUB_3_ID to FakeMobileConnectionRepository(SUB_3_ID, mock()), - SUB_4_ID to FakeMobileConnectionRepository(SUB_4_ID, mock()), - ) - ) - setActiveMobileDataSubscriptionId(SUB_1_ID) - } + useUnconfinedTestDispatcher() + mobileConnectionsRepositoryKairos = + fakeMobileConnectionsRepositoryKairos.apply { + setActiveMobileDataSubscriptionId(SUB_1_ID) + subscriptions.setValue(listOf(SUB_1, SUB_2, SUB_3_OPP, SUB_4_OPP)) + } featureFlagsClassic.fake.set(Flags.FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS, true) } - } - // shortcut rename - private val Kosmos.connectionsRepository by Fixture { mobileConnectionsRepository.fake } - - private val Kosmos.carrierConfigTracker by Fixture { mock<CarrierConfigTracker>() } - - private val Kosmos.underTest by Fixture { - MobileIconsInteractorKairosImpl( - mobileConnectionsRepository, - carrierConfigTracker, - tableLogger = mock(), - connectivityRepository, - FakeUserSetupRepository(), - testScope.backgroundScope, - context, - featureFlagsClassic, - ) - } + private val Kosmos.underTest + get() = mobileIconsInteractorKairos + + private fun runTest(block: suspend KairosTestScope.() -> Unit) = + kosmos.run { runKairosTest { block() } } @Test - fun filteredSubscriptions_default() = - kosmos.runTest { - val latest by collectLastValue(underTest.filteredSubscriptions) + fun filteredSubscriptions_default() = runTest { + mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(emptyList()) + val latest by underTest.filteredSubscriptions.collectLastValue() - assertThat(latest).isEqualTo(listOf<SubscriptionModel>()) - } + assertThat(latest).isEqualTo(emptyList<SubscriptionModel>()) + } // Based on the logic from the old pipeline, we'll never filter subs when there are more than 2 @Test - fun filteredSubscriptions_moreThanTwo_doesNotFilter() = - kosmos.runTest { - connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_3_OPP, SUB_4_OPP)) - connectionsRepository.setActiveMobileDataSubscriptionId(SUB_4_ID) + fun filteredSubscriptions_moreThanTwo_doesNotFilter() = runTest { + mobileConnectionsRepositoryKairos.fake.subscriptions.setValue( + listOf(SUB_1, SUB_3_OPP, SUB_4_OPP) + ) + mobileConnectionsRepositoryKairos.fake.setActiveMobileDataSubscriptionId(SUB_4_ID) - val latest by collectLastValue(underTest.filteredSubscriptions) + val latest by underTest.filteredSubscriptions.collectLastValue() - assertThat(latest).isEqualTo(listOf(SUB_1, SUB_3_OPP, SUB_4_OPP)) - } + assertThat(latest).isEqualTo(listOf(SUB_1, SUB_3_OPP, SUB_4_OPP)) + } @Test - fun filteredSubscriptions_nonOpportunistic_updatesWithMultipleSubs() = - kosmos.runTest { - connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2)) + fun filteredSubscriptions_nonOpportunistic_updatesWithMultipleSubs() = runTest { + mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(listOf(SUB_1, SUB_2)) - val latest by collectLastValue(underTest.filteredSubscriptions) + val latest by underTest.filteredSubscriptions.collectLastValue() - assertThat(latest).isEqualTo(listOf(SUB_1, SUB_2)) - } + assertThat(latest).isEqualTo(listOf(SUB_1, SUB_2)) + } @Test - fun filteredSubscriptions_opportunistic_differentGroups_doesNotFilter() = - kosmos.runTest { - connectionsRepository.setSubscriptions(listOf(SUB_3_OPP, SUB_4_OPP)) - connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID) + fun filteredSubscriptions_opportunistic_differentGroups_doesNotFilter() = runTest { + mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(listOf(SUB_3_OPP, SUB_4_OPP)) + mobileConnectionsRepositoryKairos.fake.setActiveMobileDataSubscriptionId(SUB_3_ID) - val latest by collectLastValue(underTest.filteredSubscriptions) + val latest by underTest.filteredSubscriptions.collectLastValue() - assertThat(latest).isEqualTo(listOf(SUB_3_OPP, SUB_4_OPP)) - } + assertThat(latest).isEqualTo(listOf(SUB_3_OPP, SUB_4_OPP)) + } @Test - fun filteredSubscriptions_opportunistic_nonGrouped_doesNotFilter() = - kosmos.runTest { - val (sub1, sub2) = - createSubscriptionPair( - subscriptionIds = Pair(SUB_1_ID, SUB_2_ID), - opportunistic = Pair(true, true), - grouped = false, - ) - connectionsRepository.setSubscriptions(listOf(sub1, sub2)) - connectionsRepository.setActiveMobileDataSubscriptionId(SUB_1_ID) + fun filteredSubscriptions_opportunistic_nonGrouped_doesNotFilter() = runTest { + val (sub1, sub2) = + createSubscriptionPair( + subscriptionIds = Pair(SUB_1_ID, SUB_2_ID), + opportunistic = Pair(true, true), + grouped = false, + ) + mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(listOf(sub1, sub2)) + mobileConnectionsRepositoryKairos.fake.setActiveMobileDataSubscriptionId(SUB_1_ID) - val latest by collectLastValue(underTest.filteredSubscriptions) + val latest by underTest.filteredSubscriptions.collectLastValue() - assertThat(latest).isEqualTo(listOf(sub1, sub2)) - } + assertThat(latest).isEqualTo(listOf(sub1, sub2)) + } @Test - fun filteredSubscriptions_opportunistic_grouped_configFalse_showsActive_3() = - kosmos.runTest { - val (sub3, sub4) = - createSubscriptionPair( - subscriptionIds = Pair(SUB_3_ID, SUB_4_ID), - opportunistic = Pair(true, true), - grouped = true, - ) - connectionsRepository.setSubscriptions(listOf(sub3, sub4)) - connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID) - whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) - .thenReturn(false) + fun filteredSubscriptions_opportunistic_grouped_configFalse_showsActive_3() = runTest { + val (sub3, sub4) = + createSubscriptionPair( + subscriptionIds = Pair(SUB_3_ID, SUB_4_ID), + opportunistic = Pair(true, true), + grouped = true, + ) + mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(listOf(sub3, sub4)) + mobileConnectionsRepositoryKairos.fake.setActiveMobileDataSubscriptionId(SUB_3_ID) + whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) + .thenReturn(false) - val latest by collectLastValue(underTest.filteredSubscriptions) + val latest by underTest.filteredSubscriptions.collectLastValue() - // Filtered subscriptions should show the active one when the config is false - assertThat(latest).isEqualTo(listOf(sub3)) - } + // Filtered subscriptions should show the active one when the config is false + assertThat(latest).isEqualTo(listOf(sub3)) + } @Test - fun filteredSubscriptions_opportunistic_grouped_configFalse_showsActive_4() = - kosmos.runTest { - val (sub3, sub4) = - createSubscriptionPair( - subscriptionIds = Pair(SUB_3_ID, SUB_4_ID), - opportunistic = Pair(true, true), - grouped = true, - ) - connectionsRepository.setSubscriptions(listOf(sub3, sub4)) - connectionsRepository.setActiveMobileDataSubscriptionId(SUB_4_ID) - whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) - .thenReturn(false) + fun filteredSubscriptions_opportunistic_grouped_configFalse_showsActive_4() = runTest { + val (sub3, sub4) = + createSubscriptionPair( + subscriptionIds = Pair(SUB_3_ID, SUB_4_ID), + opportunistic = Pair(true, true), + grouped = true, + ) + mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(listOf(sub3, sub4)) + mobileConnectionsRepositoryKairos.fake.setActiveMobileDataSubscriptionId(SUB_4_ID) + whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) + .thenReturn(false) - val latest by collectLastValue(underTest.filteredSubscriptions) + val latest by underTest.filteredSubscriptions.collectLastValue() - // Filtered subscriptions should show the active one when the config is false - assertThat(latest).isEqualTo(listOf(sub4)) - } + // Filtered subscriptions should show the active one when the config is false + assertThat(latest).isEqualTo(listOf(sub4)) + } @Test fun filteredSubscriptions_oneOpportunistic_grouped_configTrue_showsPrimary_active_1() = - kosmos.runTest { + runTest { val (sub1, sub3) = createSubscriptionPair( subscriptionIds = Pair(SUB_1_ID, SUB_3_ID), opportunistic = Pair(false, true), grouped = true, ) - connectionsRepository.setSubscriptions(listOf(sub1, sub3)) - connectionsRepository.setActiveMobileDataSubscriptionId(SUB_1_ID) + mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(listOf(sub1, sub3)) + mobileConnectionsRepositoryKairos.fake.setActiveMobileDataSubscriptionId(SUB_1_ID) whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) .thenReturn(true) - val latest by collectLastValue(underTest.filteredSubscriptions) + val latest by underTest.filteredSubscriptions.collectLastValue() // Filtered subscriptions should show the primary (non-opportunistic) if the config is // true @@ -216,19 +194,19 @@ class MobileIconsInteractorKairosTest : SysuiTestCase() { @Test fun filteredSubscriptions_oneOpportunistic_grouped_configTrue_showsPrimary_nonActive_1() = - kosmos.runTest { + runTest { val (sub1, sub3) = createSubscriptionPair( subscriptionIds = Pair(SUB_1_ID, SUB_3_ID), opportunistic = Pair(false, true), grouped = true, ) - connectionsRepository.setSubscriptions(listOf(sub1, sub3)) - connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID) + mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(listOf(sub1, sub3)) + mobileConnectionsRepositoryKairos.fake.setActiveMobileDataSubscriptionId(SUB_3_ID) whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) .thenReturn(true) - val latest by collectLastValue(underTest.filteredSubscriptions) + val latest by underTest.filteredSubscriptions.collectLastValue() // Filtered subscriptions should show the primary (non-opportunistic) if the config is // true @@ -236,135 +214,130 @@ class MobileIconsInteractorKairosTest : SysuiTestCase() { } @Test - fun filteredSubscriptions_vcnSubId_agreesWithActiveSubId_usesActiveAkaVcnSub() = - kosmos.runTest { - val (sub1, sub3) = - createSubscriptionPair( - subscriptionIds = Pair(SUB_1_ID, SUB_3_ID), - opportunistic = Pair(true, true), - grouped = true, - ) - connectionsRepository.setSubscriptions(listOf(sub1, sub3)) - connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID) - kosmos.connectivityRepository.fake.vcnSubId.value = SUB_3_ID - whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) - .thenReturn(false) + fun filteredSubscriptions_vcnSubId_agreesWithActiveSubId_usesActiveAkaVcnSub() = runTest { + val (sub1, sub3) = + createSubscriptionPair( + subscriptionIds = Pair(SUB_1_ID, SUB_3_ID), + opportunistic = Pair(true, true), + grouped = true, + ) + mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(listOf(sub1, sub3)) + mobileConnectionsRepositoryKairos.fake.setActiveMobileDataSubscriptionId(SUB_3_ID) + kosmos.connectivityRepository.fake.vcnSubId.value = SUB_3_ID + whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) + .thenReturn(false) - val latest by collectLastValue(underTest.filteredSubscriptions) + val latest by underTest.filteredSubscriptions.collectLastValue() - assertThat(latest).isEqualTo(listOf(sub3)) - } + assertThat(latest).isEqualTo(listOf(sub3)) + } @Test - fun filteredSubscriptions_vcnSubId_disagreesWithActiveSubId_usesVcnSub() = - kosmos.runTest { - val (sub1, sub3) = - createSubscriptionPair( - subscriptionIds = Pair(SUB_1_ID, SUB_3_ID), - opportunistic = Pair(true, true), - grouped = true, - ) - connectionsRepository.setSubscriptions(listOf(sub1, sub3)) - connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID) - kosmos.connectivityRepository.fake.vcnSubId.value = SUB_1_ID - whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) - .thenReturn(false) + fun filteredSubscriptions_vcnSubId_disagreesWithActiveSubId_usesVcnSub() = runTest { + val (sub1, sub3) = + createSubscriptionPair( + subscriptionIds = Pair(SUB_1_ID, SUB_3_ID), + opportunistic = Pair(true, true), + grouped = true, + ) + mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(listOf(sub1, sub3)) + mobileConnectionsRepositoryKairos.fake.setActiveMobileDataSubscriptionId(SUB_3_ID) + kosmos.connectivityRepository.fake.vcnSubId.value = SUB_1_ID + whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) + .thenReturn(false) - val latest by collectLastValue(underTest.filteredSubscriptions) + val latest by underTest.filteredSubscriptions.collectLastValue() - assertThat(latest).isEqualTo(listOf(sub1)) - } + assertThat(latest).isEqualTo(listOf(sub1)) + } @Test - fun filteredSubscriptions_doesNotFilterProvisioningWhenFlagIsFalse() = - kosmos.runTest { - // GIVEN the flag is false - featureFlagsClassic.fake.set(Flags.FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS, false) + fun filteredSubscriptions_doesNotFilterProvisioningWhenFlagIsFalse() = runTest { + // GIVEN the flag is false + featureFlagsClassic.fake.set(Flags.FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS, false) - // GIVEN 1 sub that is in PROFILE_CLASS_PROVISIONING - val sub1 = - SubscriptionModel( - subscriptionId = SUB_1_ID, - isOpportunistic = false, - carrierName = "Carrier 1", - profileClass = PROFILE_CLASS_PROVISIONING, - ) + // GIVEN 1 sub that is in PROFILE_CLASS_PROVISIONING + val sub1 = + SubscriptionModel( + subscriptionId = SUB_1_ID, + isOpportunistic = false, + carrierName = "Carrier 1", + profileClass = PROFILE_CLASS_PROVISIONING, + ) - connectionsRepository.setSubscriptions(listOf(sub1)) + mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(listOf(sub1)) - // WHEN filtering is applied - val latest by collectLastValue(underTest.filteredSubscriptions) + // WHEN filtering is applied + val latest by underTest.filteredSubscriptions.collectLastValue() - // THEN the provisioning sub is still present (unfiltered) - assertThat(latest).isEqualTo(listOf(sub1)) - } + // THEN the provisioning sub is still present (unfiltered) + assertThat(latest).isEqualTo(listOf(sub1)) + } @Test - fun filteredSubscriptions_filtersOutProvisioningSubs() = - kosmos.runTest { - val sub1 = - SubscriptionModel( - subscriptionId = SUB_1_ID, - isOpportunistic = false, - carrierName = "Carrier 1", - profileClass = PROFILE_CLASS_UNSET, - ) - val sub2 = - SubscriptionModel( - subscriptionId = SUB_2_ID, - isOpportunistic = false, - carrierName = "Carrier 2", - profileClass = PROFILE_CLASS_PROVISIONING, - ) + fun filteredSubscriptions_filtersOutProvisioningSubs() = runTest { + val sub1 = + SubscriptionModel( + subscriptionId = SUB_1_ID, + isOpportunistic = false, + carrierName = "Carrier 1", + profileClass = PROFILE_CLASS_UNSET, + ) + val sub2 = + SubscriptionModel( + subscriptionId = SUB_2_ID, + isOpportunistic = false, + carrierName = "Carrier 2", + profileClass = PROFILE_CLASS_PROVISIONING, + ) - connectionsRepository.setSubscriptions(listOf(sub1, sub2)) + mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(listOf(sub1, sub2)) - val latest by collectLastValue(underTest.filteredSubscriptions) + val latest by underTest.filteredSubscriptions.collectLastValue() - assertThat(latest).isEqualTo(listOf(sub1)) - } + assertThat(latest).isEqualTo(listOf(sub1)) + } /** Note: I'm not sure if this will ever be the case, but we can test it at least */ @Test - fun filteredSubscriptions_filtersOutProvisioningSubsBeforeOpportunistic() = - kosmos.runTest { - // This is a contrived test case, where the active subId is the one that would - // also be filtered by opportunistic filtering. - - // GIVEN grouped, opportunistic subscriptions - val groupUuid = ParcelUuid(UUID.randomUUID()) - val sub1 = - SubscriptionModel( - subscriptionId = 1, - isOpportunistic = true, - groupUuid = groupUuid, - carrierName = "Carrier 1", - profileClass = PROFILE_CLASS_PROVISIONING, - ) + fun filteredSubscriptions_filtersOutProvisioningSubsBeforeOpportunistic() = runTest { + // This is a contrived test case, where the active subId is the one that would + // also be filtered by opportunistic filtering. - val sub2 = - SubscriptionModel( - subscriptionId = 2, - isOpportunistic = true, - groupUuid = groupUuid, - carrierName = "Carrier 2", - profileClass = PROFILE_CLASS_UNSET, - ) + // GIVEN grouped, opportunistic subscriptions + val groupUuid = ParcelUuid(UUID.randomUUID()) + val sub1 = + SubscriptionModel( + subscriptionId = 1, + isOpportunistic = true, + groupUuid = groupUuid, + carrierName = "Carrier 1", + profileClass = PROFILE_CLASS_PROVISIONING, + ) - // GIVEN active subId is 1 - connectionsRepository.setSubscriptions(listOf(sub1, sub2)) - connectionsRepository.setActiveMobileDataSubscriptionId(1) + val sub2 = + SubscriptionModel( + subscriptionId = 2, + isOpportunistic = true, + groupUuid = groupUuid, + carrierName = "Carrier 2", + profileClass = PROFILE_CLASS_UNSET, + ) - // THEN filtering of provisioning subs takes place first, and we result in sub2 + // GIVEN active subId is 1 + mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(listOf(sub1, sub2)) + mobileConnectionsRepositoryKairos.fake.setActiveMobileDataSubscriptionId(1) - val latest by collectLastValue(underTest.filteredSubscriptions) + // THEN filtering of provisioning subs takes place first, and we result in sub2 - assertThat(latest).isEqualTo(listOf(sub2)) - } + val latest by underTest.filteredSubscriptions.collectLastValue() + + assertThat(latest).isEqualTo(listOf(sub2)) + } @Test fun filteredSubscriptions_groupedPairAndNonProvisioned_groupedFilteringStillHappens() = - kosmos.runTest { + runTest { // Grouped filtering only happens when the list of subs is length 2. In this case // we'll show that filtering of provisioning subs happens before, and thus grouped // filtering happens even though the unfiltered list is length 3 @@ -384,87 +357,88 @@ class MobileIconsInteractorKairosTest : SysuiTestCase() { profileClass = PROFILE_CLASS_PROVISIONING, ) - connectionsRepository.setSubscriptions(listOf(sub1, sub2, sub3)) - connectionsRepository.setActiveMobileDataSubscriptionId(1) + mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(listOf(sub1, sub2, sub3)) + mobileConnectionsRepositoryKairos.fake.setActiveMobileDataSubscriptionId(1) - val latest by collectLastValue(underTest.filteredSubscriptions) + val latest by underTest.filteredSubscriptions.collectLastValue() assertThat(latest).isEqualTo(listOf(sub1)) } @Test - fun filteredSubscriptions_subNotExclusivelyNonTerrestrial_hasSub() = - kosmos.runTest { - val notExclusivelyNonTerrestrialSub = - SubscriptionModel( - isExclusivelyNonTerrestrial = false, - subscriptionId = 5, - carrierName = "Carrier 5", - profileClass = PROFILE_CLASS_UNSET, - ) + fun filteredSubscriptions_subNotExclusivelyNonTerrestrial_hasSub() = runTest { + val notExclusivelyNonTerrestrialSub = + SubscriptionModel( + isExclusivelyNonTerrestrial = false, + subscriptionId = 5, + carrierName = "Carrier 5", + profileClass = PROFILE_CLASS_UNSET, + ) - connectionsRepository.setSubscriptions(listOf(notExclusivelyNonTerrestrialSub)) + mobileConnectionsRepositoryKairos.fake.subscriptions.setValue( + listOf(notExclusivelyNonTerrestrialSub) + ) - val latest by collectLastValue(underTest.filteredSubscriptions) + val latest by underTest.filteredSubscriptions.collectLastValue() - assertThat(latest).isEqualTo(listOf(notExclusivelyNonTerrestrialSub)) - } + assertThat(latest).isEqualTo(listOf(notExclusivelyNonTerrestrialSub)) + } @Test - fun filteredSubscriptions_subExclusivelyNonTerrestrial_doesNotHaveSub() = - kosmos.runTest { - val exclusivelyNonTerrestrialSub = - SubscriptionModel( - isExclusivelyNonTerrestrial = true, - subscriptionId = 5, - carrierName = "Carrier 5", - profileClass = PROFILE_CLASS_UNSET, - ) + fun filteredSubscriptions_subExclusivelyNonTerrestrial_doesNotHaveSub() = runTest { + val exclusivelyNonTerrestrialSub = + SubscriptionModel( + isExclusivelyNonTerrestrial = true, + subscriptionId = 5, + carrierName = "Carrier 5", + profileClass = PROFILE_CLASS_UNSET, + ) - connectionsRepository.setSubscriptions(listOf(exclusivelyNonTerrestrialSub)) + mobileConnectionsRepositoryKairos.fake.subscriptions.setValue( + listOf(exclusivelyNonTerrestrialSub) + ) - val latest by collectLastValue(underTest.filteredSubscriptions) + val latest by underTest.filteredSubscriptions.collectLastValue() - assertThat(latest).isEmpty() - } + assertThat(latest).isEmpty() + } @Test - fun filteredSubscription_mixOfExclusivelyNonTerrestrialAndOther_hasOtherSubsOnly() = - kosmos.runTest { - val exclusivelyNonTerrestrialSub = - SubscriptionModel( - isExclusivelyNonTerrestrial = true, - subscriptionId = 5, - carrierName = "Carrier 5", - profileClass = PROFILE_CLASS_UNSET, - ) - val otherSub1 = - SubscriptionModel( - isExclusivelyNonTerrestrial = false, - subscriptionId = 1, - carrierName = "Carrier 1", - profileClass = PROFILE_CLASS_UNSET, - ) - val otherSub2 = - SubscriptionModel( - isExclusivelyNonTerrestrial = false, - subscriptionId = 2, - carrierName = "Carrier 2", - profileClass = PROFILE_CLASS_UNSET, - ) - - connectionsRepository.setSubscriptions( - listOf(otherSub1, exclusivelyNonTerrestrialSub, otherSub2) + fun filteredSubscription_mixOfExclusivelyNonTerrestrialAndOther_hasOtherSubsOnly() = runTest { + val exclusivelyNonTerrestrialSub = + SubscriptionModel( + isExclusivelyNonTerrestrial = true, + subscriptionId = 5, + carrierName = "Carrier 5", + profileClass = PROFILE_CLASS_UNSET, + ) + val otherSub1 = + SubscriptionModel( + isExclusivelyNonTerrestrial = false, + subscriptionId = 1, + carrierName = "Carrier 1", + profileClass = PROFILE_CLASS_UNSET, + ) + val otherSub2 = + SubscriptionModel( + isExclusivelyNonTerrestrial = false, + subscriptionId = 2, + carrierName = "Carrier 2", + profileClass = PROFILE_CLASS_UNSET, ) - val latest by collectLastValue(underTest.filteredSubscriptions) + mobileConnectionsRepositoryKairos.fake.subscriptions.setValue( + listOf(otherSub1, exclusivelyNonTerrestrialSub, otherSub2) + ) + + val latest by underTest.filteredSubscriptions.collectLastValue() - assertThat(latest).isEqualTo(listOf(otherSub1, otherSub2)) - } + assertThat(latest).isEqualTo(listOf(otherSub1, otherSub2)) + } @Test fun filteredSubscriptions_exclusivelyNonTerrestrialSub_andOpportunistic_bothFiltersHappen() = - kosmos.runTest { + runTest { // Exclusively non-terrestrial sub val exclusivelyNonTerrestrialSub = SubscriptionModel( @@ -483,10 +457,12 @@ class MobileIconsInteractorKairosTest : SysuiTestCase() { ) // WHEN both an exclusively non-terrestrial sub and opportunistic sub pair is included - connectionsRepository.setSubscriptions(listOf(sub3, sub4, exclusivelyNonTerrestrialSub)) - connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID) + mobileConnectionsRepositoryKairos.fake.subscriptions.setValue( + listOf(sub3, sub4, exclusivelyNonTerrestrialSub) + ) + mobileConnectionsRepositoryKairos.fake.setActiveMobileDataSubscriptionId(SUB_3_ID) - val latest by collectLastValue(underTest.filteredSubscriptions) + val latest by underTest.filteredSubscriptions.collectLastValue() // THEN both the only-non-terrestrial sub and the non-active sub are filtered out, // leaving only sub3. @@ -494,484 +470,427 @@ class MobileIconsInteractorKairosTest : SysuiTestCase() { } @Test - fun activeDataConnection_turnedOn() = - kosmos.runTest { - (fakeMobileConnectionsRepository.getRepoForSubId(SUB_1_ID) - as FakeMobileConnectionRepository) - .dataEnabled - .value = true + fun activeDataConnection_turnedOn() = runTest { + val connection1 = + mobileConnectionsRepositoryKairos.fake.mobileConnectionsBySubId.sample()[SUB_1_ID]!! - val latest by collectLastValue(underTest.activeDataConnectionHasDataEnabled) + connection1.dataEnabled.setValue(true) - assertThat(latest).isTrue() - } + val latest by underTest.activeDataConnectionHasDataEnabled.collectLastValue() + + assertThat(latest).isTrue() + } @Test - fun activeDataConnection_turnedOff() = - kosmos.runTest { - (fakeMobileConnectionsRepository.getRepoForSubId(SUB_1_ID) - as FakeMobileConnectionRepository) - .dataEnabled - .value = true + fun activeDataConnection_turnedOff() = runTest { + val connection1 = + mobileConnectionsRepositoryKairos.fake.mobileConnectionsBySubId.sample()[SUB_1_ID]!! - val latest by collectLastValue(underTest.activeDataConnectionHasDataEnabled) + connection1.dataEnabled.setValue(true) + val latest by underTest.activeDataConnectionHasDataEnabled.collectLastValue() - (fakeMobileConnectionsRepository.getRepoForSubId(SUB_1_ID) - as FakeMobileConnectionRepository) - .dataEnabled - .value = false + connection1.dataEnabled.setValue(false) - assertThat(latest).isFalse() - } + assertThat(latest).isFalse() + } @Test - fun activeDataConnection_invalidSubId() = - kosmos.runTest { - val latest by collectLastValue(underTest.activeDataConnectionHasDataEnabled) + fun activeDataConnection_invalidSubId() = runTest { + val latest by underTest.activeDataConnectionHasDataEnabled.collectLastValue() - connectionsRepository.setActiveMobileDataSubscriptionId(INVALID_SUBSCRIPTION_ID) + mobileConnectionsRepositoryKairos.fake.setActiveMobileDataSubscriptionId( + INVALID_SUBSCRIPTION_ID + ) - // An invalid active subId should tell us that data is off - assertThat(latest).isFalse() - } + // An invalid active subId should tell us that data is off + assertThat(latest).isFalse() + } @Test - fun failedConnection_default_validated_notFailed() = - kosmos.runTest { - val latest by collectLastValue(underTest.isDefaultConnectionFailed) + fun failedConnection_default_validated_notFailed() = runTest { + val latest by underTest.isDefaultConnectionFailed.collectLastValue() - connectionsRepository.mobileIsDefault.value = true - connectionsRepository.defaultConnectionIsValidated.value = true + mobileConnectionsRepositoryKairos.fake.mobileIsDefault.setValue(true) + mobileConnectionsRepositoryKairos.fake.defaultConnectionIsValidated.setValue(true) - assertThat(latest).isFalse() - } + assertThat(latest).isFalse() + } @Test - fun failedConnection_notDefault_notValidated_notFailed() = - kosmos.runTest { - val latest by collectLastValue(underTest.isDefaultConnectionFailed) + fun failedConnection_notDefault_notValidated_notFailed() = runTest { + val latest by underTest.isDefaultConnectionFailed.collectLastValue() - connectionsRepository.mobileIsDefault.value = false - connectionsRepository.defaultConnectionIsValidated.value = false + mobileConnectionsRepositoryKairos.fake.mobileIsDefault.setValue(false) + mobileConnectionsRepositoryKairos.fake.defaultConnectionIsValidated.setValue(false) - assertThat(latest).isFalse() - } + assertThat(latest).isFalse() + } @Test - fun failedConnection_default_notValidated_failed() = - kosmos.runTest { - val latest by collectLastValue(underTest.isDefaultConnectionFailed) + fun failedConnection_default_notValidated_failed() = runTest { + val latest by underTest.isDefaultConnectionFailed.collectLastValue() - connectionsRepository.mobileIsDefault.value = true - connectionsRepository.defaultConnectionIsValidated.value = false + mobileConnectionsRepositoryKairos.fake.mobileIsDefault.setValue(true) + mobileConnectionsRepositoryKairos.fake.defaultConnectionIsValidated.setValue(false) - assertThat(latest).isTrue() - } + assertThat(latest).isTrue() + } @Test - fun failedConnection_carrierMergedDefault_notValidated_failed() = - kosmos.runTest { - val latest by collectLastValue(underTest.isDefaultConnectionFailed) + fun failedConnection_carrierMergedDefault_notValidated_failed() = runTest { + val latest by underTest.isDefaultConnectionFailed.collectLastValue() - connectionsRepository.hasCarrierMergedConnection.value = true - connectionsRepository.defaultConnectionIsValidated.value = false + mobileConnectionsRepositoryKairos.fake.hasCarrierMergedConnection.setValue(true) + mobileConnectionsRepositoryKairos.fake.defaultConnectionIsValidated.setValue(false) - assertThat(latest).isTrue() - } + assertThat(latest).isTrue() + } /** Regression test for b/275076959. */ @Test - fun failedConnection_dataSwitchInSameGroup_notFailed() = - kosmos.runTest { - val latest by collectLastValue(underTest.isDefaultConnectionFailed) + fun failedConnection_dataSwitchInSameGroup_notFailed() = runTest { + val latest by underTest.isDefaultConnectionFailed.collectLastValue() - connectionsRepository.mobileIsDefault.value = true - connectionsRepository.defaultConnectionIsValidated.value = true - runCurrent() + mobileConnectionsRepositoryKairos.fake.mobileIsDefault.setValue(true) + mobileConnectionsRepositoryKairos.fake.defaultConnectionIsValidated.setValue(true) + runCurrent() - // WHEN there's a data change in the same subscription group - connectionsRepository.activeSubChangedInGroupEvent.emit(Unit) - connectionsRepository.defaultConnectionIsValidated.value = false - runCurrent() + // WHEN there's a data change in the same subscription group + mobileConnectionsRepositoryKairos.fake.activeSubChangedInGroupEvent.emit(Unit) + mobileConnectionsRepositoryKairos.fake.defaultConnectionIsValidated.setValue(false) + runCurrent() - // THEN the default connection is *not* marked as failed because of forced validation - assertThat(latest).isFalse() - } + // THEN the default connection is *not* marked as failed because of forced validation + assertThat(latest).isFalse() + } @Test - fun failedConnection_dataSwitchNotInSameGroup_isFailed() = - kosmos.runTest { - val latest by collectLastValue(underTest.isDefaultConnectionFailed) + fun failedConnection_dataSwitchNotInSameGroup_isFailed() = runTest { + val latest by underTest.isDefaultConnectionFailed.collectLastValue() - connectionsRepository.mobileIsDefault.value = true - connectionsRepository.defaultConnectionIsValidated.value = true - runCurrent() + mobileConnectionsRepositoryKairos.fake.mobileIsDefault.setValue(true) + mobileConnectionsRepositoryKairos.fake.defaultConnectionIsValidated.setValue(true) + runCurrent() - // WHEN the connection is invalidated without a activeSubChangedInGroupEvent - connectionsRepository.defaultConnectionIsValidated.value = false + // WHEN the connection is invalidated without a activeSubChangedInGroupEvent + mobileConnectionsRepositoryKairos.fake.defaultConnectionIsValidated.setValue(false) - // THEN the connection is immediately marked as failed - assertThat(latest).isTrue() - } + // THEN the connection is immediately marked as failed + assertThat(latest).isTrue() + } @Test - fun alwaysShowDataRatIcon_configHasTrue() = - kosmos.runTest { - val latest by collectLastValue(underTest.alwaysShowDataRatIcon) + fun alwaysShowDataRatIcon_configHasTrue() = runTest { + val latest by underTest.alwaysShowDataRatIcon.collectLastValue() - val config = MobileMappings.Config() - config.alwaysShowDataRatIcon = true - connectionsRepository.defaultDataSubRatConfig.value = config + val config = MobileMappings.Config() + config.alwaysShowDataRatIcon = true + mobileConnectionsRepositoryKairos.fake.defaultDataSubRatConfig.setValue(config) - assertThat(latest).isTrue() - } + assertThat(latest).isTrue() + } @Test - fun alwaysShowDataRatIcon_configHasFalse() = - kosmos.runTest { - val latest by collectLastValue(underTest.alwaysShowDataRatIcon) + fun alwaysShowDataRatIcon_configHasFalse() = runTest { + val latest by underTest.alwaysShowDataRatIcon.collectLastValue() - val config = MobileMappings.Config() - config.alwaysShowDataRatIcon = false - connectionsRepository.defaultDataSubRatConfig.value = config + val config = MobileMappings.Config() + config.alwaysShowDataRatIcon = false + mobileConnectionsRepositoryKairos.fake.defaultDataSubRatConfig.setValue(config) - assertThat(latest).isFalse() - } + assertThat(latest).isFalse() + } @Test - fun alwaysUseCdmaLevel_configHasTrue() = - kosmos.runTest { - val latest by collectLastValue(underTest.alwaysUseCdmaLevel) + fun alwaysUseCdmaLevel_configHasTrue() = runTest { + val latest by underTest.alwaysUseCdmaLevel.collectLastValue() - val config = MobileMappings.Config() - config.alwaysShowCdmaRssi = true - connectionsRepository.defaultDataSubRatConfig.value = config + val config = MobileMappings.Config() + config.alwaysShowCdmaRssi = true + mobileConnectionsRepositoryKairos.fake.defaultDataSubRatConfig.setValue(config) - assertThat(latest).isTrue() - } + assertThat(latest).isTrue() + } @Test - fun alwaysUseCdmaLevel_configHasFalse() = - kosmos.runTest { - val latest by collectLastValue(underTest.alwaysUseCdmaLevel) + fun alwaysUseCdmaLevel_configHasFalse() = runTest { + val latest by underTest.alwaysUseCdmaLevel.collectLastValue() - val config = MobileMappings.Config() - config.alwaysShowCdmaRssi = false - connectionsRepository.defaultDataSubRatConfig.value = config + val config = MobileMappings.Config() + config.alwaysShowCdmaRssi = false + mobileConnectionsRepositoryKairos.fake.defaultDataSubRatConfig.setValue(config) - assertThat(latest).isFalse() - } + assertThat(latest).isFalse() + } @Test - fun isSingleCarrier_zeroSubscriptions_false() = - kosmos.runTest { - val latest by collectLastValue(underTest.isSingleCarrier) + fun isSingleCarrier_zeroSubscriptions_false() = runTest { + val latest by underTest.isSingleCarrier.collectLastValue() - connectionsRepository.setSubscriptions(emptyList()) + mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(emptyList()) - assertThat(latest).isFalse() - } + assertThat(latest).isFalse() + } @Test - fun isSingleCarrier_oneSubscription_true() = - kosmos.runTest { - val latest by collectLastValue(underTest.isSingleCarrier) + fun isSingleCarrier_oneSubscription_true() = runTest { + val latest by underTest.isSingleCarrier.collectLastValue() - connectionsRepository.setSubscriptions(listOf(SUB_1)) + mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(listOf(SUB_1)) - assertThat(latest).isTrue() - } + assertThat(latest).isTrue() + } @Test - fun isSingleCarrier_twoSubscriptions_false() = - kosmos.runTest { - val latest by collectLastValue(underTest.isSingleCarrier) + fun isSingleCarrier_twoSubscriptions_false() = runTest { + val latest by underTest.isSingleCarrier.collectLastValue() - connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2)) + mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(listOf(SUB_1, SUB_2)) - assertThat(latest).isFalse() - } + assertThat(latest).isFalse() + } @Test - fun isSingleCarrier_updates() = - kosmos.runTest { - val latest by collectLastValue(underTest.isSingleCarrier) + fun isSingleCarrier_updates() = runTest { + val latest by underTest.isSingleCarrier.collectLastValue() - connectionsRepository.setSubscriptions(listOf(SUB_1)) - assertThat(latest).isTrue() + mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(listOf(SUB_1)) + assertThat(latest).isTrue() - connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2)) - assertThat(latest).isFalse() - } + mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(listOf(SUB_1, SUB_2)) + assertThat(latest).isFalse() + } @Test - fun mobileIsDefault_mobileFalseAndCarrierMergedFalse_false() = - kosmos.runTest { - val latest by collectLastValue(underTest.mobileIsDefault) + fun mobileIsDefault_mobileFalseAndCarrierMergedFalse_false() = runTest { + val latest by underTest.mobileIsDefault.collectLastValue() - connectionsRepository.mobileIsDefault.value = false - connectionsRepository.hasCarrierMergedConnection.value = false + mobileConnectionsRepositoryKairos.fake.mobileIsDefault.setValue(false) + mobileConnectionsRepositoryKairos.fake.hasCarrierMergedConnection.setValue(false) - assertThat(latest).isFalse() - } + assertThat(latest).isFalse() + } @Test - fun mobileIsDefault_mobileTrueAndCarrierMergedFalse_true() = - kosmos.runTest { - val latest by collectLastValue(underTest.mobileIsDefault) + fun mobileIsDefault_mobileTrueAndCarrierMergedFalse_true() = runTest { + val latest by underTest.mobileIsDefault.collectLastValue() - connectionsRepository.mobileIsDefault.value = true - connectionsRepository.hasCarrierMergedConnection.value = false + mobileConnectionsRepositoryKairos.fake.mobileIsDefault.setValue(true) + mobileConnectionsRepositoryKairos.fake.hasCarrierMergedConnection.setValue(false) - assertThat(latest).isTrue() - } + assertThat(latest).isTrue() + } /** Regression test for b/272586234. */ @Test - fun mobileIsDefault_mobileFalseAndCarrierMergedTrue_true() = - kosmos.runTest { - val latest by collectLastValue(underTest.mobileIsDefault) + fun mobileIsDefault_mobileFalseAndCarrierMergedTrue_true() = runTest { + val latest by underTest.mobileIsDefault.collectLastValue() - connectionsRepository.mobileIsDefault.value = false - connectionsRepository.hasCarrierMergedConnection.value = true + mobileConnectionsRepositoryKairos.fake.mobileIsDefault.setValue(false) + mobileConnectionsRepositoryKairos.fake.hasCarrierMergedConnection.setValue(true) - assertThat(latest).isTrue() - } + assertThat(latest).isTrue() + } @Test - fun mobileIsDefault_updatesWhenRepoUpdates() = - kosmos.runTest { - val latest by collectLastValue(underTest.mobileIsDefault) + fun mobileIsDefault_updatesWhenRepoUpdates() = runTest { + val latest by underTest.mobileIsDefault.collectLastValue() - connectionsRepository.mobileIsDefault.value = true - assertThat(latest).isTrue() + mobileConnectionsRepositoryKairos.fake.mobileIsDefault.setValue(true) + assertThat(latest).isTrue() - connectionsRepository.mobileIsDefault.value = false - assertThat(latest).isFalse() + mobileConnectionsRepositoryKairos.fake.mobileIsDefault.setValue(false) + assertThat(latest).isFalse() - connectionsRepository.hasCarrierMergedConnection.value = true - assertThat(latest).isTrue() - } + mobileConnectionsRepositoryKairos.fake.hasCarrierMergedConnection.setValue(true) + assertThat(latest).isTrue() + } // The data switch tests are mostly testing the [forcingCellularValidation] flow, but that flow // is private and can only be tested by looking at [isDefaultConnectionFailed]. @Test - fun dataSwitch_inSameGroup_validatedMatchesPreviousValue_expiresAfter2s() = - kosmos.runTest { - val latest by collectLastValue(underTest.isDefaultConnectionFailed) - - connectionsRepository.mobileIsDefault.value = true - connectionsRepository.defaultConnectionIsValidated.value = true - runCurrent() - - // Trigger a data change in the same subscription group that's not yet validated - connectionsRepository.activeSubChangedInGroupEvent.emit(Unit) - connectionsRepository.defaultConnectionIsValidated.value = false - runCurrent() - - // After 1s, the force validation bit is still present, so the connection is not marked - // as failed - testScope.advanceTimeBy(1000) - assertThat(latest).isFalse() - - // After 2s, the force validation expires so the connection updates to failed - testScope.advanceTimeBy(1001) - assertThat(latest).isTrue() - } + fun dataSwitch_inSameGroup_validatedMatchesPreviousValue_expiresAfter2s() = runTest { + val latest by underTest.isDefaultConnectionFailed.collectLastValue() - @Test - fun dataSwitch_inSameGroup_notValidated_immediatelyMarkedAsFailed() = - kosmos.runTest { - val latest by collectLastValue(underTest.isDefaultConnectionFailed) + mobileConnectionsRepositoryKairos.fake.mobileIsDefault.setValue(true) + mobileConnectionsRepositoryKairos.fake.defaultConnectionIsValidated.setValue(true) + runCurrent() - connectionsRepository.mobileIsDefault.value = true - connectionsRepository.defaultConnectionIsValidated.value = false - runCurrent() + // Trigger a data change in the same subscription group that's not yet validated + mobileConnectionsRepositoryKairos.fake.activeSubChangedInGroupEvent.emit(Unit) + mobileConnectionsRepositoryKairos.fake.defaultConnectionIsValidated.setValue(false) + runCurrent() - connectionsRepository.activeSubChangedInGroupEvent.emit(Unit) + // After 1s, the force validation bit is still present, so the connection is not marked + // as failed + testScope.advanceTimeBy(1000) + assertThat(latest).isFalse() - assertThat(latest).isTrue() - } + // After 2s, the force validation expires so the connection updates to failed + testScope.advanceTimeBy(1001) + assertThat(latest).isTrue() + } @Test - fun dataSwitch_loseValidation_thenSwitchHappens_clearsForcedBit() = - kosmos.runTest { - val latest by collectLastValue(underTest.isDefaultConnectionFailed) + fun dataSwitch_inSameGroup_notValidated_immediatelyMarkedAsFailed() = runTest { + val latest by underTest.isDefaultConnectionFailed.collectLastValue() - // GIVEN the network starts validated - connectionsRepository.mobileIsDefault.value = true - connectionsRepository.defaultConnectionIsValidated.value = true - runCurrent() + mobileConnectionsRepositoryKairos.fake.mobileIsDefault.setValue(true) + mobileConnectionsRepositoryKairos.fake.defaultConnectionIsValidated.setValue(false) + runCurrent() - // WHEN a data change happens in the same group - connectionsRepository.activeSubChangedInGroupEvent.emit(Unit) + mobileConnectionsRepositoryKairos.fake.activeSubChangedInGroupEvent.emit(Unit) - // WHEN the validation bit is lost - connectionsRepository.defaultConnectionIsValidated.value = false - runCurrent() - - // WHEN another data change happens in the same group - connectionsRepository.activeSubChangedInGroupEvent.emit(Unit) - - // THEN the forced validation bit is still used... - assertThat(latest).isFalse() - - testScope.advanceTimeBy(1000) - assertThat(latest).isFalse() - - // ... but expires after 2s - testScope.advanceTimeBy(1001) - assertThat(latest).isTrue() - } + assertThat(latest).isTrue() + } @Test - fun dataSwitch_whileAlreadyForcingValidation_resetsClock() = - kosmos.runTest { - val latest by collectLastValue(underTest.isDefaultConnectionFailed) - connectionsRepository.mobileIsDefault.value = true - connectionsRepository.defaultConnectionIsValidated.value = true - runCurrent() + fun dataSwitch_loseValidation_thenSwitchHappens_clearsForcedBit() = runTest { + val latest by underTest.isDefaultConnectionFailed.collectLastValue() - connectionsRepository.activeSubChangedInGroupEvent.emit(Unit) + // GIVEN the network starts validated + mobileConnectionsRepositoryKairos.fake.mobileIsDefault.setValue(true) + mobileConnectionsRepositoryKairos.fake.defaultConnectionIsValidated.setValue(true) + runCurrent() - testScope.advanceTimeBy(1000) + // WHEN a data change happens in the same group + mobileConnectionsRepositoryKairos.fake.activeSubChangedInGroupEvent.emit(Unit) - // WHEN another change in same group event happens - connectionsRepository.activeSubChangedInGroupEvent.emit(Unit) - connectionsRepository.defaultConnectionIsValidated.value = false - runCurrent() + // WHEN the validation bit is lost + mobileConnectionsRepositoryKairos.fake.defaultConnectionIsValidated.setValue(false) + runCurrent() - // THEN the forced validation remains for exactly 2 more seconds from now + // WHEN another data change happens in the same group + mobileConnectionsRepositoryKairos.fake.activeSubChangedInGroupEvent.emit(Unit) - // 1.500s from second event - testScope.advanceTimeBy(1500) - assertThat(latest).isFalse() + // THEN the forced validation bit is still used... + assertThat(latest).isFalse() - // 2.001s from the second event - testScope.advanceTimeBy(501) - assertThat(latest).isTrue() - } + testScope.advanceTimeBy(1000) + assertThat(latest).isFalse() - @Test - fun isForceHidden_repoHasMobileHidden_true() = - kosmos.runTest { - val latest by collectLastValue(underTest.isForceHidden) + // ... but expires after 2s + testScope.advanceTimeBy(1001) + assertThat(latest).isTrue() + } - kosmos.connectivityRepository.fake.setForceHiddenIcons(setOf(ConnectivitySlot.MOBILE)) + @Test + fun dataSwitch_whileAlreadyForcingValidation_resetsClock() = runTest { + val latest by underTest.isDefaultConnectionFailed.collectLastValue() + mobileConnectionsRepositoryKairos.fake.mobileIsDefault.setValue(true) + mobileConnectionsRepositoryKairos.fake.defaultConnectionIsValidated.setValue(true) + runCurrent() - assertThat(latest).isTrue() - } + mobileConnectionsRepositoryKairos.fake.activeSubChangedInGroupEvent.emit(Unit) - @Test - fun isForceHidden_repoDoesNotHaveMobileHidden_false() = - kosmos.runTest { - val latest by collectLastValue(underTest.isForceHidden) + testScope.advanceTimeBy(1000) - kosmos.connectivityRepository.fake.setForceHiddenIcons(setOf(ConnectivitySlot.WIFI)) + // WHEN another change in same group event happens + mobileConnectionsRepositoryKairos.fake.activeSubChangedInGroupEvent.emit(Unit) + mobileConnectionsRepositoryKairos.fake.defaultConnectionIsValidated.setValue(false) + runCurrent() - assertThat(latest).isFalse() - } + // THEN the forced validation remains for exactly 2 more seconds from now - @Test - fun iconInteractor_cachedPerSubId() = - kosmos.runTest { - val interactor1 = underTest.getMobileConnectionInteractorForSubId(SUB_1_ID) - val interactor2 = underTest.getMobileConnectionInteractorForSubId(SUB_1_ID) + // 1.500s from second event + testScope.advanceTimeBy(1500) + assertThat(latest).isFalse() - assertThat(interactor1).isNotNull() - assertThat(interactor1).isSameInstanceAs(interactor2) - } + // 2.001s from the second event + testScope.advanceTimeBy(501) + assertThat(latest).isTrue() + } @Test - fun deviceBasedEmergencyMode_emergencyCallsOnly_followsDeviceServiceStateFromRepo() = - kosmos.runTest { - val latest by collectLastValue(underTest.isDeviceInEmergencyCallsOnlyMode) + fun isForceHidden_repoHasMobileHidden_true() = runTest { + val latest by underTest.isForceHidden.collectLastValue() - connectionsRepository.isDeviceEmergencyCallCapable.value = true + kosmos.connectivityRepository.fake.setForceHiddenIcons(setOf(ConnectivitySlot.MOBILE)) - assertThat(latest).isTrue() + assertThat(latest).isTrue() + } + + @Test + fun isForceHidden_repoDoesNotHaveMobileHidden_false() = runTest { + val latest by underTest.isForceHidden.collectLastValue() - connectionsRepository.isDeviceEmergencyCallCapable.value = false + kosmos.connectivityRepository.fake.setForceHiddenIcons(setOf(ConnectivitySlot.WIFI)) - assertThat(latest).isFalse() - } + assertThat(latest).isFalse() + } @Test - fun defaultDataSubId_tracksRepo() = - kosmos.runTest { - val latest by collectLastValue(underTest.defaultDataSubId) + fun deviceBasedEmergencyMode_emergencyCallsOnly_followsDeviceServiceStateFromRepo() = runTest { + val latest by underTest.isDeviceInEmergencyCallsOnlyMode.collectLastValue() - connectionsRepository.defaultDataSubId.value = 1 + mobileConnectionsRepositoryKairos.fake.isDeviceEmergencyCallCapable.setValue(true) - assertThat(latest).isEqualTo(1) + assertThat(latest).isTrue() - connectionsRepository.defaultDataSubId.value = 2 + mobileConnectionsRepositoryKairos.fake.isDeviceEmergencyCallCapable.setValue(false) - assertThat(latest).isEqualTo(2) - } + assertThat(latest).isFalse() + } @Test @EnableFlags(NewStatusBarIcons.FLAG_NAME, StatusBarRootModernization.FLAG_NAME) - fun isStackable_tracksNumberOfSubscriptions() = - kosmos.runTest { - val latest by collectLastValue(underTest.isStackable) + fun isStackable_tracksNumberOfSubscriptions() = runTest { + val latest by underTest.isStackable.collectLastValue() - connectionsRepository.setSubscriptions(listOf(SUB_1)) - assertThat(latest).isFalse() + mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(listOf(SUB_1)) + assertThat(latest).isFalse() - connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2)) - assertThat(latest).isTrue() + mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(listOf(SUB_1, SUB_2)) + assertThat(latest).isTrue() - connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2, SUB_3_OPP)) - assertThat(latest).isFalse() - } + mobileConnectionsRepositoryKairos.fake.subscriptions.setValue( + listOf(SUB_1, SUB_2, SUB_3_OPP) + ) + assertThat(latest).isFalse() + } @Test @EnableFlags(NewStatusBarIcons.FLAG_NAME, StatusBarRootModernization.FLAG_NAME) - fun isStackable_checksForTerrestrialConnections() = - kosmos.runTest { - val latest by collectLastValue(underTest.isStackable) + fun isStackable_checksForTerrestrialConnections() = runTest { + val latest by underTest.isStackable.collectLastValue() - connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2)) - setNumberOfLevelsForSubId(SUB_1_ID, 5) - setNumberOfLevelsForSubId(SUB_2_ID, 5) - assertThat(latest).isTrue() + mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(listOf(SUB_1, SUB_2)) + setNumberOfLevelsForSubId(SUB_1_ID, 5) + setNumberOfLevelsForSubId(SUB_2_ID, 5) + assertThat(latest).isTrue() - (fakeMobileConnectionsRepository.getRepoForSubId(SUB_1_ID) - as FakeMobileConnectionRepository) - .isNonTerrestrial - .value = true + mobileConnectionsRepositoryKairos.fake.mobileConnectionsBySubId + .sample()[SUB_1_ID]!! + .isNonTerrestrial + .setValue(true) - assertThat(latest).isFalse() - } + assertThat(latest).isFalse() + } @Test @EnableFlags(NewStatusBarIcons.FLAG_NAME, StatusBarRootModernization.FLAG_NAME) - fun isStackable_checksForNumberOfBars() = - kosmos.runTest { - val latest by collectLastValue(underTest.isStackable) + fun isStackable_checksForNumberOfBars() = runTest { + val latest by underTest.isStackable.collectLastValue() - // Number of levels is the same for both - connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2)) - setNumberOfLevelsForSubId(SUB_1_ID, 5) - setNumberOfLevelsForSubId(SUB_2_ID, 5) + // Number of levels is the same for both + mobileConnectionsRepositoryKairos.fake.subscriptions.setValue(listOf(SUB_1, SUB_2)) + setNumberOfLevelsForSubId(SUB_1_ID, 5) + setNumberOfLevelsForSubId(SUB_2_ID, 5) - assertThat(latest).isTrue() + assertThat(latest).isTrue() - // Change the number of levels to be different than SUB_2 - setNumberOfLevelsForSubId(SUB_1_ID, 6) + // Change the number of levels to be different than SUB_2 + setNumberOfLevelsForSubId(SUB_1_ID, 6) - assertThat(latest).isFalse() - } + assertThat(latest).isFalse() + } - private fun setNumberOfLevelsForSubId(subId: Int, numberOfLevels: Int) { - with(kosmos) { - (fakeMobileConnectionsRepository.getRepoForSubId(subId) - as FakeMobileConnectionRepository) - .numberOfLevels - .value = numberOfLevels - } + private suspend fun KairosTestScope.setNumberOfLevelsForSubId(subId: Int, numberOfLevels: Int) { + mobileConnectionsRepositoryKairos.fake.mobileConnectionsBySubId + .sample()[subId]!! + .numberOfLevels + .setValue(numberOfLevels) } /** diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelKairosTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelKairosTest.kt new file mode 100644 index 000000000000..57e63a595b8f --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelKairosTest.kt @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.flags.FakeFeatureFlagsClassic +import com.android.systemui.flags.Flags +import com.android.systemui.log.table.logcatTableLogBuffer +import com.android.systemui.statusbar.connectivity.MobileIconCarrierIdOverridesFake +import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository +import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor +import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState +import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType +import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository +import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository +import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractor +import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractorImpl +import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor +import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractorImpl +import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel +import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants +import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository +import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository +import com.android.systemui.testKosmos +import com.android.systemui.util.CarrierConfigTracker +import com.android.systemui.util.mockito.mock +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.MockitoAnnotations + +@Suppress("EXPERIMENTAL_IS_NOT_ENABLED") +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +class LocationBasedMobileIconViewModelKairosTest : SysuiTestCase() { + private val kosmos = testKosmos() + + private lateinit var commonImpl: MobileIconViewModelCommonKairos + private lateinit var homeIcon: HomeMobileIconViewModelKairos + private lateinit var qsIcon: QsMobileIconViewModelKairos + private lateinit var keyguardIcon: KeyguardMobileIconViewModelKairos + private lateinit var iconsInteractor: MobileIconsInteractor + private lateinit var interactor: MobileIconInteractor + private val connectionsRepository = kosmos.fakeMobileConnectionsRepository + private lateinit var repository: FakeMobileConnectionRepository + private lateinit var airplaneModeInteractor: AirplaneModeInteractor + + private val connectivityRepository = FakeConnectivityRepository() + private val flags = + FakeFeatureFlagsClassic().also { + it.set(Flags.FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS, true) + } + + @Mock private lateinit var constants: ConnectivityConstants + private val tableLogBuffer = + logcatTableLogBuffer(kosmos, "LocationBasedMobileIconViewModelTest") + @Mock private lateinit var carrierConfigTracker: CarrierConfigTracker + + private val testDispatcher = UnconfinedTestDispatcher() + private val testScope = TestScope(testDispatcher) + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + airplaneModeInteractor = + AirplaneModeInteractor( + FakeAirplaneModeRepository(), + FakeConnectivityRepository(), + connectionsRepository, + ) + repository = + FakeMobileConnectionRepository(SUB_1_ID, tableLogBuffer).apply { + isInService.value = true + cdmaLevel.value = 1 + primaryLevel.value = 1 + isEmergencyOnly.value = false + numberOfLevels.value = 4 + resolvedNetworkType.value = ResolvedNetworkType.DefaultNetworkType(lookupKey = "3G") + dataConnectionState.value = DataConnectionState.Connected + } + + connectionsRepository.activeMobileDataRepository.value = repository + + connectivityRepository.apply { setMobileConnected() } + + iconsInteractor = + MobileIconsInteractorImpl( + connectionsRepository, + carrierConfigTracker, + tableLogBuffer, + connectivityRepository, + FakeUserSetupRepository(), + testScope.backgroundScope, + context, + flags, + ) + + interactor = + MobileIconInteractorImpl( + testScope.backgroundScope, + iconsInteractor.activeDataConnectionHasDataEnabled, + iconsInteractor.alwaysShowDataRatIcon, + iconsInteractor.alwaysUseCdmaLevel, + iconsInteractor.isSingleCarrier, + iconsInteractor.mobileIsDefault, + iconsInteractor.defaultMobileIconMapping, + iconsInteractor.defaultMobileIconGroup, + iconsInteractor.isDefaultConnectionFailed, + iconsInteractor.isForceHidden, + repository, + context, + MobileIconCarrierIdOverridesFake(), + ) + + commonImpl = + MobileIconViewModelKairos( + SUB_1_ID, + interactor, + airplaneModeInteractor, + constants, + testScope.backgroundScope, + ) + + homeIcon = HomeMobileIconViewModelKairos(commonImpl, mock()) + qsIcon = QsMobileIconViewModelKairos(commonImpl) + keyguardIcon = KeyguardMobileIconViewModelKairos(commonImpl) + } + + @Test + fun locationBasedViewModelsReceiveSameIconIdWhenCommonImplUpdates() = + testScope.runTest { + var latestHome: SignalIconModel? = null + val homeJob = homeIcon.icon.onEach { latestHome = it }.launchIn(this) + + var latestQs: SignalIconModel? = null + val qsJob = qsIcon.icon.onEach { latestQs = it }.launchIn(this) + + var latestKeyguard: SignalIconModel? = null + val keyguardJob = keyguardIcon.icon.onEach { latestKeyguard = it }.launchIn(this) + + var expected = defaultSignal(level = 1) + + assertThat(latestHome).isEqualTo(expected) + assertThat(latestQs).isEqualTo(expected) + assertThat(latestKeyguard).isEqualTo(expected) + + repository.setAllLevels(2) + expected = defaultSignal(level = 2) + + assertThat(latestHome).isEqualTo(expected) + assertThat(latestQs).isEqualTo(expected) + assertThat(latestKeyguard).isEqualTo(expected) + + homeJob.cancel() + qsJob.cancel() + keyguardJob.cancel() + } + + companion object { + private const val SUB_1_ID = 1 + private const val NUM_LEVELS = 4 + + /** Convenience constructor for these tests */ + fun defaultSignal(level: Int = 1): SignalIconModel { + return SignalIconModel.Cellular( + level, + NUM_LEVELS, + showExclamationMark = false, + carrierNetworkChange = false, + ) + } + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelKairosTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelKairosTest.kt new file mode 100644 index 000000000000..6b114a8256f2 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelKairosTest.kt @@ -0,0 +1,1077 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel + +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.settingslib.mobile.MobileMappings +import com.android.settingslib.mobile.TelephonyIcons.G +import com.android.settingslib.mobile.TelephonyIcons.THREE_G +import com.android.settingslib.mobile.TelephonyIcons.UNKNOWN +import com.android.systemui.Flags.FLAG_STATUS_BAR_STATIC_INOUT_INDICATORS +import com.android.systemui.SysuiTestCase +import com.android.systemui.common.shared.model.ContentDescription +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.flags.FakeFeatureFlagsClassic +import com.android.systemui.flags.Flags +import com.android.systemui.log.table.logcatTableLogBuffer +import com.android.systemui.res.R +import com.android.systemui.statusbar.connectivity.MobileIconCarrierIdOverridesFake +import com.android.systemui.statusbar.core.NewStatusBarIcons +import com.android.systemui.statusbar.core.StatusBarRootModernization +import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository +import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor +import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState +import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel +import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository +import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository.Companion.DEFAULT_NETWORK_NAME +import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository +import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository +import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractorImpl +import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractorImpl +import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel +import com.android.systemui.statusbar.pipeline.mobile.ui.model.MobileContentDescription +import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy +import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants +import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot +import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel +import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository +import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository +import com.android.systemui.testKosmos +import com.android.systemui.util.CarrierConfigTracker +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import com.google.common.truth.Truth.assertWithMessage +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.filterIsInstance +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.yield +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.MockitoAnnotations + +@Suppress("EXPERIMENTAL_IS_NOT_ENABLED") +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +class MobileIconViewModelKairosTest : SysuiTestCase() { + private val kosmos = testKosmos() + + private var connectivityRepository = FakeConnectivityRepository() + + private lateinit var underTest: MobileIconViewModelKairos + private lateinit var interactor: MobileIconInteractorImpl + private lateinit var iconsInteractor: MobileIconsInteractorImpl + private lateinit var repository: FakeMobileConnectionRepository + private lateinit var connectionsRepository: FakeMobileConnectionsRepository + private lateinit var airplaneModeRepository: FakeAirplaneModeRepository + private lateinit var airplaneModeInteractor: AirplaneModeInteractor + @Mock private lateinit var constants: ConnectivityConstants + private val tableLogBuffer = logcatTableLogBuffer(kosmos, "MobileIconViewModelTest") + @Mock private lateinit var carrierConfigTracker: CarrierConfigTracker + + private val flags = + FakeFeatureFlagsClassic().also { + it.set(Flags.FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS, true) + } + private val testDispatcher = UnconfinedTestDispatcher() + private val testScope = TestScope(testDispatcher) + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + whenever(constants.hasDataCapabilities).thenReturn(true) + + connectionsRepository = + FakeMobileConnectionsRepository(FakeMobileMappingsProxy(), tableLogBuffer) + + repository = + FakeMobileConnectionRepository(SUB_1_ID, tableLogBuffer).apply { + setNetworkTypeKey(connectionsRepository.GSM_KEY) + isInService.value = true + dataConnectionState.value = DataConnectionState.Connected + dataEnabled.value = true + } + connectionsRepository.activeMobileDataRepository.value = repository + connectionsRepository.mobileIsDefault.value = true + + airplaneModeRepository = FakeAirplaneModeRepository() + airplaneModeInteractor = + AirplaneModeInteractor( + airplaneModeRepository, + connectivityRepository, + kosmos.fakeMobileConnectionsRepository, + ) + + iconsInteractor = + MobileIconsInteractorImpl( + connectionsRepository, + carrierConfigTracker, + tableLogBuffer, + connectivityRepository, + FakeUserSetupRepository(), + testScope.backgroundScope, + context, + flags, + ) + + interactor = + MobileIconInteractorImpl( + testScope.backgroundScope, + iconsInteractor.activeDataConnectionHasDataEnabled, + iconsInteractor.alwaysShowDataRatIcon, + iconsInteractor.alwaysUseCdmaLevel, + iconsInteractor.isSingleCarrier, + iconsInteractor.mobileIsDefault, + iconsInteractor.defaultMobileIconMapping, + iconsInteractor.defaultMobileIconGroup, + iconsInteractor.isDefaultConnectionFailed, + iconsInteractor.isForceHidden, + repository, + context, + MobileIconCarrierIdOverridesFake(), + ) + createAndSetViewModel() + } + + @Test + fun isVisible_notDataCapable_alwaysFalse() = + testScope.runTest { + // Create a new view model here so the constants are properly read + whenever(constants.hasDataCapabilities).thenReturn(false) + createAndSetViewModel() + + var latest: Boolean? = null + val job = underTest.isVisible.onEach { latest = it }.launchIn(this) + + assertThat(latest).isFalse() + + job.cancel() + } + + @Test + fun isVisible_notAirplane_notForceHidden_true() = + testScope.runTest { + var latest: Boolean? = null + val job = underTest.isVisible.onEach { latest = it }.launchIn(this) + + airplaneModeRepository.setIsAirplaneMode(false) + + assertThat(latest).isTrue() + + job.cancel() + } + + @Test + fun isVisible_airplaneAndNotAllowed_false() = + testScope.runTest { + var latest: Boolean? = null + val job = underTest.isVisible.onEach { latest = it }.launchIn(this) + + airplaneModeRepository.setIsAirplaneMode(true) + repository.isAllowedDuringAirplaneMode.value = false + connectivityRepository.setForceHiddenIcons(setOf()) + + assertThat(latest).isFalse() + + job.cancel() + } + + /** Regression test for b/291993542. */ + @Test + fun isVisible_airplaneButAllowed_true() = + testScope.runTest { + var latest: Boolean? = null + val job = underTest.isVisible.onEach { latest = it }.launchIn(this) + + airplaneModeRepository.setIsAirplaneMode(true) + repository.isAllowedDuringAirplaneMode.value = true + connectivityRepository.setForceHiddenIcons(setOf()) + + assertThat(latest).isTrue() + + job.cancel() + } + + @Test + fun isVisible_forceHidden_false() = + testScope.runTest { + var latest: Boolean? = null + val job = underTest.isVisible.onEach { latest = it }.launchIn(this) + + airplaneModeRepository.setIsAirplaneMode(false) + connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.MOBILE)) + + assertThat(latest).isFalse() + + job.cancel() + } + + @Test + fun isVisible_respondsToUpdates() = + testScope.runTest { + var latest: Boolean? = null + val job = underTest.isVisible.onEach { latest = it }.launchIn(this) + + airplaneModeRepository.setIsAirplaneMode(false) + connectivityRepository.setForceHiddenIcons(setOf()) + + assertThat(latest).isTrue() + + airplaneModeRepository.setIsAirplaneMode(true) + assertThat(latest).isFalse() + + repository.isAllowedDuringAirplaneMode.value = true + assertThat(latest).isTrue() + + connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.MOBILE)) + assertThat(latest).isFalse() + + job.cancel() + } + + @Test + fun isVisible_satellite_respectsAirplaneMode() = + testScope.runTest { + val latest by collectLastValue(underTest.isVisible) + + repository.isNonTerrestrial.value = true + airplaneModeInteractor.setIsAirplaneMode(false) + + assertThat(latest).isTrue() + + airplaneModeInteractor.setIsAirplaneMode(true) + + assertThat(latest).isFalse() + } + + @Test + fun contentDescription_notInService_usesNoPhone() = + testScope.runTest { + val latest by collectLastValue(underTest.contentDescription) + + repository.isInService.value = false + + assertThat(latest as MobileContentDescription.Cellular) + .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, NO_SIGNAL)) + } + + @Test + fun contentDescription_includesNetworkName() = + testScope.runTest { + val latest by collectLastValue(underTest.contentDescription) + + repository.isInService.value = true + repository.networkName.value = NetworkNameModel.SubscriptionDerived("Test Network Name") + repository.numberOfLevels.value = 5 + repository.setAllLevels(3) + + assertThat(latest as MobileContentDescription.Cellular) + .isEqualTo(MobileContentDescription.Cellular("Test Network Name", THREE_BARS)) + } + + @Test + fun contentDescription_inService_usesLevel() = + testScope.runTest { + val latest by collectLastValue(underTest.contentDescription) + + repository.setAllLevels(2) + + assertThat(latest as MobileContentDescription.Cellular) + .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, TWO_BARS)) + + repository.setAllLevels(0) + + assertThat(latest as MobileContentDescription.Cellular) + .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, NO_SIGNAL)) + } + + @Test + fun contentDescription_nonInflated_invalidLevelUsesNoSignalText() = + testScope.runTest { + val latest by collectLastValue(underTest.contentDescription) + + repository.inflateSignalStrength.value = false + repository.setAllLevels(-1) + + assertThat(latest as MobileContentDescription.Cellular) + .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, NO_SIGNAL)) + + repository.setAllLevels(100) + + assertThat(latest as MobileContentDescription.Cellular) + .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, NO_SIGNAL)) + } + + @Test + fun contentDescription_nonInflated_levelStrings() = + testScope.runTest { + val latest by collectLastValue(underTest.contentDescription) + + repository.inflateSignalStrength.value = false + repository.setAllLevels(0) + + assertThat(latest as MobileContentDescription.Cellular) + .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, NO_SIGNAL)) + + repository.setAllLevels(1) + + assertThat(latest as MobileContentDescription.Cellular) + .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, ONE_BAR)) + + repository.setAllLevels(2) + + assertThat(latest as MobileContentDescription.Cellular) + .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, TWO_BARS)) + + repository.setAllLevels(3) + + assertThat(latest as MobileContentDescription.Cellular) + .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, THREE_BARS)) + + repository.setAllLevels(4) + + assertThat(latest as MobileContentDescription.Cellular) + .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, FULL_BARS)) + } + + @Test + fun contentDescription_inflated_invalidLevelUsesNoSignalText() = + testScope.runTest { + val latest by collectLastValue(underTest.contentDescription) + + repository.inflateSignalStrength.value = true + repository.numberOfLevels.value = 6 + + repository.setAllLevels(-2) + + assertThat(latest as MobileContentDescription.Cellular) + .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, NO_SIGNAL)) + + repository.setAllLevels(100) + + assertThat(latest as MobileContentDescription.Cellular) + .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, NO_SIGNAL)) + } + + @Test + fun contentDescription_inflated_levelStrings() = + testScope.runTest { + val latest by collectLastValue(underTest.contentDescription) + + repository.inflateSignalStrength.value = true + repository.numberOfLevels.value = 6 + + // Note that the _repo_ level is 1 lower than the reported level through the interactor + + repository.setAllLevels(0) + + assertThat(latest as MobileContentDescription.Cellular) + .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, ONE_BAR)) + + repository.setAllLevels(1) + + assertThat(latest as MobileContentDescription.Cellular) + .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, TWO_BARS)) + + repository.setAllLevels(2) + + assertThat(latest as MobileContentDescription.Cellular) + .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, THREE_BARS)) + + repository.setAllLevels(3) + + assertThat(latest as MobileContentDescription.Cellular) + .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, FOUR_BARS)) + + repository.setAllLevels(4) + + assertThat(latest as MobileContentDescription.Cellular) + .isEqualTo(MobileContentDescription.Cellular(DEFAULT_NETWORK_NAME, FULL_BARS)) + } + + @Test + fun contentDescription_nonInflated_testABunchOfLevelsForNull() = + testScope.runTest { + val latest by collectLastValue(underTest.contentDescription) + + repository.inflateSignalStrength.value = false + repository.numberOfLevels.value = 5 + + // -1 and 5 are out of the bounds for non-inflated content descriptions + for (i in -1..5) { + repository.setAllLevels(i) + when (i) { + -1, + 5 -> + assertWithMessage("Level $i is expected to be 'no signal'") + .that((latest as MobileContentDescription.Cellular).levelDescriptionRes) + .isEqualTo(NO_SIGNAL) + else -> + assertWithMessage("Level $i is expected not to be null") + .that(latest) + .isNotNull() + } + } + } + + @Test + fun contentDescription_inflated_testABunchOfLevelsForNull() = + testScope.runTest { + val latest by collectLastValue(underTest.contentDescription) + repository.inflateSignalStrength.value = true + repository.numberOfLevels.value = 6 + // -1 and 6 are out of the bounds for inflated content descriptions + // Note that the interactor adds 1 to the reported level, hence the -2 to 5 range + for (i in -2..5) { + repository.setAllLevels(i) + when (i) { + -2, + 5 -> + assertWithMessage("Level $i is expected to be 'no signal'") + .that((latest as MobileContentDescription.Cellular).levelDescriptionRes) + .isEqualTo(NO_SIGNAL) + else -> + assertWithMessage("Level $i is not expected to be null") + .that(latest) + .isNotNull() + } + } + } + + @Test + fun networkType_dataEnabled_groupIsRepresented() = + testScope.runTest { + val expected = + Icon.Resource( + THREE_G.dataType, + ContentDescription.Resource(THREE_G.dataContentDescription), + ) + connectionsRepository.mobileIsDefault.value = true + repository.setNetworkTypeKey(connectionsRepository.GSM_KEY) + + var latest: Icon? = null + val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this) + + assertThat(latest).isEqualTo(expected) + + job.cancel() + } + + @Test + fun networkType_null_whenDisabled() = + testScope.runTest { + repository.setNetworkTypeKey(connectionsRepository.GSM_KEY) + repository.setDataEnabled(false) + connectionsRepository.mobileIsDefault.value = true + var latest: Icon? = null + val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this) + + assertThat(latest).isNull() + + job.cancel() + } + + @Test + fun networkType_null_whenCarrierNetworkChangeActive() = + testScope.runTest { + repository.setNetworkTypeKey(connectionsRepository.GSM_KEY) + repository.carrierNetworkChangeActive.value = true + connectionsRepository.mobileIsDefault.value = true + var latest: Icon? = null + val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this) + + assertThat(latest).isNull() + + job.cancel() + } + + @Test + fun networkTypeIcon_notNull_whenEnabled() = + testScope.runTest { + val expected = + Icon.Resource( + THREE_G.dataType, + ContentDescription.Resource(THREE_G.dataContentDescription), + ) + repository.setNetworkTypeKey(connectionsRepository.GSM_KEY) + repository.setDataEnabled(true) + repository.dataConnectionState.value = DataConnectionState.Connected + connectionsRepository.mobileIsDefault.value = true + var latest: Icon? = null + val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this) + + assertThat(latest).isEqualTo(expected) + + job.cancel() + } + + @Test + fun networkType_nullWhenDataDisconnects() = + testScope.runTest { + val initial = + Icon.Resource( + THREE_G.dataType, + ContentDescription.Resource(THREE_G.dataContentDescription), + ) + + repository.setNetworkTypeKey(connectionsRepository.GSM_KEY) + var latest: Icon? = null + val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this) + + assertThat(latest).isEqualTo(initial) + + repository.dataConnectionState.value = DataConnectionState.Disconnected + + assertThat(latest).isNull() + + job.cancel() + } + + @Test + fun networkType_null_changeToDisabled() = + testScope.runTest { + val expected = + Icon.Resource( + THREE_G.dataType, + ContentDescription.Resource(THREE_G.dataContentDescription), + ) + repository.dataEnabled.value = true + var latest: Icon? = null + val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this) + + assertThat(latest).isEqualTo(expected) + + repository.dataEnabled.value = false + + assertThat(latest).isNull() + + job.cancel() + } + + @Test + fun networkType_alwaysShow_shownEvenWhenDisabled() = + testScope.runTest { + repository.dataEnabled.value = false + + connectionsRepository.defaultDataSubRatConfig.value = + MobileMappings.Config().also { it.alwaysShowDataRatIcon = true } + + var latest: Icon? = null + val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this) + + val expected = + Icon.Resource( + THREE_G.dataType, + ContentDescription.Resource(THREE_G.dataContentDescription), + ) + assertThat(latest).isEqualTo(expected) + + job.cancel() + } + + @Test + fun networkType_alwaysShow_shownEvenWhenDisconnected() = + testScope.runTest { + repository.setNetworkTypeKey(connectionsRepository.GSM_KEY) + repository.dataConnectionState.value = DataConnectionState.Disconnected + + connectionsRepository.defaultDataSubRatConfig.value = + MobileMappings.Config().also { it.alwaysShowDataRatIcon = true } + + var latest: Icon? = null + val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this) + + val expected = + Icon.Resource( + THREE_G.dataType, + ContentDescription.Resource(THREE_G.dataContentDescription), + ) + assertThat(latest).isEqualTo(expected) + + job.cancel() + } + + @Test + fun networkType_alwaysShow_shownEvenWhenFailedConnection() = + testScope.runTest { + repository.setNetworkTypeKey(connectionsRepository.GSM_KEY) + connectionsRepository.mobileIsDefault.value = true + connectionsRepository.defaultDataSubRatConfig.value = + MobileMappings.Config().also { it.alwaysShowDataRatIcon = true } + + var latest: Icon? = null + val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this) + + val expected = + Icon.Resource( + THREE_G.dataType, + ContentDescription.Resource(THREE_G.dataContentDescription), + ) + assertThat(latest).isEqualTo(expected) + + job.cancel() + } + + @Test + fun networkType_alwaysShow_usesDefaultIconWhenInvalid() = + testScope.runTest { + // The UNKNOWN icon group doesn't have a valid data type icon ID, and the logic from the + // old pipeline was to use the default icon group if the map doesn't exist + repository.setNetworkTypeKey(UNKNOWN.name) + connectionsRepository.defaultDataSubRatConfig.value = + MobileMappings.Config().also { it.alwaysShowDataRatIcon = true } + + var latest: Icon? = null + val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this) + + val expected = + Icon.Resource( + connectionsRepository.defaultMobileIconGroup.value.dataType, + ContentDescription.Resource(G.dataContentDescription), + ) + + assertThat(latest).isEqualTo(expected) + + job.cancel() + } + + @Test + fun networkType_alwaysShow_shownWhenNotDefault() = + testScope.runTest { + repository.setNetworkTypeKey(connectionsRepository.GSM_KEY) + connectionsRepository.mobileIsDefault.value = false + connectionsRepository.defaultDataSubRatConfig.value = + MobileMappings.Config().also { it.alwaysShowDataRatIcon = true } + + var latest: Icon? = null + val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this) + + val expected = + Icon.Resource( + THREE_G.dataType, + ContentDescription.Resource(THREE_G.dataContentDescription), + ) + assertThat(latest).isEqualTo(expected) + + job.cancel() + } + + @Test + fun networkType_notShownWhenNotDefault() = + testScope.runTest { + repository.setNetworkTypeKey(connectionsRepository.GSM_KEY) + repository.dataConnectionState.value = DataConnectionState.Connected + connectionsRepository.mobileIsDefault.value = false + + var latest: Icon? = null + val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this) + + assertThat(latest).isNull() + + job.cancel() + } + + @Test + fun roaming() = + testScope.runTest { + repository.setAllRoaming(true) + + var latest: Boolean? = null + val job = underTest.roaming.onEach { latest = it }.launchIn(this) + + assertThat(latest).isTrue() + + repository.setAllRoaming(false) + + assertThat(latest).isFalse() + + job.cancel() + } + + @Test + fun dataActivity_nullWhenConfigIsOff() = + testScope.runTest { + // Create a new view model here so the constants are properly read + whenever(constants.shouldShowActivityConfig).thenReturn(false) + createAndSetViewModel() + + var inVisible: Boolean? = null + val inJob = underTest.activityInVisible.onEach { inVisible = it }.launchIn(this) + + var outVisible: Boolean? = null + val outJob = underTest.activityInVisible.onEach { outVisible = it }.launchIn(this) + + var containerVisible: Boolean? = null + val containerJob = + underTest.activityInVisible.onEach { containerVisible = it }.launchIn(this) + + repository.dataActivityDirection.value = + DataActivityModel(hasActivityIn = true, hasActivityOut = true) + + assertThat(inVisible).isFalse() + assertThat(outVisible).isFalse() + assertThat(containerVisible).isFalse() + + inJob.cancel() + outJob.cancel() + containerJob.cancel() + } + + @Test + @DisableFlags(FLAG_STATUS_BAR_STATIC_INOUT_INDICATORS) + fun dataActivity_configOn_testIndicators_staticFlagOff() = + testScope.runTest { + // Create a new view model here so the constants are properly read + whenever(constants.shouldShowActivityConfig).thenReturn(true) + createAndSetViewModel() + + var inVisible: Boolean? = null + val inJob = underTest.activityInVisible.onEach { inVisible = it }.launchIn(this) + + var outVisible: Boolean? = null + val outJob = underTest.activityOutVisible.onEach { outVisible = it }.launchIn(this) + + var containerVisible: Boolean? = null + val containerJob = + underTest.activityContainerVisible.onEach { containerVisible = it }.launchIn(this) + + repository.dataActivityDirection.value = + DataActivityModel(hasActivityIn = true, hasActivityOut = false) + + yield() + + assertThat(inVisible).isTrue() + assertThat(outVisible).isFalse() + assertThat(containerVisible).isTrue() + + repository.dataActivityDirection.value = + DataActivityModel(hasActivityIn = false, hasActivityOut = true) + + assertThat(inVisible).isFalse() + assertThat(outVisible).isTrue() + assertThat(containerVisible).isTrue() + + repository.dataActivityDirection.value = + DataActivityModel(hasActivityIn = false, hasActivityOut = false) + + assertThat(inVisible).isFalse() + assertThat(outVisible).isFalse() + assertThat(containerVisible).isFalse() + + inJob.cancel() + outJob.cancel() + containerJob.cancel() + } + + @Test + @EnableFlags(FLAG_STATUS_BAR_STATIC_INOUT_INDICATORS) + fun dataActivity_configOn_testIndicators_staticFlagOn() = + testScope.runTest { + // Create a new view model here so the constants are properly read + whenever(constants.shouldShowActivityConfig).thenReturn(true) + createAndSetViewModel() + + var inVisible: Boolean? = null + val inJob = underTest.activityInVisible.onEach { inVisible = it }.launchIn(this) + + var outVisible: Boolean? = null + val outJob = underTest.activityOutVisible.onEach { outVisible = it }.launchIn(this) + + var containerVisible: Boolean? = null + val containerJob = + underTest.activityContainerVisible.onEach { containerVisible = it }.launchIn(this) + + repository.dataActivityDirection.value = + DataActivityModel(hasActivityIn = true, hasActivityOut = false) + + yield() + + assertThat(inVisible).isTrue() + assertThat(outVisible).isFalse() + assertThat(containerVisible).isTrue() + + repository.dataActivityDirection.value = + DataActivityModel(hasActivityIn = false, hasActivityOut = true) + + assertThat(inVisible).isFalse() + assertThat(outVisible).isTrue() + assertThat(containerVisible).isTrue() + + repository.dataActivityDirection.value = + DataActivityModel(hasActivityIn = false, hasActivityOut = false) + + assertThat(inVisible).isFalse() + assertThat(outVisible).isFalse() + assertThat(containerVisible).isTrue() + + inJob.cancel() + outJob.cancel() + containerJob.cancel() + } + + @Test + fun netTypeBackground_nullWhenNoPrioritizedCapabilities() = + testScope.runTest { + createAndSetViewModel() + + val latest by collectLastValue(underTest.networkTypeBackground) + + repository.hasPrioritizedNetworkCapabilities.value = false + + assertThat(latest).isNull() + } + + @Test + @EnableFlags(NewStatusBarIcons.FLAG_NAME, StatusBarRootModernization.FLAG_NAME) + fun netTypeBackground_sliceUiEnabled_notNullWhenPrioritizedCapabilities_newIcons() = + testScope.runTest { + createAndSetViewModel() + + val latest by collectLastValue(underTest.networkTypeBackground) + + repository.hasPrioritizedNetworkCapabilities.value = true + + assertThat(latest) + .isEqualTo(Icon.Resource(R.drawable.mobile_network_type_background_updated, null)) + } + + @Test + @DisableFlags(NewStatusBarIcons.FLAG_NAME, StatusBarRootModernization.FLAG_NAME) + fun netTypeBackground_sliceUiDisabled_notNullWhenPrioritizedCapabilities_oldIcons() = + testScope.runTest { + createAndSetViewModel() + + val latest by collectLastValue(underTest.networkTypeBackground) + + repository.hasPrioritizedNetworkCapabilities.value = true + + assertThat(latest) + .isEqualTo(Icon.Resource(R.drawable.mobile_network_type_background, null)) + } + + @Test + fun nonTerrestrial_defaultProperties() = + testScope.runTest { + repository.isNonTerrestrial.value = true + + val roaming by collectLastValue(underTest.roaming) + val networkTypeIcon by collectLastValue(underTest.networkTypeIcon) + val networkTypeBackground by collectLastValue(underTest.networkTypeBackground) + val activityInVisible by collectLastValue(underTest.activityInVisible) + val activityOutVisible by collectLastValue(underTest.activityOutVisible) + val activityContainerVisible by collectLastValue(underTest.activityContainerVisible) + + assertThat(roaming).isFalse() + assertThat(networkTypeIcon).isNull() + assertThat(networkTypeBackground).isNull() + assertThat(activityInVisible).isFalse() + assertThat(activityOutVisible).isFalse() + assertThat(activityContainerVisible).isFalse() + } + + @Test + fun nonTerrestrial_ignoresDefaultProperties() = + testScope.runTest { + repository.isNonTerrestrial.value = true + + val roaming by collectLastValue(underTest.roaming) + val networkTypeIcon by collectLastValue(underTest.networkTypeIcon) + val networkTypeBackground by collectLastValue(underTest.networkTypeBackground) + val activityInVisible by collectLastValue(underTest.activityInVisible) + val activityOutVisible by collectLastValue(underTest.activityOutVisible) + val activityContainerVisible by collectLastValue(underTest.activityContainerVisible) + + repository.setAllRoaming(true) + repository.setNetworkTypeKey(connectionsRepository.LTE_KEY) + // sets the background on cellular + repository.hasPrioritizedNetworkCapabilities.value = true + repository.dataActivityDirection.value = + DataActivityModel(hasActivityIn = true, hasActivityOut = true) + + assertThat(roaming).isFalse() + assertThat(networkTypeIcon).isNull() + assertThat(networkTypeBackground).isNull() + assertThat(activityInVisible).isFalse() + assertThat(activityOutVisible).isFalse() + assertThat(activityContainerVisible).isFalse() + } + + @DisableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN) + @Test + fun nonTerrestrial_usesSatelliteIcon_flagOff() = + testScope.runTest { + repository.isNonTerrestrial.value = true + repository.setAllLevels(0) + repository.satelliteLevel.value = 0 + + val latest by + collectLastValue(underTest.icon.filterIsInstance(SignalIconModel.Satellite::class)) + + // Level 0 -> no connection + assertThat(latest).isNotNull() + assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_0) + + // 1-2 -> 1 bar + repository.setAllLevels(1) + repository.satelliteLevel.value = 1 + assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_1) + + repository.setAllLevels(2) + repository.satelliteLevel.value = 2 + assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_1) + + // 3-4 -> 2 bars + repository.setAllLevels(3) + repository.satelliteLevel.value = 3 + assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2) + + repository.setAllLevels(4) + repository.satelliteLevel.value = 4 + assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2) + } + + @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN) + @Test + fun nonTerrestrial_usesSatelliteIcon_flagOn() = + testScope.runTest { + repository.isNonTerrestrial.value = true + repository.satelliteLevel.value = 0 + + val latest by + collectLastValue(underTest.icon.filterIsInstance(SignalIconModel.Satellite::class)) + + // Level 0 -> no connection + assertThat(latest).isNotNull() + assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_0) + + // 1-2 -> 1 bar + repository.satelliteLevel.value = 1 + assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_1) + + repository.satelliteLevel.value = 2 + assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_1) + + // 3-4 -> 2 bars + repository.satelliteLevel.value = 3 + assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2) + + repository.satelliteLevel.value = 4 + assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2) + } + + @DisableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN) + @Test + fun satelliteIcon_ignoresInflateSignalStrength_flagOff() = + testScope.runTest { + // Note that this is the exact same test as above, but with inflateSignalStrength set to + // true we note that the level is unaffected by inflation + repository.inflateSignalStrength.value = true + repository.isNonTerrestrial.value = true + repository.setAllLevels(0) + repository.satelliteLevel.value = 0 + + val latest by + collectLastValue(underTest.icon.filterIsInstance(SignalIconModel.Satellite::class)) + + // Level 0 -> no connection + assertThat(latest).isNotNull() + assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_0) + + // 1-2 -> 1 bar + repository.setAllLevels(1) + repository.satelliteLevel.value = 1 + assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_1) + + repository.setAllLevels(2) + repository.satelliteLevel.value = 2 + assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_1) + + // 3-4 -> 2 bars + repository.setAllLevels(3) + repository.satelliteLevel.value = 3 + assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2) + + repository.setAllLevels(4) + repository.satelliteLevel.value = 4 + assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2) + } + + @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN) + @Test + fun satelliteIcon_ignoresInflateSignalStrength_flagOn() = + testScope.runTest { + // Note that this is the exact same test as above, but with inflateSignalStrength set to + // true we note that the level is unaffected by inflation + repository.inflateSignalStrength.value = true + repository.isNonTerrestrial.value = true + repository.satelliteLevel.value = 0 + + val latest by + collectLastValue(underTest.icon.filterIsInstance(SignalIconModel.Satellite::class)) + + // Level 0 -> no connection + assertThat(latest).isNotNull() + assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_0) + + // 1-2 -> 1 bar + repository.satelliteLevel.value = 1 + assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_1) + + repository.satelliteLevel.value = 2 + assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_1) + + // 3-4 -> 2 bars + repository.satelliteLevel.value = 3 + assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2) + + repository.satelliteLevel.value = 4 + assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2) + } + + private fun createAndSetViewModel() { + underTest = + MobileIconViewModelKairos( + SUB_1_ID, + interactor, + airplaneModeInteractor, + constants, + testScope.backgroundScope, + ) + } + + companion object { + private const val SUB_1_ID = 1 + + // For convenience, just define these as constants + private val NO_SIGNAL = R.string.accessibility_no_signal + private val ONE_BAR = R.string.accessibility_one_bar + private val TWO_BARS = R.string.accessibility_two_bars + private val THREE_BARS = R.string.accessibility_three_bars + private val FOUR_BARS = R.string.accessibility_four_bars + private val FULL_BARS = R.string.accessibility_signal_full + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelKairosTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelKairosTest.kt new file mode 100644 index 000000000000..e921430394c2 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelKairosTest.kt @@ -0,0 +1,385 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel + +import android.telephony.SubscriptionManager.PROFILE_CLASS_UNSET +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.settingslib.mobile.TelephonyIcons +import com.android.systemui.SysuiTestCase +import com.android.systemui.flags.FakeFeatureFlagsClassic +import com.android.systemui.statusbar.phone.StatusBarLocation +import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository +import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor +import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel +import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository +import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor +import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel +import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger +import com.android.systemui.statusbar.pipeline.mobile.ui.VerboseMobileViewLogger +import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy +import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants +import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository +import com.android.systemui.testKosmos +import com.android.systemui.util.mockito.mock +import com.google.common.truth.Truth.assertThat +import junit.framework.Assert.assertFalse +import junit.framework.Assert.assertTrue +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.isActive +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.MockitoAnnotations + +@Suppress("EXPERIMENTAL_IS_NOT_ENABLED") +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +class MobileIconsViewModelKairosTest : SysuiTestCase() { + private val kosmos = testKosmos() + + private lateinit var underTest: MobileIconsViewModelKairos + private val interactor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock()) + private val flags = FakeFeatureFlagsClassic() + + private lateinit var airplaneModeInteractor: AirplaneModeInteractor + @Mock private lateinit var constants: ConnectivityConstants + @Mock private lateinit var logger: MobileViewLogger + @Mock private lateinit var verboseLogger: VerboseMobileViewLogger + + private val testDispatcher = UnconfinedTestDispatcher() + private val testScope = TestScope(testDispatcher) + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + airplaneModeInteractor = + AirplaneModeInteractor( + FakeAirplaneModeRepository(), + FakeConnectivityRepository(), + kosmos.fakeMobileConnectionsRepository, + ) + + underTest = + MobileIconsViewModelKairos( + logger, + verboseLogger, + interactor, + airplaneModeInteractor, + constants, + testScope.backgroundScope, + ) + + interactor.filteredSubscriptions.value = listOf(SUB_1, SUB_2) + } + + @Test + fun subscriptionIdsFlow_matchesInteractor() = + testScope.runTest { + var latest: List<Int>? = null + val job = underTest.subscriptionIdsFlow.onEach { latest = it }.launchIn(this) + + interactor.filteredSubscriptions.value = + listOf( + SubscriptionModel( + subscriptionId = 1, + isOpportunistic = false, + carrierName = "Carrier 1", + profileClass = PROFILE_CLASS_UNSET, + ) + ) + assertThat(latest).isEqualTo(listOf(1)) + + interactor.filteredSubscriptions.value = + listOf( + SubscriptionModel( + subscriptionId = 2, + isOpportunistic = false, + carrierName = "Carrier 2", + profileClass = PROFILE_CLASS_UNSET, + ), + SubscriptionModel( + subscriptionId = 5, + isOpportunistic = true, + carrierName = "Carrier 5", + profileClass = PROFILE_CLASS_UNSET, + ), + SubscriptionModel( + subscriptionId = 7, + isOpportunistic = true, + carrierName = "Carrier 7", + profileClass = PROFILE_CLASS_UNSET, + ), + ) + assertThat(latest).isEqualTo(listOf(2, 5, 7)) + + interactor.filteredSubscriptions.value = emptyList() + assertThat(latest).isEmpty() + + job.cancel() + } + + @Test + fun caching_mobileIconViewModelIsReusedForSameSubId() = + testScope.runTest { + val model1 = underTest.viewModelForSub(1, StatusBarLocation.HOME) + val model2 = underTest.viewModelForSub(1, StatusBarLocation.QS) + + assertThat(model1.commonImpl).isSameInstanceAs(model2.commonImpl) + } + + @Test + fun caching_invalidViewModelsAreRemovedFromCacheWhenSubDisappears() = + testScope.runTest { + // Retrieve models to trigger caching + val model1 = underTest.viewModelForSub(1, StatusBarLocation.HOME) + val model2 = underTest.viewModelForSub(2, StatusBarLocation.QS) + + // Both impls are cached + assertThat(underTest.reuseCache.keys).containsExactly(1, 2) + + // SUB_1 is removed from the list... + interactor.filteredSubscriptions.value = listOf(SUB_2) + + // ... and dropped from the cache + assertThat(underTest.reuseCache.keys).containsExactly(2) + } + + @Test + fun caching_invalidatedViewModelsAreCanceled() = + testScope.runTest { + // Retrieve models to trigger caching + val model1 = underTest.viewModelForSub(1, StatusBarLocation.HOME) + val model2 = underTest.viewModelForSub(2, StatusBarLocation.QS) + + var scope1 = underTest.reuseCache[1]?.second + var scope2 = underTest.reuseCache[2]?.second + + // Scopes are not canceled + assertTrue(scope1!!.isActive) + assertTrue(scope2!!.isActive) + + // SUB_1 is removed from the list... + interactor.filteredSubscriptions.value = listOf(SUB_2) + + // scope1 is canceled + assertFalse(scope1!!.isActive) + assertTrue(scope2!!.isActive) + } + + @Test + fun firstMobileSubShowingNetworkTypeIcon_noSubs_false() = + testScope.runTest { + var latest: Boolean? = null + val job = + underTest.firstMobileSubShowingNetworkTypeIcon.onEach { latest = it }.launchIn(this) + + interactor.filteredSubscriptions.value = emptyList() + + assertThat(latest).isFalse() + + job.cancel() + } + + @Test + fun firstMobileSubShowingNetworkTypeIcon_oneSub_notShowingRat_false() = + testScope.runTest { + var latest: Boolean? = null + val job = + underTest.firstMobileSubShowingNetworkTypeIcon.onEach { latest = it }.launchIn(this) + + interactor.filteredSubscriptions.value = listOf(SUB_1) + // The unknown icon group doesn't show a RAT + interactor.getInteractorForSubId(1)!!.networkTypeIconGroup.value = + NetworkTypeIconModel.DefaultIcon(TelephonyIcons.UNKNOWN) + + assertThat(latest).isFalse() + + job.cancel() + } + + @Test + fun firstMobileSubShowingNetworkTypeIcon_oneSub_showingRat_true() = + testScope.runTest { + var latest: Boolean? = null + val job = + underTest.firstMobileSubShowingNetworkTypeIcon.onEach { latest = it }.launchIn(this) + + interactor.filteredSubscriptions.value = listOf(SUB_1) + // The 3G icon group will show a RAT + interactor.getInteractorForSubId(1)!!.networkTypeIconGroup.value = + NetworkTypeIconModel.DefaultIcon(TelephonyIcons.THREE_G) + + assertThat(latest).isTrue() + + job.cancel() + } + + @Test + fun firstMobileSubShowingNetworkTypeIcon_updatesAsSubUpdates() = + testScope.runTest { + var latest: Boolean? = null + val job = + underTest.firstMobileSubShowingNetworkTypeIcon.onEach { latest = it }.launchIn(this) + + interactor.filteredSubscriptions.value = listOf(SUB_1) + val sub1Interactor = interactor.getInteractorForSubId(1)!! + + sub1Interactor.networkTypeIconGroup.value = + NetworkTypeIconModel.DefaultIcon(TelephonyIcons.THREE_G) + assertThat(latest).isTrue() + + sub1Interactor.networkTypeIconGroup.value = + NetworkTypeIconModel.DefaultIcon(TelephonyIcons.UNKNOWN) + assertThat(latest).isFalse() + + sub1Interactor.networkTypeIconGroup.value = + NetworkTypeIconModel.DefaultIcon(TelephonyIcons.LTE) + assertThat(latest).isTrue() + + job.cancel() + } + + @Test + fun firstMobileSubShowingNetworkTypeIcon_multipleSubs_lastSubNotShowingRat_false() = + testScope.runTest { + var latest: Boolean? = null + val job = + underTest.firstMobileSubShowingNetworkTypeIcon.onEach { latest = it }.launchIn(this) + + interactor.filteredSubscriptions.value = listOf(SUB_1, SUB_2) + interactor.getInteractorForSubId(1)?.networkTypeIconGroup?.value = + NetworkTypeIconModel.DefaultIcon(TelephonyIcons.THREE_G) + interactor.getInteractorForSubId(2)!!.networkTypeIconGroup.value = + NetworkTypeIconModel.DefaultIcon(TelephonyIcons.UNKNOWN) + + assertThat(latest).isFalse() + + job.cancel() + } + + @Test + fun firstMobileSubShowingNetworkTypeIcon_multipleSubs_lastSubShowingRat_true() = + testScope.runTest { + var latest: Boolean? = null + val job = + underTest.firstMobileSubShowingNetworkTypeIcon.onEach { latest = it }.launchIn(this) + + interactor.filteredSubscriptions.value = listOf(SUB_1, SUB_2) + interactor.getInteractorForSubId(1)?.networkTypeIconGroup?.value = + NetworkTypeIconModel.DefaultIcon(TelephonyIcons.UNKNOWN) + interactor.getInteractorForSubId(2)!!.networkTypeIconGroup.value = + NetworkTypeIconModel.DefaultIcon(TelephonyIcons.THREE_G) + + assertThat(latest).isTrue() + job.cancel() + } + + @Test + fun firstMobileSubShowingNetworkTypeIcon_subListUpdates_valAlsoUpdates() = + testScope.runTest { + var latest: Boolean? = null + val job = + underTest.firstMobileSubShowingNetworkTypeIcon.onEach { latest = it }.launchIn(this) + + interactor.filteredSubscriptions.value = listOf(SUB_1, SUB_2) + interactor.getInteractorForSubId(1)?.networkTypeIconGroup?.value = + NetworkTypeIconModel.DefaultIcon(TelephonyIcons.UNKNOWN) + interactor.getInteractorForSubId(2)!!.networkTypeIconGroup.value = + NetworkTypeIconModel.DefaultIcon(TelephonyIcons.THREE_G) + + assertThat(latest).isTrue() + + // WHEN the sub list gets new subscriptions where the last subscription is not showing + // the network type icon + interactor.filteredSubscriptions.value = listOf(SUB_1, SUB_2, SUB_3) + interactor.getInteractorForSubId(3)!!.networkTypeIconGroup.value = + NetworkTypeIconModel.DefaultIcon(TelephonyIcons.UNKNOWN) + + // THEN the flow updates + assertThat(latest).isFalse() + + job.cancel() + } + + @Test + fun firstMobileSubShowingNetworkTypeIcon_subListReorders_valAlsoUpdates() = + testScope.runTest { + var latest: Boolean? = null + val job = + underTest.firstMobileSubShowingNetworkTypeIcon.onEach { latest = it }.launchIn(this) + + interactor.filteredSubscriptions.value = listOf(SUB_1, SUB_2) + // Immediately switch the order so that we've created both interactors + interactor.filteredSubscriptions.value = listOf(SUB_2, SUB_1) + val sub1Interactor = interactor.getInteractorForSubId(1)!! + val sub2Interactor = interactor.getInteractorForSubId(2)!! + + interactor.filteredSubscriptions.value = listOf(SUB_1, SUB_2) + sub1Interactor.networkTypeIconGroup.value = + NetworkTypeIconModel.DefaultIcon(TelephonyIcons.UNKNOWN) + sub2Interactor.networkTypeIconGroup.value = + NetworkTypeIconModel.DefaultIcon(TelephonyIcons.THREE_G) + assertThat(latest).isTrue() + + // WHEN sub1 becomes last and sub1 has no network type icon + interactor.filteredSubscriptions.value = listOf(SUB_2, SUB_1) + + // THEN the flow updates + assertThat(latest).isFalse() + + // WHEN sub2 becomes last and sub2 has a network type icon + interactor.filteredSubscriptions.value = listOf(SUB_1, SUB_2) + + // THEN the flow updates + assertThat(latest).isTrue() + + job.cancel() + } + + companion object { + private val SUB_1 = + SubscriptionModel( + subscriptionId = 1, + isOpportunistic = false, + carrierName = "Carrier 1", + profileClass = PROFILE_CLASS_UNSET, + ) + private val SUB_2 = + SubscriptionModel( + subscriptionId = 2, + isOpportunistic = false, + carrierName = "Carrier 2", + profileClass = PROFILE_CLASS_UNSET, + ) + private val SUB_3 = + SubscriptionModel( + subscriptionId = 3, + isOpportunistic = false, + carrierName = "Carrier 3", + profileClass = PROFILE_CLASS_UNSET, + ) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelKairosTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelKairosTest.kt new file mode 100644 index 000000000000..ce35d9d8610f --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelKairosTest.kt @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel + +import android.platform.test.annotations.EnableFlags +import android.telephony.SubscriptionManager.PROFILE_CLASS_UNSET +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.kosmos.runTest +import com.android.systemui.kosmos.testScope +import com.android.systemui.kosmos.useUnconfinedTestDispatcher +import com.android.systemui.lifecycle.activateIn +import com.android.systemui.statusbar.core.NewStatusBarIcons +import com.android.systemui.statusbar.core.StatusBarRootModernization +import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel +import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.fakeMobileIconsInteractor +import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class StackedMobileIconViewModelKairosTest : SysuiTestCase() { + private val kosmos = testKosmos().useUnconfinedTestDispatcher() + private val testScope = kosmos.testScope + + private val Kosmos.underTest: StackedMobileIconViewModelKairos by Fixture { + stackedMobileIconViewModelKairos + } + + @Before + fun setUp() { + kosmos.underTest.activateIn(testScope) + } + + @Test + @EnableFlags(NewStatusBarIcons.FLAG_NAME, StatusBarRootModernization.FLAG_NAME) + fun dualSim_filtersOutNonDualConnections() = + kosmos.runTest { + fakeMobileIconsInteractor.filteredSubscriptions.value = listOf() + assertThat(underTest.dualSim).isNull() + + fakeMobileIconsInteractor.filteredSubscriptions.value = listOf(SUB_1) + assertThat(underTest.dualSim).isNull() + + fakeMobileIconsInteractor.filteredSubscriptions.value = listOf(SUB_1, SUB_2, SUB_3) + assertThat(underTest.dualSim).isNull() + + fakeMobileIconsInteractor.filteredSubscriptions.value = listOf(SUB_1, SUB_2) + assertThat(underTest.dualSim).isNotNull() + } + + @Test + @EnableFlags(NewStatusBarIcons.FLAG_NAME, StatusBarRootModernization.FLAG_NAME) + fun dualSim_filtersOutNonCellularIcons() = + kosmos.runTest { + fakeMobileIconsInteractor.filteredSubscriptions.value = listOf(SUB_1) + assertThat(underTest.dualSim).isNull() + + fakeMobileIconsInteractor + .getInteractorForSubId(SUB_1.subscriptionId)!! + .signalLevelIcon + .value = + SignalIconModel.Satellite( + level = 0, + icon = Icon.Resource(res = 0, contentDescription = null), + ) + fakeMobileIconsInteractor.filteredSubscriptions.value = listOf(SUB_1, SUB_2) + assertThat(underTest.dualSim).isNull() + } + + @Test + @EnableFlags(NewStatusBarIcons.FLAG_NAME, StatusBarRootModernization.FLAG_NAME) + fun dualSim_tracksActiveSubId() = + kosmos.runTest { + // Active sub id is null, order is unchanged + fakeMobileIconsInteractor.filteredSubscriptions.value = listOf(SUB_1, SUB_2) + setIconLevel(SUB_1.subscriptionId, 1) + setIconLevel(SUB_2.subscriptionId, 2) + + assertThat(underTest.dualSim!!.primary.level).isEqualTo(1) + assertThat(underTest.dualSim!!.secondary.level).isEqualTo(2) + + // Active sub is 2, order is swapped + fakeMobileIconsInteractor.activeMobileDataSubscriptionId.value = SUB_2.subscriptionId + + assertThat(underTest.dualSim!!.primary.level).isEqualTo(2) + assertThat(underTest.dualSim!!.secondary.level).isEqualTo(1) + } + + private fun setIconLevel(subId: Int, level: Int) { + with(kosmos.fakeMobileIconsInteractor.getInteractorForSubId(subId)!!) { + signalLevelIcon.value = + (signalLevelIcon.value as SignalIconModel.Cellular).copy(level = level) + } + } + + companion object { + private val SUB_1 = + SubscriptionModel( + subscriptionId = 1, + isOpportunistic = false, + carrierName = "Carrier 1", + profileClass = PROFILE_CLASS_UNSET, + ) + private val SUB_2 = + SubscriptionModel( + subscriptionId = 2, + isOpportunistic = false, + carrierName = "Carrier 2", + profileClass = PROFILE_CLASS_UNSET, + ) + private val SUB_3 = + SubscriptionModel( + subscriptionId = 3, + isOpportunistic = false, + carrierName = "Carrier 3", + profileClass = PROFILE_CLASS_UNSET, + ) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/topwindoweffects/TopLevelWindowEffectsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/topwindoweffects/TopLevelWindowEffectsTest.kt new file mode 100644 index 000000000000..6d3813c90bfd --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/topwindoweffects/TopLevelWindowEffectsTest.kt @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.topwindoweffects + +import android.view.View +import android.view.WindowManager +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.app.viewcapture.ViewCapture +import com.android.app.viewcapture.ViewCaptureAwareWindowManager +import com.android.systemui.SysuiTestCase +import com.android.systemui.keyevent.data.repository.fakeKeyEventRepository +import com.android.systemui.keyevent.data.repository.keyEventRepository +import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor +import com.android.systemui.keyevent.domain.interactor.keyEventInteractor +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.runTest +import com.android.systemui.kosmos.testScope +import com.android.systemui.kosmos.useUnconfinedTestDispatcher +import com.android.systemui.testKosmos +import com.android.systemui.topwindoweffects.data.repository.fakeSqueezeEffectRepository +import com.android.systemui.topwindoweffects.domain.interactor.SqueezeEffectInteractor +import com.android.systemui.topwindoweffects.ui.compose.EffectsWindowRoot +import com.android.systemui.topwindoweffects.ui.viewmodel.SqueezeEffectViewModel +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.MockitoAnnotations +import org.mockito.kotlin.any +import org.mockito.kotlin.doNothing +import org.mockito.kotlin.never +import org.mockito.kotlin.times +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever + +@SmallTest +@RunWith(AndroidJUnit4::class) +class TopLevelWindowEffectsTest : SysuiTestCase() { + + private val kosmos = testKosmos().useUnconfinedTestDispatcher() + + @Mock + private lateinit var windowManager: WindowManager + + @Mock + private lateinit var viewCapture: Lazy<ViewCapture> + + @Mock + private lateinit var viewModelFactory: SqueezeEffectViewModel.Factory + + private val Kosmos.underTest by Kosmos.Fixture { + TopLevelWindowEffects( + context = mContext, + applicationScope = testScope.backgroundScope, + windowManager = ViewCaptureAwareWindowManager( + windowManager = windowManager, + lazyViewCapture = viewCapture, + isViewCaptureEnabled = false + ), + keyEventInteractor = keyEventInteractor, + viewModelFactory = viewModelFactory, + squeezeEffectInteractor = SqueezeEffectInteractor( + squeezeEffectRepository = fakeSqueezeEffectRepository + ) + ) + } + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + + doNothing().whenever(windowManager).addView(any<View>(), any<WindowManager.LayoutParams>()) + doNothing().whenever(windowManager).removeView(any<View>()) + doNothing().whenever(windowManager).removeView(any<EffectsWindowRoot>()) + } + + @Test + fun noWindowWhenSqueezeEffectDisabled() = + kosmos.runTest { + fakeSqueezeEffectRepository.isSqueezeEffectEnabled.value = false + + underTest.start() + + verify(windowManager, never()).addView(any<View>(), any<WindowManager.LayoutParams>()) + } + + @Test + fun addViewToWindowWhenSqueezeEffectEnabled() = + kosmos.runTest { + fakeSqueezeEffectRepository.isSqueezeEffectEnabled.value = true + fakeKeyEventRepository.setPowerButtonDown(true) + + underTest.start() + + verify(windowManager, times(1)).addView(any<View>(), + any<WindowManager.LayoutParams>()) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/topwindoweffects/data/repository/SqueezeEffectRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/topwindoweffects/data/repository/SqueezeEffectRepositoryTest.kt new file mode 100644 index 000000000000..9b01fd3242e5 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/topwindoweffects/data/repository/SqueezeEffectRepositoryTest.kt @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.topwindoweffects.data.repository + +import android.os.Handler +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags +import android.provider.Settings.Global.POWER_BUTTON_LONG_PRESS +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.collectLastValue +import com.android.systemui.kosmos.runTest +import com.android.systemui.kosmos.testScope +import com.android.systemui.kosmos.useUnconfinedTestDispatcher +import com.android.systemui.shared.Flags +import com.android.systemui.testKosmos +import com.android.systemui.util.settings.FakeGlobalSettings +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.StandardTestDispatcher +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidJUnit4::class) +class SqueezeEffectRepositoryTest : SysuiTestCase() { + + private val kosmos = testKosmos().useUnconfinedTestDispatcher() + private val globalSettings = FakeGlobalSettings(StandardTestDispatcher()) + + @Mock + private lateinit var bgHandler: Handler + + private val Kosmos.underTest by Kosmos.Fixture { + SqueezeEffectRepositoryImpl( + bgHandler = bgHandler, + bgCoroutineContext = testScope.testScheduler, + globalSettings = globalSettings + ) + } + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + } + + @DisableFlags(Flags.FLAG_ENABLE_LPP_SQUEEZE_EFFECT) + @Test + fun testSqueezeEffectDisabled_WhenFlagDisabled() = + kosmos.runTest { + val isSqueezeEffectEnabled by collectLastValue(underTest.isSqueezeEffectEnabled) + + assertThat(isSqueezeEffectEnabled).isFalse() + } + + @EnableFlags(Flags.FLAG_ENABLE_LPP_SQUEEZE_EFFECT) + @Test + fun testSqueezeEffectDisabled_WhenFlagEnabled_GlobalSettingsDisabled() = + kosmos.runTest { + globalSettings.putInt(POWER_BUTTON_LONG_PRESS, 0) + + val isSqueezeEffectEnabled by collectLastValue(underTest.isSqueezeEffectEnabled) + + assertThat(isSqueezeEffectEnabled).isFalse() + } + + @EnableFlags(Flags.FLAG_ENABLE_LPP_SQUEEZE_EFFECT) + @Test + fun testSqueezeEffectEnabled_WhenFlagEnabled_GlobalSettingEnabled() = + kosmos.runTest { + globalSettings.putInt(POWER_BUTTON_LONG_PRESS, 5) + + val isSqueezeEffectEnabled by collectLastValue(underTest.isSqueezeEffectEnabled) + + assertThat(isSqueezeEffectEnabled).isTrue() + } +}
\ No newline at end of file diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/topwindoweffects/domain/interactor/SqueezeEffectInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/topwindoweffects/domain/interactor/SqueezeEffectInteractorTest.kt new file mode 100644 index 000000000000..a94d49c5d40e --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/topwindoweffects/domain/interactor/SqueezeEffectInteractorTest.kt @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.topwindoweffects.domain.interactor + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.collectLastValue +import com.android.systemui.kosmos.runTest +import com.android.systemui.kosmos.useUnconfinedTestDispatcher +import com.android.systemui.testKosmos +import com.android.systemui.topwindoweffects.data.repository.fakeSqueezeEffectRepository +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class SqueezeEffectInteractorTest : SysuiTestCase() { + + private val kosmos = testKosmos().useUnconfinedTestDispatcher() + + private val Kosmos.underTest by Kosmos.Fixture { + SqueezeEffectInteractor( + squeezeEffectRepository = fakeSqueezeEffectRepository + ) + } + + @Test + fun testIsSqueezeEffectDisabled_whenDisabledInRepository() = + kosmos.runTest { + fakeSqueezeEffectRepository.isSqueezeEffectEnabled.value = false + + val isSqueezeEffectEnabled by collectLastValue(underTest.isSqueezeEffectEnabled) + + assertThat(isSqueezeEffectEnabled).isFalse() + } + + @Test + fun testIsSqueezeEffectEnabled_whenEnabledInRepository() = + kosmos.runTest { + fakeSqueezeEffectRepository.isSqueezeEffectEnabled.value = true + + val isSqueezeEffectEnabled by collectLastValue(underTest.isSqueezeEffectEnabled) + + assertThat(isSqueezeEffectEnabled).isTrue() + } +}
\ No newline at end of file diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/wakelock/ClientTrackingWakeLockTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/wakelock/ClientTrackingWakeLockTest.kt index fdfcdc486c02..9a58365116fd 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/wakelock/ClientTrackingWakeLockTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/wakelock/ClientTrackingWakeLockTest.kt @@ -16,14 +16,13 @@ package com.android.systemui.util.wakelock -import android.os.Build import android.os.PowerManager import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.log.assertLogsWtf import org.junit.After import org.junit.Assert -import org.junit.Assume import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -82,12 +81,11 @@ class ClientTrackingWakeLockTest : SysuiTestCase() { @Test fun wakeLock_releasedTooManyTimes_stillReleased_noThrow() { - Assume.assumeFalse(Build.IS_ENG) mWakeLock.acquire(WHY) mWakeLock.acquire(WHY_2) mWakeLock.release(WHY) mWakeLock.release(WHY_2) - mWakeLock.release(WHY) + assertLogsWtf { mWakeLock.release(WHY) } Assert.assertFalse(mInner.isHeld) } @@ -104,9 +102,8 @@ class ClientTrackingWakeLockTest : SysuiTestCase() { @Test fun prodBuild_wakeLock_releaseWithoutAcquire_noThrow() { - Assume.assumeFalse(Build.IS_ENG) // shouldn't throw an exception on production builds - mWakeLock.release(WHY) + assertLogsWtf { mWakeLock.release(WHY) } } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/wakelock/WakeLockTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/wakelock/WakeLockTest.java index 90aecfb62e91..3951670c4125 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/wakelock/WakeLockTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/wakelock/WakeLockTest.java @@ -19,16 +19,15 @@ package com.android.systemui.util.wakelock; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import android.os.Build; import android.os.PowerManager; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; +import com.android.systemui.log.LogAssertKt; import org.junit.After; -import org.junit.Assume; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -93,8 +92,7 @@ public class WakeLockTest extends SysuiTestCase { @Test public void prodBuild_wakeLock_releaseWithoutAcquire_noThrow() { - Assume.assumeFalse(Build.IS_ENG); // shouldn't throw an exception on production builds - mWakeLock.release(WHY); + LogAssertKt.assertRunnableLogsWtf(() -> mWakeLock.release(WHY)); } } diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockController.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockController.kt index b52db83d513c..7657a2179d4f 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockController.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockController.kt @@ -13,7 +13,6 @@ */ package com.android.systemui.plugins.clocks -import android.graphics.RectF import com.android.systemui.plugins.annotations.ProtectedInterface import com.android.systemui.plugins.annotations.SimpleProperty import java.io.PrintWriter @@ -50,5 +49,5 @@ interface ClockController { } interface ClockEventListener { - fun onBoundsChanged(bounds: RectF) + fun onBoundsChanged(bounds: VRectF) } diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockFaceEvents.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockFaceEvents.kt index 029e54658f60..a658c15a1a99 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockFaceEvents.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockFaceEvents.kt @@ -13,6 +13,7 @@ */ package com.android.systemui.plugins.clocks +import android.content.Context import android.graphics.Rect import com.android.systemui.plugins.annotations.ProtectedInterface @@ -44,6 +45,7 @@ interface ClockFaceEvents { * render within the centered targetRect to avoid obstructing other elements. The specified * targetRegion is relative to the parent view. */ + @Deprecated("No longer necessary, pending removal") fun onTargetRegionChanged(targetRegion: Rect?) /** Called to notify the clock about its display. */ @@ -60,4 +62,12 @@ data class ThemeConfig( * value denotes that we should use the seed color for the current system theme. */ val seedColor: Int?, -) +) { + fun getDefaultColor(context: Context): Int { + return when { + seedColor != null -> seedColor!! + isDarkTheme -> context.resources.getColor(android.R.color.system_accent1_100) + else -> context.resources.getColor(android.R.color.system_accent2_600) + } + } +} diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockLogger.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockLogger.kt index 02a3902a042c..f9ff75d5fdc8 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockLogger.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockLogger.kt @@ -16,7 +16,6 @@ package com.android.systemui.plugins.clocks -import android.graphics.Rect import android.view.View import android.view.View.MeasureSpec import com.android.systemui.log.core.LogLevel @@ -56,12 +55,9 @@ class ClockLogger(private val view: View?, buffer: MessageBuffer, tag: String) : } fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { - d({ "onLayout($bool1, ${Rect(int1, int2, long1.toInt(), long2.toInt())})" }) { + d({ "onLayout($bool1, ${VRect(long1.toULong())})" }) { bool1 = changed - int1 = left - int2 = top - long1 = right.toLong() - long2 = bottom.toLong() + long1 = VRect(left, top, right, bottom).data.toLong() } } @@ -108,8 +104,11 @@ class ClockLogger(private val view: View?, buffer: MessageBuffer, tag: String) : } } - fun animateDoze() { - d("animateDoze()") + fun animateDoze(isDozing: Boolean, isAnimated: Boolean) { + d({ "animateDoze(isDozing=$bool1, isAnimated=$bool2)" }) { + bool1 = isDozing + bool2 = isAnimated + } } fun animateCharge() { @@ -117,10 +116,7 @@ class ClockLogger(private val view: View?, buffer: MessageBuffer, tag: String) : } fun animateFidget(x: Float, y: Float) { - d({ "animateFidget($str1, $str2)" }) { - str1 = x.toString() - str2 = y.toString() - } + d({ "animateFidget(${VPointF(long1.toULong())})" }) { long1 = VPointF(x, y).data.toLong() } } companion object { diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/VPoint.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/VPoint.kt index 3dae5305542b..1fb37ec28835 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/VPoint.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/VPoint.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.shared.clocks +package com.android.systemui.plugins.clocks import android.graphics.Point import android.graphics.PointF @@ -30,22 +30,20 @@ private val Y_MASK: ULong = 0x00000000FFFFFFFFU private fun unpackX(data: ULong): Int = ((data and X_MASK) shr 32).toInt() -private fun unpackY(data: ULong): Int = (data and Y_MASK).toInt() +private fun unpackY(data: ULong): Int = ((data and Y_MASK) shr 0).toInt() private fun pack(x: Int, y: Int): ULong { - return ((x.toULong() shl 32) and X_MASK) or (y.toULong() and Y_MASK) + return ((x.toULong() shl 32) and X_MASK) or ((y.toULong() shl 0) and Y_MASK) } @JvmInline -value class VPointF(private val data: ULong) { +value class VPointF(val data: ULong) { val x: Float get() = Float.fromBits(unpackX(data)) val y: Float get() = Float.fromBits(unpackY(data)) - constructor() : this(0f, 0f) - constructor(pt: PointF) : this(pt.x, pt.y) constructor(x: Int, y: Int) : this(x.toFloat(), y.toFloat()) @@ -139,15 +137,13 @@ value class VPointF(private val data: ULong) { } @JvmInline -value class VPoint(private val data: ULong) { +value class VPoint(val data: ULong) { val x: Int get() = unpackX(data) val y: Int get() = unpackY(data) - constructor() : this(0, 0) - constructor(x: Int, y: Int) : this(pack(x, y)) fun toPoint() = Point(x, y) diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/VRect.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/VRect.kt new file mode 100644 index 000000000000..3c1adf22a405 --- /dev/null +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/VRect.kt @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.plugins.clocks + +import android.graphics.Rect +import android.graphics.RectF +import android.util.Half + +private val LEFT_MASK: ULong = 0xFFFF000000000000U +private val TOP_MASK: ULong = 0x0000FFFF00000000U +private val RIGHT_MASK: ULong = 0x00000000FFFF0000U +private val BOTTOM_MASK: ULong = 0x000000000000FFFFU + +private fun unpackLeft(data: ULong): Short = ((data and LEFT_MASK) shr 48).toShort() + +private fun unpackTop(data: ULong): Short = ((data and TOP_MASK) shr 32).toShort() + +private fun unpackRight(data: ULong): Short = ((data and RIGHT_MASK) shr 16).toShort() + +private fun unpackBottom(data: ULong): Short = ((data and BOTTOM_MASK) shr 0).toShort() + +private fun pack(left: Short, top: Short, right: Short, bottom: Short): ULong { + return ((left.toULong() shl 48) and LEFT_MASK) or + ((top.toULong() shl 32) and TOP_MASK) or + ((right.toULong() shl 16) and RIGHT_MASK) or + ((bottom.toULong() shl 0) and BOTTOM_MASK) +} + +@JvmInline +value class VRectF(val data: ULong) { + val left: Float + get() = fromBits(unpackLeft(data)) + + val top: Float + get() = fromBits(unpackTop(data)) + + val right: Float + get() = fromBits(unpackRight(data)) + + val bottom: Float + get() = fromBits(unpackBottom(data)) + + val width: Float + get() = right - left + + val height: Float + get() = bottom - top + + constructor(rect: RectF) : this(rect.left, rect.top, rect.right, rect.bottom) + + constructor( + rect: Rect + ) : this( + left = rect.left.toFloat(), + top = rect.top.toFloat(), + right = rect.right.toFloat(), + bottom = rect.bottom.toFloat(), + ) + + constructor( + left: Float, + top: Float, + right: Float, + bottom: Float, + ) : this(pack(toBits(left), toBits(top), toBits(right), toBits(bottom))) + + val center: VPointF + get() = VPointF(left, top) + size / 2f + + val size: VPointF + get() = VPointF(width, height) + + override fun toString() = "($left, $top) -> ($right, $bottom)" + + companion object { + private fun toBits(value: Float): Short = Half.halfToShortBits(Half.toHalf(value)) + + private fun fromBits(value: Short): Float = Half.toFloat(Half.intBitsToHalf(value.toInt())) + + fun fromCenter(center: VPointF, size: VPointF): VRectF { + return VRectF( + center.x - size.x / 2, + center.y - size.y / 2, + center.x + size.x / 2, + center.y + size.y / 2, + ) + } + + fun fromTopLeft(pos: VPointF, size: VPointF): VRectF { + return VRectF(pos.x, pos.y, pos.x + size.x, pos.y + size.y) + } + + val ZERO = VRectF(0f, 0f, 0f, 0f) + } +} + +@JvmInline +value class VRect(val data: ULong) { + val left: Int + get() = unpackLeft(data).toInt() + + val top: Int + get() = unpackTop(data).toInt() + + val right: Int + get() = unpackRight(data).toInt() + + val bottom: Int + get() = unpackBottom(data).toInt() + + val width: Int + get() = right - left + + val height: Int + get() = bottom - top + + constructor( + rect: Rect + ) : this( + left = rect.left.toShort(), + top = rect.top.toShort(), + right = rect.right.toShort(), + bottom = rect.bottom.toShort(), + ) + + constructor( + left: Int, + top: Int, + right: Int, + bottom: Int, + ) : this( + left = left.toShort(), + top = top.toShort(), + right = right.toShort(), + bottom = bottom.toShort(), + ) + + constructor( + left: Short, + top: Short, + right: Short, + bottom: Short, + ) : this(pack(left, top, right, bottom)) + + val center: VPoint + get() = VPoint(left, top) + size / 2 + + val size: VPoint + get() = VPoint(width, height) + + override fun toString() = "($left, $top) -> ($right, $bottom)" + + companion object { + val ZERO = VRect(0, 0, 0, 0) + + fun fromCenter(center: VPoint, size: VPoint): VRect { + return VRect( + (center.x - size.x / 2).toShort(), + (center.y - size.y / 2).toShort(), + (center.x + size.x / 2).toShort(), + (center.y + size.y / 2).toShort(), + ) + } + + fun fromTopLeft(pos: VPoint, size: VPoint): VRect { + return VRect( + pos.x.toShort(), + pos.y.toShort(), + (pos.x + size.x).toShort(), + (pos.y + size.y).toShort(), + ) + } + } +} diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/WeatherData.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/WeatherData.kt index f920b187e7e5..f59dda049aa1 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/WeatherData.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/WeatherData.kt @@ -24,6 +24,8 @@ data class WeatherData( @VisibleForTesting const val TEMPERATURE_KEY = "temperature" private const val INVALID_WEATHER_ICON_STATE = -1 + @JvmStatic + @JvmOverloads fun fromBundle(extras: Bundle, touchAction: WeatherTouchAction? = null): WeatherData? { val description = extras.getString(DESCRIPTION_KEY) val state = @@ -46,7 +48,7 @@ data class WeatherData( state = state, useCelsius = extras.getBoolean(USE_CELSIUS_KEY), temperature = temperature, - touchAction = touchAction + touchAction = touchAction, ) if (DEBUG) { Log.i(TAG, "Weather data parsed $result from $extras") @@ -87,53 +89,53 @@ data class WeatherData( } // Values for WeatherStateIcon must stay in sync with go/g3-WeatherStateIcon - enum class WeatherStateIcon(val id: Int) { - UNKNOWN_ICON(0), + enum class WeatherStateIcon(val id: Int, val icon: String) { + UNKNOWN_ICON(0, ""), // Clear, day & night. - SUNNY(1), - CLEAR_NIGHT(2), + SUNNY(1, "a"), + CLEAR_NIGHT(2, "f"), // Mostly clear, day & night. - MOSTLY_SUNNY(3), - MOSTLY_CLEAR_NIGHT(4), + MOSTLY_SUNNY(3, "b"), + MOSTLY_CLEAR_NIGHT(4, "n"), // Partly cloudy, day & night. - PARTLY_CLOUDY(5), - PARTLY_CLOUDY_NIGHT(6), + PARTLY_CLOUDY(5, "b"), + PARTLY_CLOUDY_NIGHT(6, "n"), // Mostly cloudy, day & night. - MOSTLY_CLOUDY_DAY(7), - MOSTLY_CLOUDY_NIGHT(8), - CLOUDY(9), - HAZE_FOG_DUST_SMOKE(10), - DRIZZLE(11), - HEAVY_RAIN(12), - SHOWERS_RAIN(13), + MOSTLY_CLOUDY_DAY(7, "e"), + MOSTLY_CLOUDY_NIGHT(8, "e"), + CLOUDY(9, "e"), + HAZE_FOG_DUST_SMOKE(10, "d"), + DRIZZLE(11, "c"), + HEAVY_RAIN(12, "c"), + SHOWERS_RAIN(13, "c"), // Scattered showers, day & night. - SCATTERED_SHOWERS_DAY(14), - SCATTERED_SHOWERS_NIGHT(15), + SCATTERED_SHOWERS_DAY(14, "c"), + SCATTERED_SHOWERS_NIGHT(15, "c"), // Isolated scattered thunderstorms, day & night. - ISOLATED_SCATTERED_TSTORMS_DAY(16), - ISOLATED_SCATTERED_TSTORMS_NIGHT(17), - STRONG_TSTORMS(18), - BLIZZARD(19), - BLOWING_SNOW(20), - FLURRIES(21), - HEAVY_SNOW(22), + ISOLATED_SCATTERED_TSTORMS_DAY(16, "i"), + ISOLATED_SCATTERED_TSTORMS_NIGHT(17, "i"), + STRONG_TSTORMS(18, "i"), + BLIZZARD(19, "j"), + BLOWING_SNOW(20, "j"), + FLURRIES(21, "h"), + HEAVY_SNOW(22, "j"), // Scattered snow showers, day & night. - SCATTERED_SNOW_SHOWERS_DAY(23), - SCATTERED_SNOW_SHOWERS_NIGHT(24), - SNOW_SHOWERS_SNOW(25), - MIXED_RAIN_HAIL_RAIN_SLEET(26), - SLEET_HAIL(27), - TORNADO(28), - TROPICAL_STORM_HURRICANE(29), - WINDY_BREEZY(30), - WINTRY_MIX_RAIN_SNOW(31); + SCATTERED_SNOW_SHOWERS_DAY(23, "h"), + SCATTERED_SNOW_SHOWERS_NIGHT(24, "h"), + SNOW_SHOWERS_SNOW(25, "g"), + MIXED_RAIN_HAIL_RAIN_SLEET(26, "h"), + SLEET_HAIL(27, "h"), + TORNADO(28, "l"), + TROPICAL_STORM_HURRICANE(29, "m"), + WINDY_BREEZY(30, "k"), + WINTRY_MIX_RAIN_SNOW(31, "h"); companion object { fun fromInt(value: Int) = values().firstOrNull { it.id == value } diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/TileDetailsViewModel.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/TileDetailsViewModel.kt index be0362fd7481..ac7a85742385 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/TileDetailsViewModel.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/TileDetailsViewModel.kt @@ -16,15 +16,13 @@ package com.android.systemui.plugins.qs -/** - * The base view model class for rendering the Tile's TileDetailsView. - */ -abstract class TileDetailsViewModel { +/** The view model interface for rendering the Tile's TileDetailsView. */ +interface TileDetailsViewModel { // The callback when the settings button is clicked. Currently this is the same as the on tile // long press callback - abstract fun clickOnSettingsButton() + fun clickOnSettingsButton() - abstract fun getTitle(): String + val title: String - abstract fun getSubTitle(): String + val subTitle: String } diff --git a/packages/SystemUI/res/drawable/notification_2025_smart_reply_button_background.xml b/packages/SystemUI/res/drawable/notification_2025_smart_reply_button_background.xml new file mode 100644 index 000000000000..d398f60ddc3c --- /dev/null +++ b/packages/SystemUI/res/drawable/notification_2025_smart_reply_button_background.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> + +<ripple xmlns:android="http://schemas.android.com/apk/res/android" + android:color="@color/notification_ripple_untinted_color"> + <item> + <inset + android:insetLeft="0dp" + android:insetTop="8dp" + android:insetRight="0dp" + android:insetBottom="8dp"> + <shape android:shape="rectangle"> + <corners android:radius="@dimen/notification_2025_smart_reply_button_corner_radius" /> + <stroke android:width="@dimen/smart_reply_button_stroke_width" + android:color="@color/smart_reply_button_stroke" /> + <solid android:color="@color/smart_reply_button_background"/> + </shape> + </inset> + </item> +</ripple> diff --git a/packages/SystemUI/res/layout/clipboard_overlay.xml b/packages/SystemUI/res/layout/clipboard_overlay.xml index 915563b1ae20..c7add163dffa 100644 --- a/packages/SystemUI/res/layout/clipboard_overlay.xml +++ b/packages/SystemUI/res/layout/clipboard_overlay.xml @@ -85,6 +85,7 @@ android:paddingVertical="@dimen/overlay_action_container_padding_vertical" android:elevation="4dp" android:scrollbars="none" + android:importantForAccessibility="no" app:layout_constraintHorizontal_bias="0" app:layout_constraintWidth_percent="1.0" app:layout_constraintWidth_max="wrap" @@ -176,6 +177,8 @@ app:layout_constraintStart_toStartOf="parent" android:layout_marginStart="4dp" android:layout_marginBottom="2dp" + android:importantForAccessibility="yes" + android:contentDescription="@string/clipboard_overlay_window_name" android:background="@drawable/clipboard_minimized_background_inset"> <ImageView android:src="@drawable/ic_content_paste" diff --git a/packages/SystemUI/res/layout/media_output_dialog.xml b/packages/SystemUI/res/layout/media_output_dialog.xml index 6f8b4cd3e4a9..9b629ace76af 100644 --- a/packages/SystemUI/res/layout/media_output_dialog.xml +++ b/packages/SystemUI/res/layout/media_output_dialog.xml @@ -15,8 +15,8 @@ ~ limitations under the License. --> -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/media_output_dialog" android:layout_width="@dimen/large_dialog_width" android:layout_height="wrap_content" @@ -35,24 +35,25 @@ android:orientation="horizontal"> <ImageView android:id="@+id/header_icon" - android:layout_width="72dp" - android:layout_height="72dp" + android:layout_width="@dimen/media_output_dialog_header_album_icon_size" + android:layout_height="@dimen/media_output_dialog_header_album_icon_size" + android:layout_marginEnd="@dimen/media_output_dialog_header_icon_padding" android:importantForAccessibility="no"/> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" - android:paddingStart="12dp" android:orientation="vertical"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" - android:gravity="center_vertical" + android:gravity="top" android:orientation="horizontal"> <ImageView android:id="@+id/app_source_icon" android:layout_width="20dp" android:layout_height="20dp" + android:layout_marginBottom="7dp" android:gravity="center_vertical" android:importantForAccessibility="no"/> @@ -103,12 +104,11 @@ android:layout_height="wrap_content" > </ViewStub> - <LinearLayout + <androidx.constraintlayout.widget.ConstraintLayout android:id="@+id/device_list" android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_weight="1" - android:orientation="vertical"> + android:layout_height="0dp" + android:layout_weight="1"> <androidx.recyclerview.widget.RecyclerView android:id="@+id/list_result" @@ -116,8 +116,11 @@ android:paddingTop="8dp" android:clipToPadding="false" android:layout_width="match_parent" - android:layout_height="wrap_content"/> - </LinearLayout> + android:layout_height="wrap_content" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintHeight_max="@dimen/media_output_dialog_list_max_height"/> + </androidx.constraintlayout.widget.ConstraintLayout> <LinearLayout android:layout_width="match_parent" diff --git a/packages/SystemUI/res/layout/notif_half_shelf.xml b/packages/SystemUI/res/layout/notif_half_shelf.xml index 9a66ca9f3baa..603cc00a312c 100644 --- a/packages/SystemUI/res/layout/notif_half_shelf.xml +++ b/packages/SystemUI/res/layout/notif_half_shelf.xml @@ -71,15 +71,18 @@ android:textSize="16sp" /> - <Switch - android:id="@+id/toggle" + <com.google.android.material.materialswitch.MaterialSwitch + android:theme="@style/Theme.Material3.DynamicColors.DayNight" + android:id="@+id/material_toggle" + android:filterTouchesWhenObscured="false" + android:clickable="true" + android:focusable="true" + android:padding="8dp" android:layout_height="48dp" android:layout_width="wrap_content" android:layout_gravity="center_vertical" - android:padding="8dp" - android:track="@drawable/settingslib_track_selector" - android:thumb="@drawable/settingslib_thumb_selector" - android:theme="@style/MainSwitch.Settingslib"/> + style="@style/SettingslibSwitchStyle.Expressive"/> + </com.android.systemui.statusbar.notification.row.AppControlView> <ScrollView diff --git a/packages/SystemUI/res/layout/notif_half_shelf_row.xml b/packages/SystemUI/res/layout/notif_half_shelf_row.xml index b2eaa6ce92b5..4bc37f82a9d7 100644 --- a/packages/SystemUI/res/layout/notif_half_shelf_row.xml +++ b/packages/SystemUI/res/layout/notif_half_shelf_row.xml @@ -80,15 +80,16 @@ /> </RelativeLayout> - <Switch - android:id="@+id/toggle" + <com.google.android.material.materialswitch.MaterialSwitch + android:theme="@style/Theme.Material3.DynamicColors.DayNight" + android:id="@+id/material_toggle" + android:filterTouchesWhenObscured="false" + android:clickable="true" + android:focusable="true" + android:padding="8dp" android:layout_height="48dp" android:layout_width="wrap_content" android:layout_gravity="center_vertical" - android:padding="8dp" - android:track="@drawable/settingslib_track_selector" - android:thumb="@drawable/settingslib_thumb_selector" - android:theme="@style/MainSwitch.Settingslib" - /> + style="@style/SettingslibSwitchStyle.Expressive"/> </LinearLayout> </com.android.systemui.statusbar.notification.row.ChannelRow> diff --git a/packages/SystemUI/res/layout/notification_2025_smart_action_button.xml b/packages/SystemUI/res/layout/notification_2025_smart_action_button.xml new file mode 100644 index 000000000000..ed905885a76f --- /dev/null +++ b/packages/SystemUI/res/layout/notification_2025_smart_action_button.xml @@ -0,0 +1,35 @@ +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> + +<!-- android:paddingHorizontal is set dynamically in SmartReplyView. --> +<Button xmlns:android="http://schemas.android.com/apk/res/android" + style="@android:style/Widget.Material.Button" + android:stateListAnimator="@null" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:minWidth="0dp" + android:minHeight="@dimen/notification_2025_smart_reply_button_min_height" + android:paddingVertical="@dimen/smart_reply_button_padding_vertical" + android:background="@drawable/notification_2025_smart_reply_button_background" + android:gravity="center" + android:fontFamily="google-sans-flex" + android:textSize="@dimen/smart_reply_button_font_size" + android:textColor="@color/smart_reply_button_text" + android:paddingStart="@dimen/smart_reply_button_action_padding_left" + android:paddingEnd="@dimen/smart_reply_button_padding_horizontal" + android:drawablePadding="@dimen/smart_action_button_icon_padding" + android:textStyle="normal" + android:ellipsize="none"/> diff --git a/packages/SystemUI/res/layout/notification_2025_smart_reply_button.xml b/packages/SystemUI/res/layout/notification_2025_smart_reply_button.xml new file mode 100644 index 000000000000..4f543e5099bf --- /dev/null +++ b/packages/SystemUI/res/layout/notification_2025_smart_reply_button.xml @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> + +<!-- android:paddingHorizontal is set dynamically in SmartReplyView. --> +<Button xmlns:android="http://schemas.android.com/apk/res/android" + style="@android:style/Widget.Material.Button" + android:stateListAnimator="@null" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:minWidth="0dp" + android:minHeight="@dimen/notification_2025_smart_reply_button_min_height" + android:paddingVertical="@dimen/smart_reply_button_padding_vertical" + android:background="@drawable/notification_2025_smart_reply_button_background" + android:gravity="center" + android:fontFamily="google-sans-flex" + android:textSize="@dimen/smart_reply_button_font_size" + android:textColor="@color/smart_reply_button_text" + android:paddingStart="@dimen/smart_reply_button_padding_horizontal" + android:paddingEnd="@dimen/smart_reply_button_padding_horizontal" + android:textStyle="normal" + android:ellipsize="none"/> diff --git a/packages/SystemUI/res/raw/fingerprint_dialogue_unlocked_to_checkmark_success_lottie.json b/packages/SystemUI/res/raw/fingerprint_dialogue_unlocked_to_checkmark_success_lottie.json index b1d6a270bc67..3f03fcff7603 100644 --- a/packages/SystemUI/res/raw/fingerprint_dialogue_unlocked_to_checkmark_success_lottie.json +++ b/packages/SystemUI/res/raw/fingerprint_dialogue_unlocked_to_checkmark_success_lottie.json @@ -1 +1 @@ -{"v":"5.7.13","fr":60,"ip":0,"op":55,"w":80,"h":80,"nm":"unlocked_to_checkmark_success","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".colorAccentPrimary","cl":"colorAccentPrimary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[47.143,32,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[2.761,0],[0,-2.7],[0,0]],"o":[[0,0],[0,-2.7],[-2.761,0],[0,0],[0,0]],"v":[[5,5],[5,-0.111],[0,-5],[-5,-0.111],[-5.01,4]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450990677,0.890196084976,0.992156863213,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"t":10,"s":[0]}],"ix":4},"w":{"a":0,"k":2.5,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":85,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".colorAccentPrimary","cl":"colorAccentPrimary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[38,45,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[18,16],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":2,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450990677,0.890196084976,0.992156863213,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"t":10,"s":[0]}],"ix":4},"w":{"a":0,"k":2.5,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":85,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".colorAccentPrimary","cl":"colorAccentPrimary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[37.999,44.999,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.42,0],[0,1.42],[1.42,0],[0,-1.42]],"o":[[1.42,0],[0,-1.42],[-1.42,0],[0,1.42]],"v":[[0,2.571],[2.571,0],[0,-2.571],[-2.571,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.827450990677,0.890196084976,0.992156863213,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"t":10,"s":[0]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":85,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".green200","cl":"green200","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40.5,40.75,0],"ix":2,"l":2},"a":{"a":0,"k":[12.5,-6.25,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":10,"s":[60,60,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":19,"s":[112,112,100]},{"t":30,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-10.556,-9.889],[7.444,6.555],[34.597,-20.486]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.658823529412,0.854901960784,0.709803921569,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":10,"s":[0]},{"t":20,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":10,"op":910,"st":10,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".green200","cl":"green200","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[0]},{"t":15,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[40.25,40.25,0],"ix":1,"l":2},"s":{"a":0,"k":[93.5,93.5,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.12,0],[0,-22.08],[-22.08,0],[0,22.08]],"o":[[-22.08,0],[0,22.08],[22.12,0],[0,-22.08]],"v":[[-0.04,-40],[-40,0],[-0.04,40],[40,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[40.25,40.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.658823529412,0.854901960784,0.709803921569,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[0]},{"t":15,"s":[100]}],"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":10,"s":[0]},{"t":20,"s":[4]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":10,"op":77,"st":10,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".grey700","cl":"grey700","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[40.25,40.25,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.12,0],[0,-22.08],[-22.08,0],[0,22.08]],"o":[[-22.08,0],[0,22.08],[22.12,0],[0,-22.08]],"v":[[-0.04,-40],[-40,0],[-0.04,40],[40,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.278431385756,0.278431385756,0.278431385756,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[100]},{"t":20,"s":[0]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[40.25,40.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file +{"v":"5.7.13","fr":60,"ip":0,"op":55,"w":80,"h":80,"nm":"unlocked_to_checkmark_success","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".onPrimary","cl":"onPrimary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[47.143,32,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[2.761,0],[0,-2.7],[0,0]],"o":[[0,0],[0,-2.7],[-2.761,0],[0,0],[0,0]],"v":[[5,5],[5,-0.111],[0,-5],[-5,-0.111],[-5.01,4]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450990677,0.890196084976,0.992156863213,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"t":10,"s":[0]}],"ix":4},"w":{"a":0,"k":2.5,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":85,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".onPrimary","cl":"onPrimary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[38,45,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[18,16],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":2,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450990677,0.890196084976,0.992156863213,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"t":10,"s":[0]}],"ix":4},"w":{"a":0,"k":2.5,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":85,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".onPrimary","cl":"onPrimary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[37.999,44.999,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.42,0],[0,1.42],[1.42,0],[0,-1.42]],"o":[[1.42,0],[0,-1.42],[-1.42,0],[0,1.42]],"v":[[0,2.571],[2.571,0],[0,-2.571],[-2.571,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.827450990677,0.890196084976,0.992156863213,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"t":10,"s":[0]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":85,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".green200","cl":"green200","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40.5,40.75,0],"ix":2,"l":2},"a":{"a":0,"k":[12.5,-6.25,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":10,"s":[60,60,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":19,"s":[112,112,100]},{"t":30,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-10.556,-9.889],[7.444,6.555],[34.597,-20.486]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.658823529412,0.854901960784,0.709803921569,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":10,"s":[0]},{"t":20,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":10,"op":910,"st":10,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".green200","cl":"green200","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[0]},{"t":15,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[40.25,40.25,0],"ix":1,"l":2},"s":{"a":0,"k":[93.5,93.5,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.12,0],[0,-22.08],[-22.08,0],[0,22.08]],"o":[[-22.08,0],[0,22.08],[22.12,0],[0,-22.08]],"v":[[-0.04,-40],[-40,0],[-0.04,40],[40,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[40.25,40.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.658823529412,0.854901960784,0.709803921569,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[0]},{"t":15,"s":[100]}],"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":10,"s":[0]},{"t":20,"s":[4]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":10,"op":77,"st":10,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".primary","cl":"primary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[40.25,40.25,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.12,0],[0,-22.08],[-22.08,0],[0,22.08]],"o":[[-22.08,0],[0,22.08],[22.12,0],[0,-22.08]],"v":[[-0.04,-40],[-40,0],[-0.04,40],[40,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.278431385756,0.278431385756,0.278431385756,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[100]},{"t":20,"s":[0]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[40.25,40.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml index 4179d8a89dee..cbec8c2e0b82 100644 --- a/packages/SystemUI/res/values-af/strings.xml +++ b/packages/SystemUI/res/values-af/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Gekoppel aan <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Gekoppel aan <xliff:g id="CAST">%s</xliff:g>."</string> <string name="accessibility_expand_group" msgid="521237935987978624">"Vou groep uit."</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Voeg toestel by groep."</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Verwyder toestel uit groep."</string> <string name="accessibility_open_application" msgid="1749126077501259712">"Maak app oop."</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"Nie gekoppel nie."</string> <string name="data_connection_roaming" msgid="375650836665414797">"Swerwing"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Invoer"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Gehoortoestelle"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Skakel tans aan …"</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Kan nie helderheid verstel nie omdat dit deur die boonste app beheer word"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Outodraai"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Outodraai skerm"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"Ligging"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Voeg by posisie <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Posisie is ongeldig."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Posisie <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Teël is bygevoeg"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Teël is verwyder"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Kitsinstellingswysiger."</string> @@ -1574,6 +1573,5 @@ <string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Onbekend"</string> <string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Stel alle teëls terug?"</string> <string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Alle Kitsinstellingsteëls sal na die toestel se oorspronklike instellings teruggestel word"</string> - <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) --> - <skip /> + <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string> </resources> diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml index a92a1416fd2c..655f06037922 100644 --- a/packages/SystemUI/res/values-am/strings.xml +++ b/packages/SystemUI/res/values-am/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"ከ<xliff:g id="BLUETOOTH">%s</xliff:g> ጋር ተገናኝቷል።"</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"ከ<xliff:g id="CAST">%s</xliff:g> ጋር ተገናኝቷል።"</string> <string name="accessibility_expand_group" msgid="521237935987978624">"ቡድንን ዘርጋ።"</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"መሣሪያን ወደ ቡድን አክል።"</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"መሣሪያን ከቡድን ላይ አስወግድ።"</string> <string name="accessibility_open_application" msgid="1749126077501259712">"መተግበሪያ ክፈት።"</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"አልተገናኘም።"</string> <string name="data_connection_roaming" msgid="375650836665414797">"በማዛወር ላይ"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"ግቤት"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"መስሚያ አጋዥ መሣሪያዎች"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"በማብራት ላይ..."</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"ከላይ ባለው መተግበሪያ ቁጥጥር እየተደረገበት ስለሆነ ብሩህነትን ማስተካከል አልተቻለም"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"በራስ ሰር አሽከርክር"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"ማያ ገጽን በራስ-አሽከርክር"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"አካባቢ"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"ወደ <xliff:g id="POSITION">%1$d</xliff:g> ቦታ አክል"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"አቀማመጡ ተቀባይነት የለውም።"</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"የ<xliff:g id="POSITION">%1$d</xliff:g> አቀማመጥ"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"ሰቅ ታክሏል"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"ሰቅ ተወግዷል"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"የፈጣን ቅንብሮች አርታዒ።"</string> @@ -1574,6 +1573,5 @@ <string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"ያልታወቀ"</string> <string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"ሁሉም ሰቆች ዳግም ይጀምሩ?"</string> <string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"ሁሉም የፈጣን ቅንብሮች ሰቆች ወደ የመሣሪያው የመጀመሪያ ቅንብሮች ዳግም ይጀምራሉ"</string> - <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) --> - <skip /> + <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>፣ <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string> </resources> diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml index 13f3fd8d4e1b..b2d71da5b61d 100644 --- a/packages/SystemUI/res/values-ar/strings.xml +++ b/packages/SystemUI/res/values-ar/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"متصل بـ <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"تم الاتصال بـ <xliff:g id="CAST">%s</xliff:g>."</string> <string name="accessibility_expand_group" msgid="521237935987978624">"سيتم توسيع المجموعة."</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"إضافة الجهاز إلى المجموعة"</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"إزالة الجهاز من المجموعة"</string> <string name="accessibility_open_application" msgid="1749126077501259712">"سيتم فتح التطبيق."</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"غير متصل."</string> <string name="data_connection_roaming" msgid="375650836665414797">"التجوال"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"الإدخال"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"سماعات الأذن الطبية"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"جارٍ التفعيل…"</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"لا يمكن ضبط مستوى السطوع لأنّ التطبيق العلوي يتحكّم فيه"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"التدوير التلقائي"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"التدوير التلقائي للشاشة"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"الموقع الجغرافي"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"الإضافة إلى الموضع <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"الموضِع غير صالح."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"الموضع: <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"تمت إضافة البطاقة."</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"تمت إزالة البطاقة."</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"برنامج تعديل الإعدادات السريعة."</string> diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml index 067649cdea65..6aa9d66ab48f 100644 --- a/packages/SystemUI/res/values-as/strings.xml +++ b/packages/SystemUI/res/values-as/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g>ৰ লগত সংযোগ কৰা হ’ল।"</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g>ত সংযোগ হ’ল।"</string> <string name="accessibility_expand_group" msgid="521237935987978624">"গোট বিস্তাৰ কৰক।"</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"গোটত ডিভাইচ যোগ দিয়ক।"</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"গোটৰ পৰা ডিভাইচ আঁতৰাওক।"</string> <string name="accessibility_open_application" msgid="1749126077501259712">"এপ্লিকেশ্বনটো খোলক।"</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"সংযোগ হৈ থকা নাই।"</string> <string name="data_connection_roaming" msgid="375650836665414797">"ৰ\'মিং"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"ইনপুট"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"শ্ৰৱণ যন্ত্ৰ"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"অন কৰি থকা হৈছে…"</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"উজ্জ্বলতা মিলাব নোৱাৰি কাৰণ সেয়া শীৰ্ষৰ এপটোৱে নিয়ন্ত্ৰণ কৰি আছে"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"স্বয়ং-ঘূৰ্ণন"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"স্বয়ং-ঘূৰ্ণন স্ক্ৰীন"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"অৱস্থান"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g> নম্বৰ স্থানত যোগ দিয়ক"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"স্থান অমান্য।"</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"<xliff:g id="POSITION">%1$d</xliff:g> নম্বৰ স্থান"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"টাইল যোগ দিয়া হৈছে"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"টাইল আঁতৰোৱা হৈছে"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"ক্ষিপ্ৰ ছেটিঙৰ সম্পাদক।"</string> @@ -1574,6 +1573,5 @@ <string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"অজ্ঞাত"</string> <string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"আটাইবোৰ টাইল ৰিছেট কৰিবনে?"</string> <string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"আটাইবোৰ ক্ষিপ্ৰ ছেটিঙৰ টাইল ডিভাইচৰ মূল ছেটিংছলৈ ৰিছেট হ’ব"</string> - <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) --> - <skip /> + <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string> </resources> diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml index 9df9923b115b..b87432f5d342 100644 --- a/packages/SystemUI/res/values-az/strings.xml +++ b/packages/SystemUI/res/values-az/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> üzərindən qoşuldu."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> cihazına qoşulub."</string> <string name="accessibility_expand_group" msgid="521237935987978624">"Qrupu genişləndirin."</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Qrupa cihaz əlavə edin."</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Cihazı qrupdan silin."</string> <string name="accessibility_open_application" msgid="1749126077501259712">"Tətbiqi açın."</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"Qoşulu deyil."</string> <string name="data_connection_roaming" msgid="375650836665414797">"Rouminq"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Giriş"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Eşitmə aparatları"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Aktiv edilir..."</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Yuxarıdakı tətbiq tərəfindən idarə olunduğu üçün parlaqlığı tənzimləmək mümkün deyil"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Avtodönüş"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Ekranın avtomatik dönməsi"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"Məkan"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g> mövqeyinə əlavə edin"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Mövqe yanlışdır."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"<xliff:g id="POSITION">%1$d</xliff:g> mövqeyi"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Mozaik əlavə edilib"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Mozaik silinib"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Sürətli ayarlar redaktoru."</string> @@ -1574,6 +1573,5 @@ <string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Naməlum"</string> <string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Bütün mozaiklər sıfırlansın?"</string> <string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Bütün Sürətli Ayarlar mozaiki cihazın orijinal ayarlarına sıfırlanacaq"</string> - <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) --> - <skip /> + <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string> </resources> diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml index 6ab7bf1d10ac..a875e7361335 100644 --- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml +++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml @@ -1003,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Dodajte na <xliff:g id="POSITION">%1$d</xliff:g>. poziciju"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Pozicija je nevažeća."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"<xliff:g id="POSITION">%1$d</xliff:g>. pozicija"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Pločica je dodata"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Pločica je uklonjena"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Uređivač za Brza podešavanja."</string> @@ -1571,6 +1573,5 @@ <string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Nepoznato"</string> <string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Želite da resetujete sve pločice?"</string> <string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Sve pločice Brzih podešavanja će se resetovati na prvobitna podešavanja uređaja"</string> - <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) --> - <skip /> + <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string> </resources> diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml index 94f950720a20..75d0a44f38a1 100644 --- a/packages/SystemUI/res/values-be/strings.xml +++ b/packages/SystemUI/res/values-be/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Падлучаны да <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Ёсць падключэнне да <xliff:g id="CAST">%s</xliff:g>."</string> <string name="accessibility_expand_group" msgid="521237935987978624">"Разгарнуць групу."</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Дадаць прыладу ў групу."</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Выдаліць прыладу з групы."</string> <string name="accessibility_open_application" msgid="1749126077501259712">"Адкрыць праграму."</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"Няма падключэння."</string> <string name="data_connection_roaming" msgid="375650836665414797">"Роўмінг"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Увод"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Слыхавыя апараты"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Уключэнне…"</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Не ўдаецца адрэгуляваць яркасць, бо яна кантралюецца верхняй праграмай"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Аўтапаварот"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Аўтаматычны паварот экрана"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"Месцазнаходжанне"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Дадаць на пазіцыю <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Няправільнае месца."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Пазіцыя <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Плітка дададзена"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Плітка выдалена"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Рэдактар хуткіх налад."</string> diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml index 4615e60730f2..4f3d64124e04 100644 --- a/packages/SystemUI/res/values-bg/strings.xml +++ b/packages/SystemUI/res/values-bg/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Има връзка с <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Установена е връзка с/ъс <xliff:g id="CAST">%s</xliff:g>."</string> <string name="accessibility_expand_group" msgid="521237935987978624">"Разгъване на групата."</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Добавяне на устройството към групата."</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Премахване на устройството от групата."</string> <string name="accessibility_open_application" msgid="1749126077501259712">"Отваряне на приложението."</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"Няма връзка."</string> <string name="data_connection_roaming" msgid="375650836665414797">"Роуминг"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Вход"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Слухови апарати"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Включва се..."</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Яркостта не може да се коригира, защото се контролира от приложението на екрана"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Авт. ориентация"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Автоматично завъртане на екрана"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"Местоположение"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Добавяне към позиция <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Невалидна позиция."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Позиция <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Панелът е добавен"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Панелът е премахнат"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Редактор за бързи настройки."</string> diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml index 6247bf14f726..3fd699db612f 100644 --- a/packages/SystemUI/res/values-bn/strings.xml +++ b/packages/SystemUI/res/values-bn/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g>এ সংযুক্ত হয়ে আছে।"</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> এর সাথে সংযুক্ত৷"</string> <string name="accessibility_expand_group" msgid="521237935987978624">"গ্রুপ বড় করুন।"</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"গ্রুপে ডিভাইস যোগ করুন।"</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"গ্রুপ থেকে ডিভাইস সরিয়ে দিন।"</string> <string name="accessibility_open_application" msgid="1749126077501259712">"অ্যাপ্লিকেশন খুলুন।"</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"সংযুক্ত নয়৷"</string> <string name="data_connection_roaming" msgid="375650836665414797">"রোমিং"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"ইনপুট"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"হিয়ারিং এড"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"চালু করা হচ্ছে…"</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"উজ্জ্বলতা অ্যাডজাস্ট করা যাচ্ছে না কারণ এটি সেরা অ্যাপের মাধ্যমে কন্ট্রোল করা হচ্ছে"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"নিজে থেকে ঘুরবে"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"অটো-রোটেট স্ক্রিন"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"লোকেশন"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"অবস্থান <xliff:g id="POSITION">%1$d</xliff:g>-এ যোগ করুন"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"পজিশন সঠিক নয়।"</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"অবস্থান <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"টাইল যোগ করা হয়েছে"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"টাইল সরানো হয়েছে"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"দ্রুত সেটিংস সম্পাদক৷"</string> @@ -1574,6 +1573,5 @@ <string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"অজানা"</string> <string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"সব টাইল রিসেট করবেন?"</string> <string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"সব কুইক সেটিংস টাইল, ডিভাইসের আসল সেটিংসে রিসেট হয়ে যাবে"</string> - <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) --> - <skip /> + <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string> </resources> diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml index 75f3f08356c2..0b5af1771614 100644 --- a/packages/SystemUI/res/values-bs/strings.xml +++ b/packages/SystemUI/res/values-bs/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Povezan na <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Povezan na <xliff:g id="CAST">%s</xliff:g>."</string> <string name="accessibility_expand_group" msgid="521237935987978624">"Proširivanje grupe."</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Dodavanje uređaja u grupu."</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Uklanjanje uređaja iz grupe."</string> <string name="accessibility_open_application" msgid="1749126077501259712">"Otvaranje aplikacije."</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"Nije povezano."</string> <string name="data_connection_roaming" msgid="375650836665414797">"Roming"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Ulaz"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Slušni aparati"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Uključivanje…"</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Nije moguće podesiti osvijetljenost jer njome upravlja gornja aplikacija"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Automatsko rotiranje"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Automatsko rotiranje ekrana"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"Lokacija"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Dodavanje u položaj <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Nevažeći položaj."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Položaj <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Kartica je dodana"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Kartica je uklonjena"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Uređivanje brzih postavki"</string> @@ -1574,6 +1573,5 @@ <string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Nepoznato"</string> <string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Vratiti sve kartice na zadano?"</string> <string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Sve kartice Brze postavke će se vratiti na originalne postavke uređaja"</string> - <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) --> - <skip /> + <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string> </resources> diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml index 19b299f14824..0225d2b27ca8 100644 --- a/packages/SystemUI/res/values-ca/strings.xml +++ b/packages/SystemUI/res/values-ca/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"S\'ha connectat a <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Està connectat amb <xliff:g id="CAST">%s</xliff:g>."</string> <string name="accessibility_expand_group" msgid="521237935987978624">"Desplega el grup."</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Afegeix el dispositiu al grup."</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Suprimeix el dispositiu del grup."</string> <string name="accessibility_open_application" msgid="1749126077501259712">"Obre l\'aplicació."</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"Sense connexió."</string> <string name="data_connection_roaming" msgid="375650836665414797">"Itinerància"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Entrada"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Audiòfons"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"S\'està activant…"</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"No es pot ajustar la brillantor perquè està controlada per l\'aplicació superior"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Gira automàticament"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Gira la pantalla automàticament"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"Ubicació"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Afegeix a la posició <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"La posició no és vàlida."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Posició <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"El mosaic s\'ha afegit"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"El mosaic s\'ha suprimit"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Editor de configuració ràpida."</string> @@ -1574,6 +1573,5 @@ <string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Desconegut"</string> <string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Vols restablir totes les icones?"</string> <string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Totes les icones de configuració ràpida es restabliran a les opcions originals del dispositiu"</string> - <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) --> - <skip /> + <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string> </resources> diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml index 51dfa2ec680d..8a6980e355c0 100644 --- a/packages/SystemUI/res/values-cs/strings.xml +++ b/packages/SystemUI/res/values-cs/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Připojeno k zařízení <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Jste připojeni k zařízení <xliff:g id="CAST">%s</xliff:g>."</string> <string name="accessibility_expand_group" msgid="521237935987978624">"Rozbalit skupinu."</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Přidat zařízení do skupiny."</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Odstranit zařízení ze skupiny."</string> <string name="accessibility_open_application" msgid="1749126077501259712">"Otevřít aplikaci."</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"Nepřipojeno."</string> <string name="data_connection_roaming" msgid="375650836665414797">"Roaming"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Vstup"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Naslouchátka"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Zapínání…"</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Jas nelze upravit, protože ho řídí hlavní aplikace"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Autom. otáčení"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Automatické otáčení obrazovky"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"Poloha"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Přidat dlaždici na pozici <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Pozice není platná."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Pozice <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Dlaždice byla přidána"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Dlaždice byla odstraněna"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Editor rychlého nastavení"</string> @@ -1574,6 +1573,5 @@ <string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Neznámé"</string> <string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Resetovat všechny dlaždice?"</string> <string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Všechny dlaždice Rychlého nastavení se resetují do původní konfigurace zařízení"</string> - <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) --> - <skip /> + <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string> </resources> diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml index ad6633e2cfaf..60c5c1c5e1c0 100644 --- a/packages/SystemUI/res/values-da/strings.xml +++ b/packages/SystemUI/res/values-da/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Forbundet med <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Forbundet til <xliff:g id="CAST">%s</xliff:g>."</string> <string name="accessibility_expand_group" msgid="521237935987978624">"Udvid gruppe."</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Føj enhed til gruppe."</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Fjern enhed fra gruppe."</string> <string name="accessibility_open_application" msgid="1749126077501259712">"Åbn app."</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"Ikke tilsluttet."</string> <string name="data_connection_roaming" msgid="375650836665414797">"Roaming"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Input"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Høreapparater"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Aktiverer…"</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Lysstyrken kan ikke justeres, fordi den styres af den øverste app"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Roter automatisk"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Roter skærmen automatisk"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"Lokation"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Føj til lokation <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Positionen er ugyldig."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Lokation <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Feltet blev tilføjet"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Feltet blev fjernet"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Redigeringsværktøj til Kvikmenu."</string> @@ -1574,6 +1573,5 @@ <string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Ukendt"</string> <string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Vil du nulstille alle handlingsfelter?"</string> <string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Alle handlingsfelter i kvikmenuen nulstilles til enhedens oprindelige indstillinger"</string> - <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) --> - <skip /> + <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string> </resources> diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml index 8bc38157f90a..66b60b18cdae 100644 --- a/packages/SystemUI/res/values-de/strings.xml +++ b/packages/SystemUI/res/values-de/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Mit <xliff:g id="BLUETOOTH">%s</xliff:g> verbunden"</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Verbunden mit <xliff:g id="CAST">%s</xliff:g>."</string> <string name="accessibility_expand_group" msgid="521237935987978624">"Gruppe erweitern."</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Gerät zu Gruppe hinzufügen."</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Gerät aus Gruppe entfernen."</string> <string name="accessibility_open_application" msgid="1749126077501259712">"Anwendung öffnen."</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"Nicht verbunden"</string> <string name="data_connection_roaming" msgid="375650836665414797">"Roaming"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Eingabe"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Hörgerät"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Wird aktiviert…"</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Die Helligkeit kann nicht angepasst werden, weil sie von der obersten App gesteuert wird"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Autom. drehen"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Bildschirm automatisch drehen"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"Standort"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Zur Position <xliff:g id="POSITION">%1$d</xliff:g> hinzufügen"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Position ist ungültig."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Position <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Ansicht hinzugefügt"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Ansicht entfernt"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Editor für Schnelleinstellungen."</string> @@ -1574,6 +1573,5 @@ <string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Unbekannt"</string> <string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Alle Kacheln zurücksetzen?"</string> <string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Alle Schnelleinstellungen-Kacheln werden auf die Standardeinstellungen des Geräts zurückgesetzt"</string> - <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) --> - <skip /> + <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string> </resources> diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml index dade080e2624..639aee2d038e 100644 --- a/packages/SystemUI/res/values-el/strings.xml +++ b/packages/SystemUI/res/values-el/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Συνδέθηκε στο <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Συνδέθηκε σε <xliff:g id="CAST">%s</xliff:g>."</string> <string name="accessibility_expand_group" msgid="521237935987978624">"Αναπτύξτε την ομάδα."</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Προσθήκη συσκευής στην ομάδα."</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Κατάργηση συσκευής από την ομάδα."</string> <string name="accessibility_open_application" msgid="1749126077501259712">"Ανοίξτε την εφαρμογή."</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"Μη συνδεδεμένο"</string> <string name="data_connection_roaming" msgid="375650836665414797">"Περιαγωγή"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Είσοδος"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Βοηθήματα ακοής"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Ενεργοποίηση…"</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Δεν είναι δυνατή η προσαρμογή της φωτεινότητας, επειδή ελέγχεται από την εφαρμογή στην κορυφή"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Αυτόματη περιστροφή"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Αυτόματη περιστροφή οθόνης"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"Τοποθεσία"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Προσθήκη στη θέση <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Μη έγκυρη θέση."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Θέση <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Το πλακίδιο προστέθηκε"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Το πλακίδιο καταργήθηκε"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Επεξεργασία γρήγορων ρυθμίσεων."</string> diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml index fb251ddc47a2..62441c8682f3 100644 --- a/packages/SystemUI/res/values-en-rAU/strings.xml +++ b/packages/SystemUI/res/values-en-rAU/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Connected to <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Connected to <xliff:g id="CAST">%s</xliff:g>."</string> <string name="accessibility_expand_group" msgid="521237935987978624">"Expand group."</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Add device to group."</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Remove device from group."</string> <string name="accessibility_open_application" msgid="1749126077501259712">"Open application."</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"Not connected."</string> <string name="data_connection_roaming" msgid="375650836665414797">"Roaming"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Input"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Hearing aids"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Turning on…"</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Can\'t adjust brightness because it\'s being controlled by the top app"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Auto-rotate"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Auto-rotate screen"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"Location"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Add to position <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Position invalid."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Position <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Tile added"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Tile removed"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Quick settings editor."</string> @@ -1574,6 +1573,5 @@ <string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Unknown"</string> <string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Reset all tiles?"</string> <string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"All Quick Settings tiles will reset to the device\'s original settings"</string> - <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) --> - <skip /> + <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string> </resources> diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml index 54ef0cc72da1..69aca1fb223e 100644 --- a/packages/SystemUI/res/values-en-rCA/strings.xml +++ b/packages/SystemUI/res/values-en-rCA/strings.xml @@ -423,12 +423,9 @@ <string name="hearing_devices_ambient_label" msgid="629440938614895797">"Surroundings"</string> <string name="hearing_devices_ambient_control_left" msgid="3586965448230412600">"Left"</string> <string name="hearing_devices_ambient_control_right" msgid="6192137602448918383">"Right"</string> - <!-- no translation found for hearing_devices_ambient_control_description (3663947879732939509) --> - <skip /> - <!-- no translation found for hearing_devices_ambient_control_left_description (4440988622896213511) --> - <skip /> - <!-- no translation found for hearing_devices_ambient_control_right_description (2230461103493378003) --> - <skip /> + <string name="hearing_devices_ambient_control_description" msgid="3663947879732939509">"Surroundings"</string> + <string name="hearing_devices_ambient_control_left_description" msgid="4440988622896213511">"Left surroundings"</string> + <string name="hearing_devices_ambient_control_right_description" msgid="2230461103493378003">"Right surroundings"</string> <string name="hearing_devices_ambient_expand_controls" msgid="2131816068187709200">"Expand to left and right separated controls"</string> <string name="hearing_devices_ambient_collapse_controls" msgid="2261097656446201581">"Collapse to unified control"</string> <string name="hearing_devices_ambient_mute" msgid="1836882837647429416">"Mute surroundings"</string> @@ -1003,6 +1000,7 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Add to position <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Position invalid."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Position <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <string name="accessibility_qs_edit_tile_already_added" msgid="5900071201690226752">"Tile already added"</string> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Tile added"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Tile removed"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Quick settings editor."</string> diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml index fb251ddc47a2..62441c8682f3 100644 --- a/packages/SystemUI/res/values-en-rGB/strings.xml +++ b/packages/SystemUI/res/values-en-rGB/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Connected to <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Connected to <xliff:g id="CAST">%s</xliff:g>."</string> <string name="accessibility_expand_group" msgid="521237935987978624">"Expand group."</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Add device to group."</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Remove device from group."</string> <string name="accessibility_open_application" msgid="1749126077501259712">"Open application."</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"Not connected."</string> <string name="data_connection_roaming" msgid="375650836665414797">"Roaming"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Input"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Hearing aids"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Turning on…"</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Can\'t adjust brightness because it\'s being controlled by the top app"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Auto-rotate"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Auto-rotate screen"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"Location"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Add to position <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Position invalid."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Position <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Tile added"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Tile removed"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Quick settings editor."</string> @@ -1574,6 +1573,5 @@ <string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Unknown"</string> <string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Reset all tiles?"</string> <string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"All Quick Settings tiles will reset to the device\'s original settings"</string> - <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) --> - <skip /> + <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string> </resources> diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml index fb251ddc47a2..62441c8682f3 100644 --- a/packages/SystemUI/res/values-en-rIN/strings.xml +++ b/packages/SystemUI/res/values-en-rIN/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Connected to <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Connected to <xliff:g id="CAST">%s</xliff:g>."</string> <string name="accessibility_expand_group" msgid="521237935987978624">"Expand group."</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Add device to group."</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Remove device from group."</string> <string name="accessibility_open_application" msgid="1749126077501259712">"Open application."</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"Not connected."</string> <string name="data_connection_roaming" msgid="375650836665414797">"Roaming"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Input"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Hearing aids"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Turning on…"</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Can\'t adjust brightness because it\'s being controlled by the top app"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Auto-rotate"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Auto-rotate screen"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"Location"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Add to position <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Position invalid."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Position <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Tile added"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Tile removed"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Quick settings editor."</string> @@ -1574,6 +1573,5 @@ <string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Unknown"</string> <string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Reset all tiles?"</string> <string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"All Quick Settings tiles will reset to the device\'s original settings"</string> - <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) --> - <skip /> + <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string> </resources> diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml index 371638b311c7..840afd45cdf2 100644 --- a/packages/SystemUI/res/values-es-rUS/strings.xml +++ b/packages/SystemUI/res/values-es-rUS/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Conectado a <xliff:g id="BLUETOOTH">%s</xliff:g>"</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Conectado a <xliff:g id="CAST">%s</xliff:g>."</string> <string name="accessibility_expand_group" msgid="521237935987978624">"Expandir grupo."</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Agregar el dispositivo al grupo."</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Quitar el dispositivo del grupo."</string> <string name="accessibility_open_application" msgid="1749126077501259712">"Abrir aplicación."</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"No conectado"</string> <string name="data_connection_roaming" msgid="375650836665414797">"Roaming"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Entrada"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Audífonos"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Activando…"</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"La app superior controla el brillo, por lo que no se puede ajustar"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Giro automático"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Girar la pantalla automáticamente"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"Ubicación"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Agregar a la posición <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Posición no válida"</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Posición <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Se agregó la tarjeta"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Se quitó la tarjeta"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Editor de Configuración rápida"</string> diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml index b02ab301bf9c..177f298d0121 100644 --- a/packages/SystemUI/res/values-es/strings.xml +++ b/packages/SystemUI/res/values-es/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Conectado a <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Conectado a <xliff:g id="CAST">%s</xliff:g>."</string> <string name="accessibility_expand_group" msgid="521237935987978624">"Mostrar grupo."</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Añade un dispositivo al grupo."</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Quita un dispositivo del grupo."</string> <string name="accessibility_open_application" msgid="1749126077501259712">"Abrir aplicación."</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"No conectado."</string> <string name="data_connection_roaming" msgid="375650836665414797">"Roaming"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Entrada"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Audífonos"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Activando…"</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"No se puede ajustar el brillo porque la aplicación superior lo está controlando"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Giro automático"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Girar pantalla automáticamente"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"Ubicación"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Añadir a la posición <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Posición no válida."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Posición <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Recuadro añadido"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Recuadro quitado"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Editor de ajustes rápidos."</string> @@ -1574,6 +1573,5 @@ <string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Desconocido"</string> <string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"¿Borrar todos los recuadros?"</string> <string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Todos los recuadros de ajustes rápidos se restablecerán a los ajustes originales del dispositivo"</string> - <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) --> - <skip /> + <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string> </resources> diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml index 578ca4d663c3..be021fc96f9d 100644 --- a/packages/SystemUI/res/values-et/strings.xml +++ b/packages/SystemUI/res/values-et/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Ühendatud: <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Ühendatud ülekandega <xliff:g id="CAST">%s</xliff:g>."</string> <string name="accessibility_expand_group" msgid="521237935987978624">"Grupi laiendamine."</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Seadme lisamine gruppi."</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Seadme eemaldamine grupist."</string> <string name="accessibility_open_application" msgid="1749126077501259712">"Rakenduse avamine."</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"Ühendus puudub."</string> <string name="data_connection_roaming" msgid="375650836665414797">"Rändlus"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Sisend"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Kuuldeaparaadid"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Sisselülitamine …"</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Heledust ei saa reguleerida, kuna seda juhib ülemine rakendus"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Autom. pööramine"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Kuva automaatne pööramine"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"Asukoht"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Lisamine asendisse <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Sobimatu asukoht."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Asend <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Paan on lisatud"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Paan on eemaldatud"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Kiirseadete redigeerija."</string> diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml index c6b8f5641f56..edcc17225ace 100644 --- a/packages/SystemUI/res/values-eu/strings.xml +++ b/packages/SystemUI/res/values-eu/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> gailura konektatuta."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Hona konektatuta: <xliff:g id="CAST">%s</xliff:g>."</string> <string name="accessibility_expand_group" msgid="521237935987978624">"Zabaldu taldea."</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Gehitu gailua taldera"</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Kendu gailua taldetik."</string> <string name="accessibility_open_application" msgid="1749126077501259712">"Ireki aplikazioa."</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"Konektatu gabe."</string> <string name="data_connection_roaming" msgid="375650836665414797">"Ibiltaritza"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Sarrera"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Audifonoak"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Aktibatzen…"</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Ezin da argitasuna doitu goiko aplikazioak kontrolatzen duelako"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Biratze automatikoa"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Biratu pantaila automatikoki"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"Kokapena"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Gehitu <xliff:g id="POSITION">%1$d</xliff:g>garren lekuan"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Kokapenak ez du balio."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"<xliff:g id="POSITION">%1$d</xliff:g>garren lekua"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Gehitu da lauza"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Kendu da lauza"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Ezarpen bizkorren editorea."</string> diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml index 6f4d1bc35a8c..f1f6a14568a5 100644 --- a/packages/SystemUI/res/values-fa/strings.xml +++ b/packages/SystemUI/res/values-fa/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"به <xliff:g id="BLUETOOTH">%s</xliff:g> متصل شد."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"به <xliff:g id="CAST">%s</xliff:g> متصل شد."</string> <string name="accessibility_expand_group" msgid="521237935987978624">"گروه را از هم باز میکند."</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"افزودن دستگاه به گروه."</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"حذف دستگاه از گروه."</string> <string name="accessibility_open_application" msgid="1749126077501259712">"برنامه را باز میکند."</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"متصل نیست."</string> <string name="data_connection_roaming" msgid="375650836665414797">"فراگردی"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"ورودی"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"سمعک"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"روشن کردن…"</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"نمیتوان روشنایی را تنظیم کرد زیرا برنامه بالایی آن را کنترل میکند"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"چرخش خودکار"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"چرخش خودکار صفحهنمایش"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"مکان"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"افزودن به موقعیت <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"موقعیت نامعتبر است."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"موقعیت <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"کاشی اضافه شد"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"کاشی حذف شد"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"ویرایشگر تنظیمات سریع."</string> @@ -1574,6 +1573,5 @@ <string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"نامشخص"</string> <string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"همه کاشیها بازنشانی شود؟"</string> <string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"همه کاشیهای «تنظیمات فوری» به تنظیمات اصلی دستگاه بازنشانی خواهد شد"</string> - <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) --> - <skip /> + <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>، <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string> </resources> diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml index 527a29288bcc..747659956dbf 100644 --- a/packages/SystemUI/res/values-fi/strings.xml +++ b/packages/SystemUI/res/values-fi/strings.xml @@ -254,10 +254,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Yhteys: <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Yhdistetty kohteeseen <xliff:g id="CAST">%s</xliff:g>"</string> <string name="accessibility_expand_group" msgid="521237935987978624">"Laajenna ryhmä."</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Lisää laite ryhmään."</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Poista laite ryhmästä."</string> <string name="accessibility_open_application" msgid="1749126077501259712">"Avaa sovellus."</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"Ei yhteyttä."</string> <string name="data_connection_roaming" msgid="375650836665414797">"Roaming"</string> @@ -335,8 +333,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Syöttölaite"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Kuulolaitteet"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Otetaan käyttöön…"</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Kirkkautta ei voi säätää, koska ensisijainen sovellus ohjaa sitä"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Automaattinen kääntö"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Käännä näyttöä automaattisesti."</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"Sijainti"</string> @@ -1008,6 +1005,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Lisää paikkaan <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Virheellinen sijainti."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Paikka <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Kiekko lisätty"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Kiekko poistettu"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Pika-asetusten muokkausnäkymä"</string> @@ -1576,6 +1575,5 @@ <string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Tuntematon"</string> <string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Nollataanko kaikki laatat?"</string> <string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Kaikki pika-asetuslaatat palautetaan laitteen alkuperäisiin asetuksiin"</string> - <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) --> - <skip /> + <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string> </resources> diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml index 6a2d7c809927..e22f306b0d57 100644 --- a/packages/SystemUI/res/values-fr-rCA/strings.xml +++ b/packages/SystemUI/res/values-fr-rCA/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Connecté à : <xliff:g id="BLUETOOTH">%s</xliff:g>"</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Connecté à <xliff:g id="CAST">%s</xliff:g>."</string> <string name="accessibility_expand_group" msgid="521237935987978624">"Développer le groupe."</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Ajouter un appareil au groupe."</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Retirer l\'appareil du groupe."</string> <string name="accessibility_open_application" msgid="1749126077501259712">"Ouvrir l\'appli."</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"Non connecté"</string> <string name="data_connection_roaming" msgid="375650836665414797">"Itinérance"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Entrée"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Prothèses auditives"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Activation en cours…"</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Impossible de régler la luminosité, car elle est contrôlée par l\'appli principale"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Rotation auto"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Rotation automatique de l\'écran"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"Localisation"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Ajouter à la position <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Position incorrecte."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Position <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Tuile ajoutée"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Tuile retirée"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Éditeur de paramètres rapides."</string> @@ -1574,6 +1573,5 @@ <string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Inconnu"</string> <string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Réinitialiser toutes les tuiles?"</string> <string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Toutes les tuiles des paramètres rapides seront réinitialisées aux paramètres par défaut de l\'appareil."</string> - <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) --> - <skip /> + <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string> </resources> diff --git a/packages/SystemUI/res/values-fr-rCA/tiles_states_strings.xml b/packages/SystemUI/res/values-fr-rCA/tiles_states_strings.xml index e9d3c487009a..487c566bf50f 100644 --- a/packages/SystemUI/res/values-fr-rCA/tiles_states_strings.xml +++ b/packages/SystemUI/res/values-fr-rCA/tiles_states_strings.xml @@ -68,7 +68,7 @@ </string-array> <string-array name="tile_states_bt"> <item msgid="5330252067413512277">"Non disponible"</item> - <item msgid="5315121904534729843">"Désactivée"</item> + <item msgid="5315121904534729843">"Désactivé"</item> <item msgid="503679232285959074">"Activé"</item> </string-array> <string-array name="tile_states_airplane"> diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml index 62916e8dff07..cceab1c3689b 100644 --- a/packages/SystemUI/res/values-fr/strings.xml +++ b/packages/SystemUI/res/values-fr/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Connecté à : <xliff:g id="BLUETOOTH">%s</xliff:g>"</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Connecté à <xliff:g id="CAST">%s</xliff:g>."</string> <string name="accessibility_expand_group" msgid="521237935987978624">"Développer le groupe."</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Ajouter l\'appareil au groupe."</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Supprimer l\'appareil du groupe."</string> <string name="accessibility_open_application" msgid="1749126077501259712">"Ouvrir l\'application."</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"Non connecté"</string> <string name="data_connection_roaming" msgid="375650836665414797">"Itinérance"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Entrée"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Appareils auditifs"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Activation…"</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Impossible d\'ajuster la luminosité, car celle-ci est contrôlée par l\'appli principale"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Rotation auto"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Rotation automatique de l\'écran"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"Localisation"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Ajouter à la position <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Emplacement non valide."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Position <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Bloc ajouté"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Bloc supprimé"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Éditeur Réglages rapides"</string> @@ -1574,6 +1573,5 @@ <string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Inconnu"</string> <string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Réinitialiser tous les blocs ?"</string> <string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Tous les blocs \"Réglages rapides\" seront réinitialisés aux paramètres d\'origine de l\'appareil"</string> - <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) --> - <skip /> + <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string> </resources> diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml index 87980c89d949..f54a634eaa18 100644 --- a/packages/SystemUI/res/values-gl/strings.xml +++ b/packages/SystemUI/res/values-gl/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Conectado a <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Dispositivo conectado: <xliff:g id="CAST">%s</xliff:g>."</string> <string name="accessibility_expand_group" msgid="521237935987978624">"Despregar o grupo."</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Engadir o dispositivo ao grupo."</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Quitar o dispositivo do grupo."</string> <string name="accessibility_open_application" msgid="1749126077501259712">"Abrir a aplicación."</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"Non conectada"</string> <string name="data_connection_roaming" msgid="375650836665414797">"Itinerancia"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Entrada"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Audiófonos"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Activando…"</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Non se pode axustar o brillo porque o controla a aplicación principal"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Xirar automaticamente"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Xirar pantalla automaticamente"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"Localización"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Engadir á posición <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Posición non válida."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Posición <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Engadiuse a tarxeta"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Quitouse a tarxeta"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Editor de configuración rápida."</string> @@ -1574,6 +1573,5 @@ <string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Categoría descoñecida"</string> <string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Queres restablecer todos os atallos?"</string> <string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Restablecerase a configuración orixinal do dispositivo para todos os atallos de Configuración rápida"</string> - <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) --> - <skip /> + <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string> </resources> diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml index e61d4e1fddf1..1830b347f829 100644 --- a/packages/SystemUI/res/values-gu/strings.xml +++ b/packages/SystemUI/res/values-gu/strings.xml @@ -1006,6 +1006,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"જગ્યા પર <xliff:g id="POSITION">%1$d</xliff:g> ઉમેરો"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"સ્થિતિ અમાન્ય છે."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"જગ્યા <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"ટાઇલ ઉમેરી"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"ટાઇલ કાઢી નાખી"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"ઝડપી સેટિંગ એડિટર."</string> @@ -1574,6 +1576,5 @@ <string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"અજાણ"</string> <string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"તમામ ટાઇલ રીસેટ કરીએ?"</string> <string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"તમામ ઝડપી સેટિંગ ટાઇલને ડિવાઇસના ઑરિજિનલ સેટિંગ પર રીસેટ કરવામાં આવશે"</string> - <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) --> - <skip /> + <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string> </resources> diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml index 0dfbb4cb58b6..644980062f9f 100644 --- a/packages/SystemUI/res/values-hi/strings.xml +++ b/packages/SystemUI/res/values-hi/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> से कनेक्ट किया गया."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> से कनेक्ट है."</string> <string name="accessibility_expand_group" msgid="521237935987978624">"ग्रुप को बड़ा करें."</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"डिवाइस को ग्रुप में जोड़ें."</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"डिवाइस को ग्रुप से हटाएं."</string> <string name="accessibility_open_application" msgid="1749126077501259712">"ऐप्लिकेशन खोलें."</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"कनेक्ट नहीं है."</string> <string name="data_connection_roaming" msgid="375650836665414797">"रोमिंग"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"इनपुट"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"कान की मशीनें"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"ब्लूटूथ चालू हो रहा है…"</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"स्क्रीन की रोशनी को अडजस्ट नहीं किया जा सकता, क्योंकि इसे टॉप ऐप्लिकेशन कंट्रोल कर रहा है"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"ऑटो-रोटेट"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"स्क्रीन का अपने-आप दिशा बदलना (ऑटो-रोटेट)"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"जगह की जानकारी"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"टाइल को <xliff:g id="POSITION">%1$d</xliff:g> पोज़िशन पर जोड़ें"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"मौजूदा जगह अमान्य है."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"टाइल की पोज़िशन <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"टाइल जोड़ी गई"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"टाइल हटाई गई"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"त्वरित सेटिंग संपादक."</string> diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml index 4dba93076218..7db95fe316ce 100644 --- a/packages/SystemUI/res/values-hr/strings.xml +++ b/packages/SystemUI/res/values-hr/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Spojen na <xliff:g id="BLUETOOTH">%s</xliff:g> ."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Povezani ste sa sljedećim uređajem: <xliff:g id="CAST">%s</xliff:g>."</string> <string name="accessibility_expand_group" msgid="521237935987978624">"Proširite grupu."</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Dodajte uređaj u grupu."</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Uklonite uređaj iz grupe."</string> <string name="accessibility_open_application" msgid="1749126077501259712">"Otvorite aplikaciju."</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"Nije povezano."</string> <string name="data_connection_roaming" msgid="375650836665414797">"Roaming"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Unos"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Slušna pomagala"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Uključivanje…"</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Svjetlina se ne može prilagoditi jer njome upravlja aplikacija pri vrhu"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Automatsko zakretanje"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Automatsko zakretanje zaslona"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"Lokacija"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Dodavanje na položaj <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Položaj nije važeći."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Položaj <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Kartica je dodana"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Kartica je uklonjena"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Uređivač brzih postavki."</string> @@ -1574,6 +1573,5 @@ <string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Nepoznato"</string> <string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Želite li poništiti sve pločice?"</string> <string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Sve pločice Brze postavke vratit će se na izvorne postavke uređaja"</string> - <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) --> - <skip /> + <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string> </resources> diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml index 891cf28a2582..906ce54f33fd 100644 --- a/packages/SystemUI/res/values-hu/strings.xml +++ b/packages/SystemUI/res/values-hu/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Csatlakoztatva a következőhöz: <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Csatlakozva a következőhöz: <xliff:g id="CAST">%s</xliff:g>."</string> <string name="accessibility_expand_group" msgid="521237935987978624">"Csoport kibontása."</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Eszköz hozzáadása csoporthoz."</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Eszköz eltávolítása csoportból."</string> <string name="accessibility_open_application" msgid="1749126077501259712">"Alkalmazás megnyitása."</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"Nincs csatlakozva."</string> <string name="data_connection_roaming" msgid="375650836665414797">"Roaming"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Bevitel"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Hallókészülék"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Bekapcsolás…"</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Nem lehet módosítani a fényerőt, mert a felső alkalmazás vezérli"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Automatikus elforgatás"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Automatikus képernyőforgatás"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"Tartózkodási hely"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Hozzáadás a következő pozícióhoz: <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Érvénytelen pozíció."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"<xliff:g id="POSITION">%1$d</xliff:g>. hely"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Kártya hozzáadva"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Kártya eltávolítva"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Gyorsbeállítások szerkesztője"</string> diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml index f231822daeb9..57a822e138c2 100644 --- a/packages/SystemUI/res/values-hy/strings.xml +++ b/packages/SystemUI/res/values-hy/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Միացված է <xliff:g id="BLUETOOTH">%s</xliff:g>-ին:"</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Միացված է <xliff:g id="CAST">%s</xliff:g>-ին:"</string> <string name="accessibility_expand_group" msgid="521237935987978624">"Ծավալել խումբը։"</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Սարքը ավելացնել խմբին։"</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Հեռացնել սարքը խմբից։"</string> <string name="accessibility_open_application" msgid="1749126077501259712">"Բացել հավելվածը։"</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"Միացված չէ:"</string> <string name="data_connection_roaming" msgid="375650836665414797">"Ռոումինգ"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Մուտքագրում"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Լսողական սարք"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Միացում…"</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Հնարավոր չէ կարգավորել պայծառությունը, քանի որ այն կառավարվում է գլխավոր հավելվածի կողմից"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Ինքնապտտում"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Ավտոմատ պտտել էկրանը"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"Տեղորոշում"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Ավելացնել դիրք <xliff:g id="POSITION">%1$d</xliff:g>-ում"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Դիրքն անվավեր է։"</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Դիրք <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Սալիկն ավելացվեց"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Սալիկը հեռացվեց"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Արագ կարգավորումների խմբագրիչ:"</string> @@ -1574,6 +1573,5 @@ <string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Անհայտ"</string> <string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Զրոյացնե՞լ բոլոր սալիկները"</string> <string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Արագ կարգավորումների բոլոր սալիկները կզրոյացվեն սարքի սկզբնական կարգավորումների համաձայն։"</string> - <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) --> - <skip /> + <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string> </resources> diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml index e0e69a9555af..2d38c8fa8e7a 100644 --- a/packages/SystemUI/res/values-in/strings.xml +++ b/packages/SystemUI/res/values-in/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Terhubung ke <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Terhubung ke <xliff:g id="CAST">%s</xliff:g>."</string> <string name="accessibility_expand_group" msgid="521237935987978624">"Luaskan grup."</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Tambahkan perangkat ke grup."</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Hapus perangkat dari grup."</string> <string name="accessibility_open_application" msgid="1749126077501259712">"Buka aplikasi."</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"Tidak terhubung."</string> <string name="data_connection_roaming" msgid="375650836665414797">"Roaming"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Input"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Alat bantu dengar"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Mengaktifkan…"</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Tidak dapat menyesuaikan kecerahan karena sedang dikontrol oleh aplikasi atas"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Putar Otomatis"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Putar layar otomatis"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"Lokasi"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Tambahkan ke posisi <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Posisi tidak valid."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Posisi <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Kartu ditambahkan"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Kartu dihapus"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Editor setelan cepat."</string> @@ -1574,6 +1573,5 @@ <string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Tidak diketahui"</string> <string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Reset semua kartu?"</string> <string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Semua kartu Setelan Cepat akan direset ke setelan asli perangkat"</string> - <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) --> - <skip /> + <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string> </resources> diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml index faff4c59f275..5c37bdb40161 100644 --- a/packages/SystemUI/res/values-is/strings.xml +++ b/packages/SystemUI/res/values-is/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Tengt við <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Tengt við <xliff:g id="CAST">%s</xliff:g>."</string> <string name="accessibility_expand_group" msgid="521237935987978624">"Stækka hóp."</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Bæta tæki við hóp."</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Fjarlægja tæki úr hóp."</string> <string name="accessibility_open_application" msgid="1749126077501259712">"Opna forrit."</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"Engin tenging."</string> <string name="data_connection_roaming" msgid="375650836665414797">"Reiki"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Inntak"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Heyrnartæki"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Kveikir…"</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Ekki er hægt að breyta birtustiginu vegna þess að efsta forritið stjórnar því"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Sjálfvirkur snúningur"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Snúa skjá sjálfkrafa"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"Staðsetning"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Bæta við í stöðu <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Staða ógild."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Staða <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Reit bætt við"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Reitur fjarlægður"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Flýtistillingaritill."</string> @@ -1574,6 +1573,5 @@ <string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Óþekkt"</string> <string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Endurstilla alla reiti?"</string> <string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Allir flýtistillingareitir munu endurstillast á upprunalegar stillingar tækisins"</string> - <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) --> - <skip /> + <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string> </resources> diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml index e6f5dee09ce3..a21b6dbac247 100644 --- a/packages/SystemUI/res/values-it/strings.xml +++ b/packages/SystemUI/res/values-it/strings.xml @@ -1003,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Aggiungi alla posizione <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Posizione non valida."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Posizione <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Riquadro aggiunto"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Riquadro rimosso"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Editor di impostazioni rapide."</string> diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml index 8e2df321d0fd..936b10e520d5 100644 --- a/packages/SystemUI/res/values-iw/strings.xml +++ b/packages/SystemUI/res/values-iw/strings.xml @@ -1006,6 +1006,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"הוספה למיקום <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"המיקום לא תקין."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"מיקום <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"הלחצן נוסף"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"הלחצן הוסר"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"עורך הגדרות מהירות."</string> @@ -1574,6 +1576,5 @@ <string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"לא ידוע"</string> <string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"לאפס את כל הלחצנים?"</string> <string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"כל הלחצנים ב\'הגדרות מהירות\' יאופסו להגדרות המקוריות של המכשיר"</string> - <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) --> - <skip /> + <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string> </resources> diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml index 5daa645ec0a8..d5ecfe538b89 100644 --- a/packages/SystemUI/res/values-ja/strings.xml +++ b/packages/SystemUI/res/values-ja/strings.xml @@ -1003,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"ポジション <xliff:g id="POSITION">%1$d</xliff:g> に追加"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"位置が無効です。"</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"位置: <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"タイルを追加しました"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"タイルを削除しました"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"クイック設定エディタ"</string> diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml index 68f48b00f3ca..2e3dc98eb153 100644 --- a/packages/SystemUI/res/values-ka/strings.xml +++ b/packages/SystemUI/res/values-ka/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"დაკავშირებულია <xliff:g id="BLUETOOTH">%s</xliff:g>-თან."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"დაკავშირებულია მოწყობილობასთან: <xliff:g id="CAST">%s</xliff:g>."</string> <string name="accessibility_expand_group" msgid="521237935987978624">"ჯგუფის გაფართოება."</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"მოწყობილობის ჯგუფში დამატება."</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"მოწყობილობის ჯგუფიდან ამოშლა."</string> <string name="accessibility_open_application" msgid="1749126077501259712">"აპლიკაციის გახსნა."</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"არ არის დაკავშირებული."</string> <string name="data_connection_roaming" msgid="375650836665414797">"როუმინგი"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"შეყვანა"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"სმენის მოწყობილობები"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"ირთვება…"</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"სიკაშკაშის კორექტირება ვერ ხერხდება, რადგან ის იმართება გახსნილი აპის მიერ"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"ავტოროტაცია"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"ეკრანის ავტომატური შეტრიალება"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"მდებარეობა"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"დამატება პოზიციაზე <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"პოზიცია არასწორია."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"პოზიცია <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"მოზაიკის ფილა დაემატა"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"მოზაიკის ფილა ამოიშალა"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"სწრაფი პარამეტრების რედაქტორი."</string> @@ -1574,6 +1573,5 @@ <string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"უცნობი"</string> <string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"გსურთ ყველა ფილის გადაყენება?"</string> <string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"სწრაფი პარამეტრების ყველა ფილა გადაყენდება მოწყობილობის ორიგინალ პარამეტრებზე"</string> - <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) --> - <skip /> + <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string> </resources> diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml index 1ecc7438b088..1c4d31a6b758 100644 --- a/packages/SystemUI/res/values-kk/strings.xml +++ b/packages/SystemUI/res/values-kk/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> қосылған."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> трансляциясына қосылды."</string> <string name="accessibility_expand_group" msgid="521237935987978624">"Топты жайыңыз."</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Құрылғыны топқа қосады."</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Құрылғыны топтан өшіреді."</string> <string name="accessibility_open_application" msgid="1749126077501259712">"Қолданбаны ашыңыз."</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"Жалғанбаған."</string> <string name="data_connection_roaming" msgid="375650836665414797">"Роуминг"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Кіріс"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Есту аппараттары"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Қосылып жатыр…"</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Жарықтықты реттеу мүмкін емес, себебі ол жетекші қолданба арқылы басқарылады."</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Автоматты түрде бұру"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Автоматты айналатын экран"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"Локация"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g> орнына қосу"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Орын жарамсыз."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"<xliff:g id="POSITION">%1$d</xliff:g> орны"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Бөлшек қосылды."</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Бөлшек өшірілді."</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Жылдам параметрлер өңдегіші."</string> @@ -1574,6 +1573,5 @@ <string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Белгісіз"</string> <string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Барлық бөлшекті бастапқы күйге қайтару керек пе?"</string> <string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Барлық \"Жылдам параметрлер\" бөлшегі құрылғының бастапқы параметрлеріне қайтарылады."</string> - <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) --> - <skip /> + <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string> </resources> diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml index 8c5ea900a97a..8b3bfcc1d30b 100644 --- a/packages/SystemUI/res/values-km/strings.xml +++ b/packages/SystemUI/res/values-km/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"បានភ្ជាប់ទៅ <xliff:g id="BLUETOOTH">%s</xliff:g> ។"</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"បានភ្ជាប់ទៅ <xliff:g id="CAST">%s</xliff:g>"</string> <string name="accessibility_expand_group" msgid="521237935987978624">"ពង្រីកក្រុម។"</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"បញ្ចូលឧបករណ៍ទៅក្រុម។"</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"ដកឧបករណ៍ចេញពីក្រុម។"</string> <string name="accessibility_open_application" msgid="1749126077501259712">"បើកកម្មវិធី។"</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"មិនបានតភ្ជាប់។"</string> <string name="data_connection_roaming" msgid="375650836665414797">"រ៉ូមីង"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"បញ្ចូល"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"ឧបករណ៍ជំនួយការស្ដាប់"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"កំពុងបើក..."</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"មិនអាចកែតម្រូវកម្រិតពន្លឺបានទេ ដោយសារវាកំពុងស្ថិតក្រោមការគ្រប់គ្រងរបស់កម្មវិធីខាងលើគេ"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"បង្វិលស្វ័យប្រវត្តិ"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"បង្វិលអេក្រង់ស្វ័យប្រវត្តិ"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"ទីតាំង"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"បញ្ចូលទៅទីតាំងទី <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"ទីតាំងគ្មានសុពលភាព។"</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"ទីតាំងទី <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"បានបញ្ចូលប្រអប់"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"បានផ្លាស់ទីប្រអប់"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"កម្មវិធីកែការកំណត់រហ័ស"</string> diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml index 7c785d95ae38..50f8c60537ba 100644 --- a/packages/SystemUI/res/values-kn/strings.xml +++ b/packages/SystemUI/res/values-kn/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> ಗೆ ಸಂಪರ್ಕಪಡಿಸಲಾಗಿದೆ."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> ಗೆ ಸಂಪರ್ಕಿಸಲಾಗಿದೆ."</string> <string name="accessibility_expand_group" msgid="521237935987978624">"ಗುಂಪು ವಿಸ್ತರಿಸಿ."</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"ಸಾಧನವನ್ನು ಗುಂಪಿಗೆ ಸೇರಿಸಿ."</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"ಸಾಧನವನ್ನು ಗುಂಪಿನಿಂದ ತೆಗೆದುಹಾಕಿ."</string> <string name="accessibility_open_application" msgid="1749126077501259712">"ಅಪ್ಲಿಕೇಶನ್ ತೆರೆಯಿರಿ."</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"ಸಂಪರ್ಕಗೊಂಡಿಲ್ಲ."</string> <string name="data_connection_roaming" msgid="375650836665414797">"ರೋಮಿಂಗ್"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"ಇನ್ಪುಟ್"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"ಶ್ರವಣ ಸಾಧನಗಳು"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"ಆನ್ ಮಾಡಲಾಗುತ್ತಿದೆ..."</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"ಬ್ರೈಟ್ನೆಸ್ ಅನ್ನು ಹೊಂದಾಣಿಕೆ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ, ಏಕೆಂದರೆ ಅದನ್ನು ಟಾಪ್ ಆ್ಯಪ್ ನಿಯಂತ್ರಿಸುತ್ತಿದೆ"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"ಸ್ವಯಂ-ತಿರುಗುವಿಕೆ"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"ಪರದೆಯನ್ನು ಸ್ವಯಂ-ತಿರುಗಿಸಿ"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"ಸ್ಥಳ"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g> ಸ್ಥಾನಕ್ಕೆ ಸೇರಿಸಿ"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"ಸ್ಥಾನವು ಅಮಾನ್ಯವಾಗಿದೆ."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"ಸ್ಥಾನ <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"ಟೈಲ್ ಸೇರಿಸಲಾಗಿದೆ"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"ಟೈಲ್ ತೆಗೆದುಹಾಕಲಾಗಿದೆ"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"ತ್ವರಿತ ಸೆಟ್ಟಿಂಗ್ಗಳ ಎಡಿಟರ್."</string> @@ -1574,6 +1573,5 @@ <string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"ಅಪರಿಚಿತ"</string> <string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"ಎಲ್ಲಾ ಟೈಲ್ಗಳನ್ನು ರೀಸೆಟ್ ಮಾಡಬೇಕೆ?"</string> <string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"ಎಲ್ಲಾ ತ್ವರಿತ ಸೆಟ್ಟಿಂಗ್ಗಳ ಟೈಲ್ಗಳನ್ನು ಸಾಧನದ ಮೂಲ ಸೆಟ್ಟಿಂಗ್ಗಳಿಗೆ ರೀಸೆಟ್ ಮಾಡಲಾಗುತ್ತದೆ"</string> - <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) --> - <skip /> + <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string> </resources> diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml index b3c21b11bf28..7342f32f116e 100644 --- a/packages/SystemUI/res/values-ko/strings.xml +++ b/packages/SystemUI/res/values-ko/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g>에 연결되었습니다."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g>에 연결됨"</string> <string name="accessibility_expand_group" msgid="521237935987978624">"그룹을 펼칩니다."</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"기기를 그룹에 추가합니다."</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"그룹에서 기기를 삭제합니다."</string> <string name="accessibility_open_application" msgid="1749126077501259712">"애플리케이션을 엽니다."</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"연결되지 않았습니다."</string> <string name="data_connection_roaming" msgid="375650836665414797">"로밍"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"입력"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"보청기"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"켜는 중..."</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"상위 앱에서 밝기를 제어하고 있으므로 밝기를 조절할 수 없습니다."</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"자동 회전"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"화면 자동 회전"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"위치"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g> 위치에 추가"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"위치가 잘못되었습니다."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"<xliff:g id="POSITION">%1$d</xliff:g> 위치"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"타일 추가됨"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"타일 삭제됨"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"빠른 설정 편집기"</string> @@ -1574,6 +1573,5 @@ <string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"알 수 없음"</string> <string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"모든 타일을 재설정하시겠습니까?"</string> <string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"모든 빠른 설정 타일이 기기의 원래 설정으로 재설정됩니다."</string> - <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) --> - <skip /> + <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string> </resources> diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml index 9aedbb5e0569..f21ee1a401b7 100644 --- a/packages/SystemUI/res/values-ky/strings.xml +++ b/packages/SystemUI/res/values-ky/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> менен туташкан."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> менен туташты."</string> <string name="accessibility_expand_group" msgid="521237935987978624">"Топту жайып көрсөтүү."</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Түзмөктү топко кошуңуз."</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Түзмөктү топтон алып салыңыз."</string> <string name="accessibility_open_application" msgid="1749126077501259712">"Колдонмону ачуу."</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"Интернет жок."</string> <string name="data_connection_roaming" msgid="375650836665414797">"Роуминг"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Киргизүү"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Угуу аппараттары"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Күйгүзүлүүдө…"</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Жарыктыкты тууралоого болбойт, анткени аны жогорку колдонмо көзөмөлдөйт"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Авто буруу"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Экранды авто буруу"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"Жайгашкан жер"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g>-позицияга кошуу"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Абал жараксыз."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"<xliff:g id="POSITION">%1$d</xliff:g>-позиция"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Карта кошулду"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Карта өчүрүлдү"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Ыкчам параметрлер түзөткүчү."</string> @@ -1574,6 +1573,5 @@ <string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Белгисиз"</string> <string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Бардык параметрлерди кайра коесузбу?"</string> <string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Бардык ыкчам параметрлер түзмөктүн баштапкы маанилерине кайтарылат"</string> - <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) --> - <skip /> + <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string> </resources> diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml index 47b32354df40..4c778d1ac869 100644 --- a/packages/SystemUI/res/values-lo/strings.xml +++ b/packages/SystemUI/res/values-lo/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"ເຊື່ອມຕໍ່ຫາ <xliff:g id="BLUETOOTH">%s</xliff:g> ແລ້ວ."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"ເຊື່ອມຕໍ່ຫາ <xliff:g id="CAST">%s</xliff:g> ແລ້ວ."</string> <string name="accessibility_expand_group" msgid="521237935987978624">"ຂະຫຍາຍກຸ່ມ."</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"ເພີ່ມອຸປະກອນໃສ່ໃນກຸ່ມ."</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"ລຶບອຸປະກອນອອກຈາກກຸ່ມ."</string> <string name="accessibility_open_application" msgid="1749126077501259712">"ເປີດແອັບພລິເຄຊັນ."</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"ບໍ່ໄດ້ເຊື່ອມຕໍ່."</string> <string name="data_connection_roaming" msgid="375650836665414797">"ໂຣມມິງ"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"ການປ້ອນຂໍ້ມູນ"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"ເຄື່ອງຊ່ວຍຟັງ"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"ກຳລັງເປີດ..."</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"ບໍ່ສາມາດປັບຄວາມສະຫວ່າງໄດ້ເນື່ອງຈາກຄວບຄຸມໂດຍແອັບທາງເທິງ"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"ໝຸນອັດຕະໂນມັດ"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"ໝຸນໜ້າຈໍອັດຕະໂນມັດ"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"ສະຖານທີ່"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"ເພີ່ມໃສ່ຕຳແໜ່ງ <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"ຕຳແໜ່ງບໍ່ຖືກຕ້ອງ."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"ຕຳແໜ່ງ <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"ເພີ່ມແຜ່ນແລ້ວ"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"ລຶບແຜ່ນແລ້ວ"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"ຕົວແກ້ໄຂການຕັ້ງຄ່າດ່ວນ"</string> @@ -1574,6 +1573,5 @@ <string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"ບໍ່ຮູ້ຈັກ"</string> <string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"ຣີເຊັດແຜ່ນທັງໝົດບໍ?"</string> <string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"ແຜ່ນການຕັ້ງຄ່າດ່ວນທັງໝົດຈະຣີເຊັດເປັນການຕັ້ງຄ່າແບບເກົ່າຂອງອຸປະກອນ"</string> - <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) --> - <skip /> + <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string> </resources> diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml index 5971e730ac74..f387a4ed4178 100644 --- a/packages/SystemUI/res/values-lt/strings.xml +++ b/packages/SystemUI/res/values-lt/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Prisijungta prie „<xliff:g id="BLUETOOTH">%s</xliff:g>“."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Prisijungta prie <xliff:g id="CAST">%s</xliff:g>."</string> <string name="accessibility_expand_group" msgid="521237935987978624">"Išskleisti grupę."</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Pridėti įrenginį prie grupės."</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Pašalinti įrenginį iš grupės."</string> <string name="accessibility_open_application" msgid="1749126077501259712">"Atidaryti programą."</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"Neprijungta."</string> <string name="data_connection_roaming" msgid="375650836665414797">"Tarptinklinis ryšys"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Įvestis"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Klausos aparatai"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Įjungiama…"</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Negalima koreguoti šviesumo, nes jį valdo viršuje esanti programa"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Automatinis pasukimas"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Automatiškai sukti ekraną"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"Vietovė"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Pridėkite <xliff:g id="POSITION">%1$d</xliff:g> pozicijoje"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Padėtis netinkama."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"<xliff:g id="POSITION">%1$d</xliff:g> pozicija"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Išklotinė pridėta"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Išklotinė pašalinta"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Sparčiųjų nustatymų redagavimo priemonė."</string> diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml index bb8d7028fce4..fd549219134f 100644 --- a/packages/SystemUI/res/values-lv/strings.xml +++ b/packages/SystemUI/res/values-lv/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Ir izveidots savienojum ar <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Savienots ar ierīci <xliff:g id="CAST">%s</xliff:g>."</string> <string name="accessibility_expand_group" msgid="521237935987978624">"Izvērst grupu."</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Pievienot ierīci grupai."</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Noņemt ierīci no grupas."</string> <string name="accessibility_open_application" msgid="1749126077501259712">"Atvērt lietojumprogrammu."</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"Savienojums nav izveidots."</string> <string name="data_connection_roaming" msgid="375650836665414797">"Viesabonēšana"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Ievade"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Dzirdes aparāti"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Notiek ieslēgšana…"</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Nevar mainīt spilgtumu, jo to kontrolē lietotne augšpusē"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Automātiska pagriešana"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Automātiska ekrāna pagriešana"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"Atrašanās vieta"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Pievienot elementu pozīcijā numur <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Nederīga pozīcija."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Pozīcija numur <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Elements ir pievienots"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Elements ir noņemts"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Ātro iestatījumu redaktors."</string> @@ -1574,6 +1573,5 @@ <string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Nezināma"</string> <string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Vai atiestatīt visus elementus?"</string> <string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Visiem ātro iestatījumu elementiem tiks atiestatīti sākotnējie iestatījumi"</string> - <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) --> - <skip /> + <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string> </resources> diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml index 189390ff9edc..2afe93ea7aae 100644 --- a/packages/SystemUI/res/values-mk/strings.xml +++ b/packages/SystemUI/res/values-mk/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Поврзано со <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Поврзано со <xliff:g id="CAST">%s</xliff:g>."</string> <string name="accessibility_expand_group" msgid="521237935987978624">"Проширете ја групата."</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Додај го уредот во групата."</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Отстрани го уредот од групата."</string> <string name="accessibility_open_application" msgid="1749126077501259712">"Отворете ја апликацијата."</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"Не е поврзана"</string> <string name="data_connection_roaming" msgid="375650836665414797">"Роаминг"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Влез"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Слушни помагала"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Се вклучува…"</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Не може да се приспособи осветленоста бидејќи е контролирана од горната апликација"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Автоматско ротирање"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Автоматско ротирање на екранот"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"Локација"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Додавање на позиција <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Позицијата е погрешна."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Позиција <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Додадена е плочка"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Отстранета е плочка"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Уредник за брзи поставки."</string> @@ -1574,6 +1573,5 @@ <string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Непознато"</string> <string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Да се ресетираат сите плочки?"</string> <string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Сите плочки на „Брзи поставки“ ќе се ресетираат на првичните поставки на уредот"</string> - <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) --> - <skip /> + <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string> </resources> diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml index 7e4282f0f70f..48d07cb2ae28 100644 --- a/packages/SystemUI/res/values-ml/strings.xml +++ b/packages/SystemUI/res/values-ml/strings.xml @@ -1003,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g> എന്ന സ്ഥാനത്തേക്ക് ചേർക്കുക"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"സ്ഥാനം അസാധുവാണ്."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"സ്ഥാനം <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"ടൈൽ ചേർത്തു"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"ടൈൽ നീക്കം ചെയ്തു"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"ദ്രുത ക്രമീകരണ എഡിറ്റർ."</string> @@ -1571,6 +1573,5 @@ <string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"അജ്ഞാതം"</string> <string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"എല്ലാ ടൈലുകളും റീസെറ്റ് ചെയ്യണോ?"</string> <string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"എല്ലാ ദ്രുത ക്രമീകരണ ടൈലുകളും ഉപകരണത്തിന്റെ ഒറിജിനൽ ക്രമീകരണത്തിലേക്ക് റീസെറ്റ് ചെയ്യും"</string> - <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) --> - <skip /> + <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string> </resources> diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml index c6ab2c5a5368..c7fb7da28ead 100644 --- a/packages/SystemUI/res/values-mn/strings.xml +++ b/packages/SystemUI/res/values-mn/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g>-тай холбогдсон."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g>-д холбогдсон."</string> <string name="accessibility_expand_group" msgid="521237935987978624">"Бүлгийг дэлгэнэ үү."</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Бүлэгт төхөөрөмж нэмнэ."</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Бүлгээс төхөөрөмж хасна."</string> <string name="accessibility_open_application" msgid="1749126077501259712">"Аппликейшныг нээнэ үү."</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"Холбогдоогүй."</string> <string name="data_connection_roaming" msgid="375650836665414797">"Роуминг"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Оролт"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Сонсголын төхөөрөмж"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Асааж байна…"</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Гэрэлтүүлгийг давуу эрхтэй аппаас хянаж байгаа тул тохируулах боломжгүй"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Автоматаар эргэх"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Дэлгэцийг автоматаар эргүүлэх"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"Байршил"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g> байрлалд нэмнэ үү"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Байрлал буруу байна."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"<xliff:g id="POSITION">%1$d</xliff:g> байрлал"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Хавтан нэмсэн"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Хавтанг хассан"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Шуурхай тохиргоо засварлагч."</string> @@ -1574,6 +1573,5 @@ <string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Тодорхойгүй"</string> <string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Бүх хавтанг шинэчлэх үү?"</string> <string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Шуурхай тохиргооны бүх хавтан төхөөрөмжийн эх тохиргоо руу шинэчлэгдэнэ"</string> - <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) --> - <skip /> + <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string> </resources> diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml index 9fa40510eed0..2c5a9a85845d 100644 --- a/packages/SystemUI/res/values-mr/strings.xml +++ b/packages/SystemUI/res/values-mr/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> शी कनेक्ट केले."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> शी कनेक्ट केले."</string> <string name="accessibility_expand_group" msgid="521237935987978624">"गटाचा विस्तार करा."</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"डिव्हाइस गटामध्ये जोडा."</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"डिव्हाइस गटामधून काढून टाका."</string> <string name="accessibility_open_application" msgid="1749126077501259712">"अॅप उघडा."</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"कनेक्ट केले नाही."</string> <string name="data_connection_roaming" msgid="375650836665414797">"रोमिंग"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"इनपुट"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"श्रवणयंत्रे"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"सुरू करत आहे…"</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"ब्राइटनेस टॉप ॲपद्वारे नियंत्रित केला जात असल्यामुळे ॲडजस्ट करू शकत नाही"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"ऑटो-रोटेट"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"ऑटो-रोटेट स्क्रीन"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"स्थान"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g> स्थानावर जोडा"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"स्थान चुकीचे आहे."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"स्थान <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"टाइल जोडली"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"टाइल काढून टाकली"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"द्रुत सेटिंग्ज संपादक."</string> @@ -1574,6 +1573,5 @@ <string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"अज्ञात"</string> <string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"सर्व टाइल रीसेट करायच्या?"</string> <string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"सर्व क्विक सेटिंग्ज टाइल डिव्हाइसच्या मूळ सेटिंग्जवर रीसेट केल्या जातील"</string> - <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) --> - <skip /> + <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string> </resources> diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml index 91e7e6340497..557509860f07 100644 --- a/packages/SystemUI/res/values-ms/strings.xml +++ b/packages/SystemUI/res/values-ms/strings.xml @@ -1003,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Tambahkan pada kedudukan <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Kedudukan tidak sah."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Kedudukan <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Jubin ditambah"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Jubin dialih keluar"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Editor tetapan pantas."</string> @@ -1571,6 +1573,5 @@ <string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Tidak diketahui"</string> <string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Tetapkan semula semua jubin?"</string> <string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Semua jubin Tetapan Pantas akan ditetapkan semula kepada tetapan asal peranti"</string> - <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) --> - <skip /> + <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string> </resources> diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml index 703562e0be12..b428422e5f07 100644 --- a/packages/SystemUI/res/values-my/strings.xml +++ b/packages/SystemUI/res/values-my/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g>သို့ ချိတ်ဆက်ထား"</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> သို့ချိတ်ဆက်ထားပါသည်။"</string> <string name="accessibility_expand_group" msgid="521237935987978624">"အုပ်စုကို ပိုပြသည်။"</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"အဖွဲ့သို့ စက်ပစ္စည်းထည့်ရန်။"</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"အဖွဲ့မှ စက်ပစ္စည်းကို ဖယ်ရှားရန်။"</string> <string name="accessibility_open_application" msgid="1749126077501259712">"အပလီကေးရှင်းကို ဖွင့်သည်။"</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"ချိတ်ဆက်မထားပါ"</string> <string name="data_connection_roaming" msgid="375650836665414797">"ပြင်ပကွန်ရက်သုံးခြင်း"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"အဝင်"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"နားကြားကိရိယာ"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"ဖွင့်နေသည်…"</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"အပေါ်အက်ပ်မှ ထိန်းချုပ်ထားသောကြောင့် တောက်ပမှုကို ချိန်ညှိ၍ မရပါ"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"အော်တို-လည်"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"မျက်နှာပြင်အား အလိုအလျောက်လှည့်ခြင်း"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"တည်နေရာ"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g> အနေအထားသို့ ပေါင်းထည့်ရန်"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"နေရာ မမှန်ပါ။"</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"<xliff:g id="POSITION">%1$d</xliff:g> အနေအထား"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"အကွက်ငယ်ကို ထည့်ပြီးပါပြီ"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"အကွက်ငယ်ကို ဖယ်ရှားပြီးပါပြီ"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"မြန်ဆန်သည့် ဆက်တင်တည်းဖြတ်စနစ်"</string> @@ -1574,6 +1573,5 @@ <string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"အမျိုးအမည်မသိ"</string> <string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"အကွက်ငယ်အားလုံးကို ပြင်ဆင်သတ်မှတ်မလား။"</string> <string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"အမြန်ဆက်တင်များ အကွက်ငယ်အားလုံးကို စက်ပစ္စည်း၏ မူရင်းဆက်တင်များသို့ ပြင်ဆင်သတ်မှတ်ပါမည်"</string> - <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) --> - <skip /> + <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>၊ <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string> </resources> diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml index b75f644ec397..95740758c6e7 100644 --- a/packages/SystemUI/res/values-nb/strings.xml +++ b/packages/SystemUI/res/values-nb/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Koblet til <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Koblet til <xliff:g id="CAST">%s</xliff:g>."</string> <string name="accessibility_expand_group" msgid="521237935987978624">"Utvid gruppen."</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Legg til enheten i gruppen."</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Fjern enheten fra gruppen."</string> <string name="accessibility_open_application" msgid="1749126077501259712">"Åpne appen."</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"Ikke tilkoblet."</string> <string name="data_connection_roaming" msgid="375650836665414797">"Roaming"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Innenhet"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Høreapparater"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Slår på …"</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Kan ikke justere lysstyrken, fordi den kontrolleres av den øvre appen"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Rotér automatisk"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Rotér skjermen automatisk"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"Sted"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Legg til posisjonen <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Posisjonen er ugyldig."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Posisjon <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"En infobrikke er lagt til"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"En infobrikke er fjernet"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Redigeringsvindu for hurtiginnstillinger."</string> @@ -1574,6 +1573,5 @@ <string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Ukjent"</string> <string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Vil du tilbakestille alle brikkene?"</string> <string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Alle brikker for hurtiginnstillinger tilbakestilles til enhetens opprinnelige innstillinger"</string> - <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) --> - <skip /> + <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string> </resources> diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml index cd20defc7849..4d37f8fd760b 100644 --- a/packages/SystemUI/res/values-ne/strings.xml +++ b/packages/SystemUI/res/values-ne/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> मा जडित।"</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> मा कनेक्ट गरियो।"</string> <string name="accessibility_expand_group" msgid="521237935987978624">"समूह एक्स्पान्ड गर्नुहोस्।"</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"समूहमा डिभाइस हाल्नुहोस्।"</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"समूहबाट डिभाइस हटाउनुहोस्।"</string> <string name="accessibility_open_application" msgid="1749126077501259712">"एप खोल्नुहोस्।"</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"जडान नगरिएको।"</string> <string name="data_connection_roaming" msgid="375650836665414797">"रोमिङ"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"इनपुट"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"हियरिङ डिभाइसहरू"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"सक्रिय गर्दै…"</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"सिरानको एपले चमक नियन्त्रण गरिरहेकाले चमक मिलाउन मिल्दैन"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"अटो रोटेट"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"स्क्रिन स्वतःघुम्ने"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"लोकेसन"</string> @@ -997,8 +994,7 @@ </string-array> <string name="tuner_low_priority" msgid="8412666814123009820">"कम प्राथमिकताका सूचना आइकनहरू देखाउनुहोस्"</string> <string name="other" msgid="429768510980739978">"अन्य"</string> - <!-- no translation found for accessibility_qs_edit_toggle_tile_size_action (1485194410119733586) --> - <skip /> + <string name="accessibility_qs_edit_toggle_tile_size_action" msgid="1485194410119733586">"टाइलको आकार टगल गर्नुहोस्"</string> <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"टाइल हटाउनुहोस्"</string> <string name="accessibility_qs_edit_tile_add_action" msgid="8311378984458545661">"अन्तिम स्थानमा टाइल हाल्नुहोस्"</string> <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"टाइल सार्नुहोस्"</string> @@ -1007,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"टाइल यो अवस्था <xliff:g id="POSITION">%1$d</xliff:g> मा हाल्नुहोस्"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"पोजिसन अवैध छ।"</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"स्थिति <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"टाइल हालियो"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"टाइल हटाइयो"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"द्रुत सेटिङ सम्पादक।"</string> @@ -1551,10 +1549,8 @@ <string name="overview_edu_toast_content" msgid="5797030644017804518">"आफूले हालसालै चलाएका एपहरू हेर्न तीन वटा औँलाले टचप्याडमा माथितिर स्वाइप गर्नुहोस् र होल्ड गर्नुहोस्"</string> <string name="all_apps_edu_toast_content" msgid="8807496014667211562">"आफ्ना सबै एपहरू हेर्न आफ्नो किबोर्डमा भएको एक्सन की थिच्नुहोस्"</string> <string name="redacted_notification_single_line_title" msgid="212019960919261670">"जानकारी लुकाउन सम्पादन गरिएको"</string> - <!-- no translation found for public_notification_single_line_text (3576190291791654933) --> - <skip /> - <!-- no translation found for redacted_otp_notification_single_line_text (5179964116354454118) --> - <skip /> + <string name="public_notification_single_line_text" msgid="3576190291791654933">"हेर्न अनलक गर्नुहोस्"</string> + <string name="redacted_otp_notification_single_line_text" msgid="5179964116354454118">"कोड हेर्न अनलक गर्नुहोस्"</string> <string name="contextual_education_dialog_title" msgid="4630392552837487324">"सान्दर्भिक शिक्षा"</string> <string name="back_edu_notification_title" msgid="5624780717751357278">"पछाडि जान आफ्नो टचप्याड प्रयोग गर्नुहोस्"</string> <string name="back_edu_notification_content" msgid="2497557451540954068">"तीन वटा औँला प्रयोग गरी बायाँ वा दायाँतिर स्वाइप गर्नुहोस्। थप जेस्चर प्रयोग गर्ने तरिका सिक्न ट्याप गर्नुहोस्।"</string> @@ -1577,6 +1573,5 @@ <string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"अज्ञात"</string> <string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"सबै टाइलहरू रिसेट गर्ने हो?"</string> <string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"द्रुत सेटिङका सबै टाइलहरू रिसेट गरी डिभाइसका मूल सेटिङ लागू गरिने छन्"</string> - <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) --> - <skip /> + <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string> </resources> diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml index 1df27df11b86..26d87522595b 100644 --- a/packages/SystemUI/res/values-nl/strings.xml +++ b/packages/SystemUI/res/values-nl/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Verbonden met <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Verbonden met <xliff:g id="CAST">%s</xliff:g>."</string> <string name="accessibility_expand_group" msgid="521237935987978624">"Groep uitvouwen."</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Voeg het apparaat aan de groep toe."</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Verwijder het apparaat uit de groep."</string> <string name="accessibility_open_application" msgid="1749126077501259712">"App openen."</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"Niet verbonden."</string> <string name="data_connection_roaming" msgid="375650836665414797">"Roaming"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Invoer"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Hoortoestellen"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Aanzetten…"</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Kan de helderheid niet aanpassen omdat deze wordt beheerd door de bovenste app"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Automatisch draaien"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Scherm automatisch draaien"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"Locatie"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Toevoegen aan positie <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Positie ongeldig."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Positie <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Tegel toegevoegd"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Tegel verwijderd"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Editor voor \'Snelle instellingen\'."</string> diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml index ddb9157c3755..a0e360cf6bcb 100644 --- a/packages/SystemUI/res/values-or/strings.xml +++ b/packages/SystemUI/res/values-or/strings.xml @@ -1003,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g> ଅବସ୍ଥିତିରେ ଯୋଗ କରନ୍ତୁ"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"ଅବସ୍ଥିତି ଅବୈଧ ଅଟେ।"</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"ଅବସ୍ଥିତି <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"ଟାଇଲ୍ ଯୋଗ କରାଯାଇଛି"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"ଟାଇଲ୍ କାଢ଼ି ଦିଆଯାଇଛି"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"ଦ୍ରୁତ ସେଟିଙ୍ଗ ଏଡିଟର୍।"</string> @@ -1571,6 +1573,5 @@ <string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"ଅଜଣା"</string> <string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"ସମସ୍ତ ଟାଇଲକୁ ରିସେଟ କରିବେ?"</string> <string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"ସମସ୍ତ କୁଇକ ସେଟିଂସ ଟାଇଲ ଡିଭାଇସର ମୂଳ ସେଟିଂସରେ ରିସେଟ ହୋଇଯିବ"</string> - <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) --> - <skip /> + <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string> </resources> diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml index 39c02d245a89..f2a6612bdb8a 100644 --- a/packages/SystemUI/res/values-pa/strings.xml +++ b/packages/SystemUI/res/values-pa/strings.xml @@ -1003,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g> ਸਥਾਨ \'ਤੇ ਸ਼ਾਮਲ ਕਰੋ"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"ਮੌਜੂਦਾ ਥਾਂ ਅਵੈਧ ਹੈ।"</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"ਸਥਾਨ <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"ਟਾਇਲ ਨੂੰ ਸ਼ਾਮਲ ਕੀਤਾ ਗਿਆ"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"ਟਾਇਲ ਨੂੰ ਹਟਾ ਦਿੱਤਾ ਗਿਆ"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"ਤਤਕਾਲ ਸੈਟਿੰਗਾਂ ਸੰਪਾਦਕ।"</string> @@ -1571,6 +1573,5 @@ <string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"ਅਗਿਆਤ"</string> <string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"ਕੀ ਸਾਰੀਆਂ ਟਾਇਲਾਂ ਨੂੰ ਰੀਸੈੱਟ ਕਰਨਾ ਹੈ?"</string> <string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"ਸਾਰੀਆਂ ਤਤਕਾਲ ਸੈਟਿੰਗਾਂ ਟਾਇਲਾਂ ਡੀਵਾਈਸ ਦੀਆਂ ਮੂਲ ਸੈਟਿੰਗਾਂ \'ਤੇ ਰੀਸੈੱਟ ਹੋ ਜਾਣਗੀਆਂ"</string> - <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) --> - <skip /> + <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string> </resources> diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml index b93bf3074ab0..99e99e1a2e6a 100644 --- a/packages/SystemUI/res/values-pl/strings.xml +++ b/packages/SystemUI/res/values-pl/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Połączono z <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Połączono z urządzeniem <xliff:g id="CAST">%s</xliff:g>."</string> <string name="accessibility_expand_group" msgid="521237935987978624">"Rozwiń grupę."</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Dodaj urządzenie do grupy."</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Usuń urządzenie z grupy."</string> <string name="accessibility_open_application" msgid="1749126077501259712">"Otwórz aplikację."</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"Nie połączono."</string> <string name="data_connection_roaming" msgid="375650836665414797">"Roaming"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Wejście"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Aparaty słuchowe"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Włączam…"</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Nie można dostosować jasności, ponieważ reguluje ją aplikacja na pierwszym planie"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Autoobracanie"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Autoobracanie ekranu"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"Lokalizacja"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Dodaj w pozycji <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Nieprawidłowa pozycja."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Pozycja <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Dodano kartę"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Usunięto kartę"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Edytor szybkich ustawień."</string> @@ -1574,6 +1573,5 @@ <string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Nieznane"</string> <string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Zresetować wszystkie kafelki?"</string> <string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Wszystkie kafelki Szybkich ustawień zostaną zresetowane do oryginalnych ustawień urządzenia"</string> - <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) --> - <skip /> + <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string> </resources> diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml index 7c9028ba068d..4663dba04bc2 100644 --- a/packages/SystemUI/res/values-pt-rBR/strings.xml +++ b/packages/SystemUI/res/values-pt-rBR/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Conectado a <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Conectado a <xliff:g id="CAST">%s</xliff:g>."</string> <string name="accessibility_expand_group" msgid="521237935987978624">"Expandir grupo."</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Adicionar dispositivo ao grupo."</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Remover dispositivo do grupo."</string> <string name="accessibility_open_application" msgid="1749126077501259712">"Abrir aplicativo."</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"Sem conexão."</string> <string name="data_connection_roaming" msgid="375650836665414797">"Roaming"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Entrada"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Aparelhos auditivos"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Ativando…"</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Não é possível ajustar o brilho, porque ele está sendo controlado pelo app principal"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Giro automático"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Giro automático da tela"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"Localização"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Adicionar à posição <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Posição inválida."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Posição <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Bloco adicionado"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Bloco removido"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Editor de configurações rápidas."</string> @@ -1574,6 +1573,5 @@ <string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Desconhecidos"</string> <string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Redefinir todos os blocos?"</string> <string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Todos os blocos \"Configurações rápidas\" serão redefinidos para as configurações originais do dispositivo"</string> - <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) --> - <skip /> + <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string> </resources> diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml index a8e0f05c971c..7a0e4f6e68b2 100644 --- a/packages/SystemUI/res/values-pt-rPT/strings.xml +++ b/packages/SystemUI/res/values-pt-rPT/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Ligado a <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Ligado a <xliff:g id="CAST">%s</xliff:g>."</string> <string name="accessibility_expand_group" msgid="521237935987978624">"Expanda o grupo."</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Adicionar dispositivo ao grupo."</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Remover dispositivo do grupo."</string> <string name="accessibility_open_application" msgid="1749126077501259712">"Abra a aplicação."</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"Sem ligação."</string> <string name="data_connection_roaming" msgid="375650836665414797">"Roaming"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Entrada"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Aparelhos auditivos"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"A ativar..."</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Não é possível ajustar o brilho porque está a ser controlado pela app principal"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Rotação auto."</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Rodar o ecrã automaticamente"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"Localização"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Adicione à posição <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Posição inválida."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Posição <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Cartão adicionado"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Cartão removido"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Editor de definições rápidas."</string> diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml index 7c9028ba068d..4663dba04bc2 100644 --- a/packages/SystemUI/res/values-pt/strings.xml +++ b/packages/SystemUI/res/values-pt/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Conectado a <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Conectado a <xliff:g id="CAST">%s</xliff:g>."</string> <string name="accessibility_expand_group" msgid="521237935987978624">"Expandir grupo."</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Adicionar dispositivo ao grupo."</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Remover dispositivo do grupo."</string> <string name="accessibility_open_application" msgid="1749126077501259712">"Abrir aplicativo."</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"Sem conexão."</string> <string name="data_connection_roaming" msgid="375650836665414797">"Roaming"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Entrada"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Aparelhos auditivos"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Ativando…"</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Não é possível ajustar o brilho, porque ele está sendo controlado pelo app principal"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Giro automático"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Giro automático da tela"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"Localização"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Adicionar à posição <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Posição inválida."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Posição <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Bloco adicionado"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Bloco removido"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Editor de configurações rápidas."</string> @@ -1574,6 +1573,5 @@ <string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Desconhecidos"</string> <string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Redefinir todos os blocos?"</string> <string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Todos os blocos \"Configurações rápidas\" serão redefinidos para as configurações originais do dispositivo"</string> - <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) --> - <skip /> + <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string> </resources> diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml index 23270ea2e9d8..e8b826b940f4 100644 --- a/packages/SystemUI/res/values-ro/strings.xml +++ b/packages/SystemUI/res/values-ro/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Conectat la <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"S-a stabilit conexiunea la <xliff:g id="CAST">%s</xliff:g>."</string> <string name="accessibility_expand_group" msgid="521237935987978624">"Extinde grupul."</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Adaugă dispozitivul în grup."</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Elimină dispozitivul din grup."</string> <string name="accessibility_open_application" msgid="1749126077501259712">"Deschide aplicația."</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"Neconectat."</string> <string name="data_connection_roaming" msgid="375650836665414797">"Roaming"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Intrare"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Aparate auditive"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Se activează..."</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Nu se poate ajusta luminozitatea deoarece este controlată de aplicația de sus"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Rotire automată"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Rotirea automată a ecranului"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"Locație"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Adaugă pe poziția <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Poziție nevalidă."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Poziția <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Cardul a fost adăugat"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Cardul a fost eliminat"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Editorul pentru setări rapide."</string> @@ -1574,6 +1573,5 @@ <string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Necunoscută"</string> <string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Resetezi toate cardurile?"</string> <string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Toate cardurile Setări rapide se vor reseta la setările inițiale ale dispozitivului"</string> - <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) --> - <skip /> + <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string> </resources> diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml index e4c1d45bf912..52458ebd176d 100644 --- a/packages/SystemUI/res/values-ru/strings.xml +++ b/packages/SystemUI/res/values-ru/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g>: подключено."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Подключено к: <xliff:g id="CAST">%s</xliff:g>."</string> <string name="accessibility_expand_group" msgid="521237935987978624">"Развернуть группу."</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Добавить устройство в группу."</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Удалить устройство из группы."</string> <string name="accessibility_open_application" msgid="1749126077501259712">"Открыть приложение."</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"Не подключено"</string> <string name="data_connection_roaming" msgid="375650836665414797">"Роуминг"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Устройство ввода"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Слуховые аппараты"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Включение…"</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Невозможно изменить яркость, поскольку ею управляет приложение сверху"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Автоповорот"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Автоповорот экрана"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"Геолокация"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Добавить на позицию <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Недопустимое расположение."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Позиция <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Панель добавлена"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Панель удалена"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Редактор быстрых настроек."</string> @@ -1574,6 +1573,5 @@ <string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Неизвестно"</string> <string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Сбросить все параметры?"</string> <string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Для всех параметров быстрых настроек будут восстановлены значения по умолчанию."</string> - <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) --> - <skip /> + <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string> </resources> diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml index 437a35b51e0e..84a981f9db89 100644 --- a/packages/SystemUI/res/values-si/strings.xml +++ b/packages/SystemUI/res/values-si/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> වෙත සම්බන්ධ කරන ලදි."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> වෙත සම්බන්ධ විය."</string> <string name="accessibility_expand_group" msgid="521237935987978624">"සමූහය දිගහැරීම"</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"සමූහයට උපාංගය එක් කරන්න."</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"උපාංගය සමූහයෙන් ඉවත් කරන්න."</string> <string name="accessibility_open_application" msgid="1749126077501259712">"යෙදුම විවෘත කරන්න."</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"සම්බන්ධ වී නැත."</string> <string name="data_connection_roaming" msgid="375650836665414797">"රෝමිං"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"ආදානය"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"ශ්රවණාධාරක"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"ක්රියාත්මක කරමින්…"</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"ඉහළ යෙදුම මඟින් එය පාලනය වන නිසා දීප්තිය ගැළපුම් කළ නොහැක"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"ස්වයංක්රීය කරකැවීම"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"ස්වයංක්රීයව-භ්රමණය වන තිරය"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"ස්ථානය"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g> ස්ථානයට එක් කරන්න"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"ස්ථානය අවලංගුයි."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"ස්ථානය <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"ටයිල් එක එක් කරන ලදි"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"ටයිල් ඉවත් කරන ලදි"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"ඉක්මන් සැකසුම් සංස්කාරකය."</string> @@ -1574,6 +1573,5 @@ <string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"නොදනී"</string> <string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"සියලු ටයිල් නැවත සකසන්න ද?"</string> <string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"සියලු ඉක්මන් සැකසීම් ටයිල් උපාංගයේ මුල් සැකසීම් වෙත නැවත සකසනු ඇත"</string> - <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) --> - <skip /> + <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string> </resources> diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml index 859c976630d7..78a332a6c45c 100644 --- a/packages/SystemUI/res/values-sk/strings.xml +++ b/packages/SystemUI/res/values-sk/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Pripojené k zariadeniu <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Pripojené k zariadeniu <xliff:g id="CAST">%s</xliff:g>."</string> <string name="accessibility_expand_group" msgid="521237935987978624">"Rozbaliť skupinu"</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Pridať zariadenie do skupiny"</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Odstrániť zariadenie zo skupiny"</string> <string name="accessibility_open_application" msgid="1749126077501259712">"Otvoriť aplikáciu"</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"Nepripojené."</string> <string name="data_connection_roaming" msgid="375650836665414797">"Roaming"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Vstup"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Načúvadlá"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Zapína sa…"</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Jas sa nedá upraviť, pretože ho ovláda horná aplikácia"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Automatické otáčanie"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Automatické otáčanie obrazovky"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"Poloha"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Pridať na <xliff:g id="POSITION">%1$d</xliff:g>. pozíciu"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Pozícia je neplatná."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"<xliff:g id="POSITION">%1$d</xliff:g>. pozícia"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Karta bola pridaná"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Karta bola odstránená"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Editor rýchlych nastavení"</string> @@ -1574,6 +1573,5 @@ <string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Neznáme"</string> <string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Chcete resetovať všetky karty?"</string> <string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Všetky karty rýchlych nastavení sa resetujú na pôvodné nastavenia zariadenia"</string> - <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) --> - <skip /> + <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string> </resources> diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml index 2a1ed4690ac5..93649f5039d0 100644 --- a/packages/SystemUI/res/values-sl/strings.xml +++ b/packages/SystemUI/res/values-sl/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Povezava vzpostavljena z: <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Vzpostavljena povezava: <xliff:g id="CAST">%s</xliff:g>."</string> <string name="accessibility_expand_group" msgid="521237935987978624">"Razširitev skupine."</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Dodajte napravo v skupino."</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Odstranite napravo iz skupine."</string> <string name="accessibility_open_application" msgid="1749126077501259712">"Odpiranje aplikacije."</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"Ni povezan."</string> <string name="data_connection_roaming" msgid="375650836665414797">"Gostovanje"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Vhodna naprava"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Slušni aparati"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Vklapljanje …"</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Svetlosti ni mogoče prilagoditi, ker jo nadzoruje aplikacija na vrhu"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Samodejno sukanje"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Samodejno sukanje zaslona"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"Lokacija"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Dodajanje na položaj <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Položaj je neveljaven."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Položaj <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Ploščica je bila dodana"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Ploščica je bila odstranjena"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Urejevalnik hitrih nastavitev."</string> @@ -1574,6 +1573,5 @@ <string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Neznano"</string> <string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Želite ponastaviti vse ploščice?"</string> <string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Vse ploščice v hitrih nastavitvah bodo ponastavljene na prvotne nastavitve naprave."</string> - <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) --> - <skip /> + <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string> </resources> diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml index 916595fb55b2..5e69808d7f63 100644 --- a/packages/SystemUI/res/values-sq/strings.xml +++ b/packages/SystemUI/res/values-sq/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Lidhur me <xliff:g id="BLUETOOTH">%s</xliff:g>"</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Është lidhur me <xliff:g id="CAST">%s</xliff:g>."</string> <string name="accessibility_expand_group" msgid="521237935987978624">"Zgjero grupin."</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Shto pajisjen te grupi."</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Hiq pajisjen nga grupi."</string> <string name="accessibility_open_application" msgid="1749126077501259712">"Hap aplikacionin."</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"Nuk është i lidhur."</string> <string name="data_connection_roaming" msgid="375650836665414797">"Roaming"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Hyrja"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Aparatet e dëgjimit"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Po aktivizohet…"</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Ndriçimi nuk mund të rregullohet pasi po kontrollohet nga aplikacioni lart"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Rrotullim automatik"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Rrotullimi automatik i ekranit"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"Vendndodhja"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Shto te pozicioni <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Pozicion i pavlefshëm."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Pozicioni <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Pllakëza u shtua"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Pllakëza u hoq"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Redaktori i cilësimeve të shpejta."</string> @@ -1574,6 +1573,5 @@ <string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Nuk njihet"</string> <string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Të rivendosen të gjitha pllakëzat?"</string> <string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Të gjitha pllakëzat e \"Cilësimeve të shpejta\" do të rivendosen te cilësimet origjinale të pajisjes"</string> - <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) --> - <skip /> + <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string> </resources> diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml index 6bf3a697ab0d..1b645a8e2017 100644 --- a/packages/SystemUI/res/values-sr/strings.xml +++ b/packages/SystemUI/res/values-sr/strings.xml @@ -1003,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Додајте на <xliff:g id="POSITION">%1$d</xliff:g>. позицију"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Позиција је неважећа."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"<xliff:g id="POSITION">%1$d</xliff:g>. позиција"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Плочица је додата"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Плочица је уклоњена"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Уређивач за Брза подешавања."</string> @@ -1571,6 +1573,5 @@ <string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Непознато"</string> <string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Желите да ресетујете све плочице?"</string> <string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Све плочице Брзих подешавања ће се ресетовати на првобитна подешавања уређаја"</string> - <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) --> - <skip /> + <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string> </resources> diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml index aa9f4a4bfcd4..a4129e0afb18 100644 --- a/packages/SystemUI/res/values-sv/strings.xml +++ b/packages/SystemUI/res/values-sv/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Ansluten till <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Ansluten till <xliff:g id="CAST">%s</xliff:g>."</string> <string name="accessibility_expand_group" msgid="521237935987978624">"Utöka gruppen."</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Lägg till enheten i gruppen."</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Ta bort enheten från gruppen."</string> <string name="accessibility_open_application" msgid="1749126077501259712">"Öppna appen."</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"Inte ansluten."</string> <string name="data_connection_roaming" msgid="375650836665414797">"Roaming"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Ingång"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Hörapparater"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Aktiverar …"</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Det går inte att ändra ljusstyrkan eftersom den styrs av den översta appen"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Rotera automatiskt"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Rotera skärmen automatiskt"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"Plats"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Lägg till på position <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Positionen är ogiltig."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Position <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Kortet har lagts till"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Kortet har tagits bort"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Redigerare för snabbinställningar."</string> @@ -1574,6 +1573,5 @@ <string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Okänt"</string> <string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Vill du återställa alla rutor?"</string> <string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Alla Snabbinställningsrutor återställs till enhetens ursprungliga inställningar"</string> - <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) --> - <skip /> + <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string> </resources> diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml index 72bfb8312cc4..cb82afff7f91 100644 --- a/packages/SystemUI/res/values-sw/strings.xml +++ b/packages/SystemUI/res/values-sw/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Imeunganishwa kwenye <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Imeunganishwa kwenye <xliff:g id="CAST">%s</xliff:g>."</string> <string name="accessibility_expand_group" msgid="521237935987978624">"Panua kikundi."</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Weka kifaa kwenye kikundi."</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Ondoa kifaa kwenye kikundi."</string> <string name="accessibility_open_application" msgid="1749126077501259712">"Fungua programu."</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"Haijaunganishwa."</string> <string name="data_connection_roaming" msgid="375650836665414797">"Mitandao ya ng\'ambo"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Vifaa vya kuingiza sauti"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Visaidizi vya kusikia"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Inawasha..."</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Imeshindwa kurekebisha mwangaza kwa sababu inadhibitiwa na programu iliyo juu"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Zungusha kiotomatiki"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Skrini ijizungushe kiotomatiki"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"Mahali"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Ongeza kwenye nafasi ya <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Nafasi si sahihi."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Nafasi ya <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Kigae kimewekwa"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Kigae kimeondolewa"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Kihariri cha Mipangilio ya haraka."</string> @@ -1574,6 +1573,5 @@ <string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Visivyojulikana"</string> <string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Ungependa kubadilisha vigae vyote?"</string> <string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Vigae vyote vya Mipangilio ya Haraka vitabadilishwa kuwa katika mipangilio halisi ya kifaa"</string> - <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) --> - <skip /> + <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string> </resources> diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml index 31ab5155eb35..77be6ea6a64b 100644 --- a/packages/SystemUI/res/values-ta/strings.xml +++ b/packages/SystemUI/res/values-ta/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g>க்கு இணைக்கப்பட்டது."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> உடன் இணைக்கப்பட்டுள்ளது."</string> <string name="accessibility_expand_group" msgid="521237935987978624">"குழுவை விரிவாக்கும்."</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"குழுவில் சாதனத்தைச் சேர்க்கும்."</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"குழுவிலிருந்து சாதனத்தை அகற்றும்."</string> <string name="accessibility_open_application" msgid="1749126077501259712">"ஆப்ஸைத் திறக்கும்."</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"இணைக்கப்படவில்லை."</string> <string name="data_connection_roaming" msgid="375650836665414797">"ரோமிங்"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"உள்ளீடு"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"செவித்துணைக் கருவி"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"ஆன் செய்கிறது…"</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"ஒளிர்வை மேலுள்ள ஆப்ஸ் கட்டுப்படுத்துவதால் அதை மாற்ற முடியவில்லை"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"தானாகச் சுழற்று"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"திரையைத் தானாகச் சுழற்று"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"இருப்பிடம்"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g>ல் சேர்க்கும்"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"நிலை தவறானது."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"இடம்: <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"கட்டம் சேர்க்கப்பட்டது"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"கட்டம் அகற்றப்பட்டது"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"விரைவு அமைப்புகள் திருத்தி."</string> @@ -1574,6 +1573,5 @@ <string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"தெரியவில்லை"</string> <string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"அனைத்துக் கட்டங்களையும் மீட்டமைக்கவா?"</string> <string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"விரைவு அமைப்புகளின் கட்டங்கள் அனைத்தும் சாதனத்தின் அசல் அமைப்புகளுக்கு மீட்டமைக்கப்படும்"</string> - <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) --> - <skip /> + <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string> </resources> diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml index d26bfaccc6cc..b4665190f33d 100644 --- a/packages/SystemUI/res/values-te/strings.xml +++ b/packages/SystemUI/res/values-te/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g>కి కనెక్ట్ చేయబడింది."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g>కి కనెక్ట్ చేయబడింది."</string> <string name="accessibility_expand_group" msgid="521237935987978624">"గ్రూప్ను విస్తరించండి."</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"గ్రూప్కి పరికరాన్ని జోడించండి."</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"గ్రూప్ నుండి పరికరాన్ని తీసివేయండి."</string> <string name="accessibility_open_application" msgid="1749126077501259712">"యాప్ను తెరవండి."</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"కనెక్ట్ చేయబడలేదు."</string> <string name="data_connection_roaming" msgid="375650836665414797">"రోమింగ్"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"ఇన్పుట్"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"వినికిడి పరికరాలు"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"ఆన్ చేస్తోంది…"</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"దీనిని టాప్ యాప్ కంట్రోల్ చేస్తోంది కనుక బ్రైట్నెస్ను సర్దుబాటు చేయడం సాధ్యం కాదు"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"ఆటో-రొటేట్"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"స్క్రీన్ ఆటో-రొటేట్"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"లొకేషన్"</string> @@ -1007,6 +1004,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g> స్థానానికి జోడించండి"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"ప్రస్తుత పొజిషన్ చెల్లదు."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"స్థానం <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"టైల్ జోడించబడింది"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"టైల్ తీసివేయబడింది"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"శీఘ్ర సెట్టింగ్ల ఎడిటర్."</string> @@ -1577,6 +1576,5 @@ <string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"తెలియదు"</string> <string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"టైల్స్ అన్ని రీసెట్ చేయాలా?"</string> <string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"అన్ని క్విక్ సెట్టింగ్ల టైల్స్, పరికరం తాలూకు ఒరిజినల్ సెట్టింగ్లకు రీసెట్ చేయబడతాయి"</string> - <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) --> - <skip /> + <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string> </resources> diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml index 9196f95de203..e816c04a5e65 100644 --- a/packages/SystemUI/res/values-th/strings.xml +++ b/packages/SystemUI/res/values-th/strings.xml @@ -1003,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"เพิ่มไปยังตำแหน่ง <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"ตำแหน่งไม่ถูกต้อง"</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"ตำแหน่ง <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"เพิ่มชิ้นส่วนแล้ว"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"นำชิ้นส่วนออกแล้ว"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"ตัวแก้ไขการตั้งค่าด่วน"</string> @@ -1571,6 +1573,5 @@ <string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"ไม่ทราบ"</string> <string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"รีเซ็ตการ์ดทั้งหมดใช่ไหม"</string> <string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"การ์ดการตั้งค่าด่วนทั้งหมดจะรีเซ็ตเป็นการตั้งค่าเดิมของอุปกรณ์"</string> - <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) --> - <skip /> + <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string> </resources> diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml index a6b0d6aa55df..d996218c64db 100644 --- a/packages/SystemUI/res/values-tl/strings.xml +++ b/packages/SystemUI/res/values-tl/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Nakakonekta sa <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Nakakonekta sa <xliff:g id="CAST">%s</xliff:g>."</string> <string name="accessibility_expand_group" msgid="521237935987978624">"I-expand ang grupo."</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Idagdag ang device sa grupo."</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Alisin ang device sa grupo."</string> <string name="accessibility_open_application" msgid="1749126077501259712">"Buksan ang application."</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"Hindi nakakonekta."</string> <string name="data_connection_roaming" msgid="375650836665414797">"Roaming"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Input"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Mga hearing aid"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Ino-on…"</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Hindi ma-adjust ang liwanag dahil kinokontrol ito ng nangingibabaw na app"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"I-auto rotate"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Awtomatikong i-rotate ang screen"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"Lokasyon"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Idagdag sa posisyong <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Invalid ang posisyon."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Posisyon <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Idinagdag ang tile"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Inalis ang tile"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Editor ng Mga mabilisang setting."</string> diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml index 13d6cd7ff9fc..9d789417b09f 100644 --- a/packages/SystemUI/res/values-tr/strings.xml +++ b/packages/SystemUI/res/values-tr/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> ile bağlı."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> bağlantısı kuruldu."</string> <string name="accessibility_expand_group" msgid="521237935987978624">"Grubu genişlet."</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Cihazı gruba ekle."</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Cihazı gruptan kaldır."</string> <string name="accessibility_open_application" msgid="1749126077501259712">"Uygulama aç."</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"Bağlanmadı."</string> <string name="data_connection_roaming" msgid="375650836665414797">"Dolaşım"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Giriş"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"İşitme cihazları"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Açılıyor…"</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Parlaklık ayarlanamıyor, çünkü bu özellik en üstteki uygulama tarafından kontrol ediliyor"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Otomatik döndür"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Ekranı otomatik döndür"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"Konum"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g> konumuna ekle"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Konum geçersiz."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Konum: <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Kutu eklendi"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Kutu kaldırıldı"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Hızlı ayar düzenleyicisi."</string> @@ -1574,6 +1573,5 @@ <string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Bilinmiyor"</string> <string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Tüm ayar kutuları sıfırlansın mı?"</string> <string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Tüm Hızlı Ayarlar kutuları cihazın özgün ayarlarına sıfırlanır"</string> - <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) --> - <skip /> + <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string> </resources> diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml index f0f4e38d6040..97e5bb70313f 100644 --- a/packages/SystemUI/res/values-uk/strings.xml +++ b/packages/SystemUI/res/values-uk/strings.xml @@ -247,15 +247,13 @@ <string name="accessibility_bluetooth_device_settings_gear" msgid="3314916468105272540">"Натисніть, щоб змінити налаштування пристрою"</string> <string name="accessibility_bluetooth_device_settings_gear_with_name" msgid="114373701123165491">"<xliff:g id="DEVICE_NAME">%s</xliff:g>. Змінити налаштування пристрою"</string> <string name="accessibility_bluetooth_device_settings_see_all" msgid="5260390270128256620">"Переглянути всі пристрої"</string> - <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="7988547106800504256">"Підключити новий пристрій"</string> + <string name="accessibility_bluetooth_device_settings_pair_new_device" msgid="7988547106800504256">"Зв’язати новий пристрій"</string> <string name="accessibility_battery_unknown" msgid="1807789554617976440">"Відсоток заряду акумулятора невідомий."</string> <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Підключено до <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Під’єднано до пристрою <xliff:g id="CAST">%s</xliff:g>."</string> <string name="accessibility_expand_group" msgid="521237935987978624">"Розгорнути групу"</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Додати пристрій у групу."</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Вилучити пристрій із групи."</string> <string name="accessibility_open_application" msgid="1749126077501259712">"Відкрити додаток"</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"Не з’єднано."</string> <string name="data_connection_roaming" msgid="375650836665414797">"Роумінг"</string> @@ -308,7 +306,7 @@ <string name="quick_settings_bluetooth_label" msgid="7018763367142041481">"Bluetooth"</string> <string name="quick_settings_bluetooth_detail_empty_text" msgid="5760239584390514322">"Немає спарених пристроїв"</string> <string name="quick_settings_bluetooth_tile_subtitle" msgid="212752719010829550">"Натисніть, щоб під’єднати або від’єднати пристрій"</string> - <string name="pair_new_bluetooth_devices" msgid="4601767620843349645">"Підключити новий пристрій"</string> + <string name="pair_new_bluetooth_devices" msgid="4601767620843349645">"Зв’язати новий пристрій"</string> <string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Показати всі"</string> <string name="turn_on_bluetooth" msgid="5681370462180289071">"Увімкнути Bluetooth"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Підключено"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Джерело сигналу"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Слухові апарати"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Увімкнення…"</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Не вдається змінити яскравість, оскільки нею керує основний додаток"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Автообертання"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Автоматично обертати екран"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"Геодані"</string> @@ -418,7 +415,7 @@ <string name="quick_settings_hearing_devices_connected" msgid="6519069502397037781">"Під’єднано"</string> <string name="quick_settings_hearing_devices_disconnected" msgid="8907061223998176187">"Від’єднано"</string> <string name="quick_settings_hearing_devices_dialog_title" msgid="9004774017688484981">"Слухові апарати"</string> - <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Підключити новий пристрій"</string> + <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Зв’язати новий пристрій"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Натисніть, щоб підключити новий пристрій"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"Не вдалось оновити набір налаштувань"</string> <string name="hearing_devices_preset_label" msgid="7878267405046232358">"Набір налаштувань"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Додати на позицію <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Позиція недійсна."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Позиція <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Опцію додано"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Опцію вилучено"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Редактор швидких налаштувань."</string> @@ -1574,6 +1573,5 @@ <string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Невідомо"</string> <string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Скинути всі панелі?"</string> <string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Усі панелі швидких налаштувань буде скинуто до стандартних налаштувань пристрою"</string> - <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) --> - <skip /> + <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string> </resources> diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml index ef10b87cbd0f..41ba6b235284 100644 --- a/packages/SystemUI/res/values-ur/strings.xml +++ b/packages/SystemUI/res/values-ur/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> سے منسلک ہیں۔"</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> سے منسلک ہے۔"</string> <string name="accessibility_expand_group" msgid="521237935987978624">"گروپ کو پھیلائیں۔"</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"آلہ کو گروپ شامل کریں۔"</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"آلہ کو گروپ سے ہٹائیں"</string> <string name="accessibility_open_application" msgid="1749126077501259712">"ایپلیکیشن کھولیں۔"</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"مربوط نہیں ہے۔"</string> <string name="data_connection_roaming" msgid="375650836665414797">"رومنگ"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"ان پٹ"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"سماعتی آلات"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"آن ہو رہا ہے…"</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"چمک کو ایڈجسٹ نہیں کیا جا سکتا کیونکہ اسے سرفہرست ایپ کے ذریعے کنٹرول کیا جا رہا ہے"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"خود کار طور پر گھمائیں"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"اسکرین کو خود کار طور پر گھمائیں"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"مقام"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"پوزیشن <xliff:g id="POSITION">%1$d</xliff:g> میں شامل کریں"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"پوزیشن غلط ہے۔"</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"پوزیشن <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"ٹائل کو شامل کیا گیا"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"ٹائل کو ہٹا دیا گیا"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"فوری ترتیبات کا ایڈیٹر۔"</string> diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml index facc51f2a1f6..f713ddc13b80 100644 --- a/packages/SystemUI/res/values-uz/strings.xml +++ b/packages/SystemUI/res/values-uz/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Ulangan: <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Bunga ulangan: <xliff:g id="CAST">%s</xliff:g>."</string> <string name="accessibility_expand_group" msgid="521237935987978624">"Guruhni yoying."</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Qurilmani guruhga kiritish."</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Qurilmani guruhdan olib tashlash."</string> <string name="accessibility_open_application" msgid="1749126077501259712">"Ilovani oching."</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"Ulanmagan."</string> <string name="data_connection_roaming" msgid="375650836665414797">"Rouming"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Kirish"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Eshitish moslamalari"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Yoqilmoqda…"</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Yorqinlikni sozlash imkonsiz, chunki tepadago ilova tomonidan boshqariladi"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Avto-burilish"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Ekranning avtomatik burilishi"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"Joylashuv"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Bu joyga kiritish: <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Pozitsiya yaroqsiz."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Joylashuv: <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Katakcha kiritildi"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Katakcha olib tashlandi"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Tezkor sozlamalar muharriri"</string> @@ -1574,6 +1573,5 @@ <string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Noaniq"</string> <string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Barcha katakchalar asliga qaytarilsinmi?"</string> <string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Barcha Tezkor sozlamalar katakchalari qurilmaning asl sozlamalariga qaytariladi"</string> - <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) --> - <skip /> + <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string> </resources> diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml index 80db132b304f..2f266a781525 100644 --- a/packages/SystemUI/res/values-vi/strings.xml +++ b/packages/SystemUI/res/values-vi/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Đã kết nối với <xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Đã kết nối với <xliff:g id="CAST">%s</xliff:g>."</string> <string name="accessibility_expand_group" msgid="521237935987978624">"Mở rộng nhóm."</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Thêm thiết bị vào nhóm."</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Xoá thiết bị khỏi nhóm."</string> <string name="accessibility_open_application" msgid="1749126077501259712">"Mở ứng dụng."</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"Chưa được kết nối."</string> <string name="data_connection_roaming" msgid="375650836665414797">"Chuyển vùng"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Thiết bị đầu vào"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Thiết bị trợ thính"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Đang bật…"</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Không điều chỉnh được độ sáng do ứng dụng ở trên cùng đang điều khiển độ sáng"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Tự động xoay"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Tự động xoay màn hình"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"Vị trí"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Thêm vào vị trí <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Vị trí không hợp lệ."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Vị trí <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Đã thêm thẻ thông tin"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Đã xóa thẻ thông tin"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Trình chỉnh sửa cài đặt nhanh."</string> @@ -1574,6 +1573,5 @@ <string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Không xác định"</string> <string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Đặt lại mọi ô?"</string> <string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Mọi ô Cài đặt nhanh sẽ được đặt lại về chế độ cài đặt ban đầu của thiết bị"</string> - <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) --> - <skip /> + <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string> </resources> diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml index feb3989114cb..480debcb239b 100644 --- a/packages/SystemUI/res/values-zh-rCN/strings.xml +++ b/packages/SystemUI/res/values-zh-rCN/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"已连接到<xliff:g id="BLUETOOTH">%s</xliff:g>。"</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"已连接到 <xliff:g id="CAST">%s</xliff:g>。"</string> <string name="accessibility_expand_group" msgid="521237935987978624">"展开群组。"</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"将设备添加到群组。"</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"从群组中移除设备。"</string> <string name="accessibility_open_application" msgid="1749126077501259712">"打开应用。"</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"未连接。"</string> <string name="data_connection_roaming" msgid="375650836665414797">"漫游"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"输入"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"助听器"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"正在开启…"</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"亮度无法调整,因为它正在被顶层应用控制"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"自动屏幕旋转"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"自动旋转屏幕"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"位置信息"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"添加到位置 <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"位置无效。"</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"位置 <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"已添加功能块"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"已移除功能块"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"快捷设置编辑器。"</string> @@ -1574,6 +1573,5 @@ <string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"未知"</string> <string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"要重置所有功能块吗?"</string> <string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"所有“快捷设置”功能块都将重置为设备的原始设置"</string> - <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) --> - <skip /> + <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>,<xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string> </resources> diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml index 031df00e1c9c..bc9e0c580901 100644 --- a/packages/SystemUI/res/values-zh-rHK/strings.xml +++ b/packages/SystemUI/res/values-zh-rHK/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"已連線至<xliff:g id="BLUETOOTH">%s</xliff:g>。"</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"已連接至 <xliff:g id="CAST">%s</xliff:g>。"</string> <string name="accessibility_expand_group" msgid="521237935987978624">"展開群組。"</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"將裝置新增至群組。"</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"從群組移除裝置。"</string> <string name="accessibility_open_application" msgid="1749126077501259712">"開啟應用程式。"</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"未連線。"</string> <string name="data_connection_roaming" msgid="375650836665414797">"漫遊"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"輸入"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"助聽器"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"正在開啟…"</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"無法調整亮度,因為目前是由上層應用程式控制亮度"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"自動旋轉"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"自動旋轉螢幕"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"位置"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"加去位置 <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"位置冇效。"</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"位置 <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"加咗圖塊"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"移除咗圖塊"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"快速設定編輯工具。"</string> @@ -1574,6 +1573,5 @@ <string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"不明"</string> <string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"要重設所有圖塊嗎?"</string> <string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"所有「快速設定」圖塊將重設為裝置的原始設定"</string> - <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) --> - <skip /> + <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>,<xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string> </resources> diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml index df05577ff942..02fda5a68313 100644 --- a/packages/SystemUI/res/values-zh-rTW/strings.xml +++ b/packages/SystemUI/res/values-zh-rTW/strings.xml @@ -252,10 +252,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"已連線至<xliff:g id="BLUETOOTH">%s</xliff:g>。"</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"已連線至 <xliff:g id="CAST">%s</xliff:g>。"</string> <string name="accessibility_expand_group" msgid="521237935987978624">"展開群組。"</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"將裝置加入群組。"</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"從群組中移除裝置。"</string> <string name="accessibility_open_application" msgid="1749126077501259712">"開啟應用程式。"</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"尚未連線。"</string> <string name="data_connection_roaming" msgid="375650836665414797">"漫遊"</string> @@ -333,8 +331,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"輸入"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"助聽器"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"開啟中…"</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"無法調整亮度,因為目前是由上層應用程式控制亮度"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"自動旋轉"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"自動旋轉螢幕"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"定位"</string> @@ -1006,6 +1003,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"新增到位置 <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"位置無效。"</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"位置 <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"已新增設定方塊"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"已移除設定方塊"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"快速設定編輯器。"</string> @@ -1574,6 +1573,5 @@ <string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"不明"</string> <string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"要重設所有設定方塊嗎?"</string> <string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"所有快速設定方塊都會恢復裝置的原始設定"</string> - <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) --> - <skip /> + <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>、<xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string> </resources> diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml index d571f0e58c92..886cbdf2b097 100644 --- a/packages/SystemUI/res/values-zu/strings.xml +++ b/packages/SystemUI/res/values-zu/strings.xml @@ -254,10 +254,8 @@ <string name="accessibility_bluetooth_name" msgid="7300973230214067678">"Xhuma ku-<xliff:g id="BLUETOOTH">%s</xliff:g>."</string> <string name="accessibility_cast_name" msgid="7344437925388773685">"Ixhumeke ku-<xliff:g id="CAST">%s</xliff:g>."</string> <string name="accessibility_expand_group" msgid="521237935987978624">"Nweba iqembu."</string> - <!-- no translation found for accessibility_add_device_to_group (5446422960697860806) --> - <skip /> - <!-- no translation found for accessibility_remove_device_from_group (3114694270949142228) --> - <skip /> + <string name="accessibility_add_device_to_group" msgid="5446422960697860806">"Faka idivayisi eqenjini."</string> + <string name="accessibility_remove_device_from_group" msgid="3114694270949142228">"Susa idivayisi eqenjini."</string> <string name="accessibility_open_application" msgid="1749126077501259712">"Vula i-application."</string> <string name="accessibility_not_connected" msgid="4061305616351042142">"Akuxhunyiwe"</string> <string name="data_connection_roaming" msgid="375650836665414797">"Iyazulazula"</string> @@ -335,8 +333,7 @@ <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Okokufaka"</string> <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Imishini yendlebe"</string> <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Iyavula..."</string> - <!-- no translation found for quick_settings_brightness_unable_adjust_msg (4124028416057617517) --> - <skip /> + <string name="quick_settings_brightness_unable_adjust_msg" msgid="4124028416057617517">"Ayikwazi ukulungisa ukukhanya ngoba ilawulwa yi-app ephezulu"</string> <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Ukuphenduka okuzenzakalelayo"</string> <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Phendula iskrini ngokuzenzakalela"</string> <string name="quick_settings_location_label" msgid="2621868789013389163">"Indawo"</string> @@ -1008,6 +1005,8 @@ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Engeza kusikhundla se-<xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Indawo ayivumelekile."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Isikhundla se-<xliff:g id="POSITION">%1$d</xliff:g>"</string> + <!-- no translation found for accessibility_qs_edit_tile_already_added (5900071201690226752) --> + <skip /> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Ithayela lingeziwe"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Ithayela likhishiwe"</string> <string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Isihleli sezilungiselelo ezisheshayo."</string> @@ -1576,6 +1575,5 @@ <string name="qs_edit_mode_category_unknown" msgid="509314252124053550">"Akwaziwa"</string> <string name="qs_edit_mode_reset_dialog_title" msgid="5344853290033761627">"Qala kabusha onke amathayela?"</string> <string name="qs_edit_mode_reset_dialog_content" msgid="7474773130622653653">"Ithayela Lamasethingi Asheshayo lizosetha kabusha libuyele kumasethingi okuqala edivayisi"</string> - <!-- no translation found for volume_slider_disabled_message_template (1305088816797803460) --> - <skip /> + <string name="volume_slider_disabled_message_template" msgid="1305088816797803460">"<xliff:g id="STREAM_NAME">%1$s</xliff:g>, <xliff:g id="DISABLED_MESSAGE">%2$s</xliff:g>"</string> </resources> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 8342a9cc244b..d0ae307b6919 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -283,7 +283,7 @@ <dimen name="notification_max_height">358dp</dimen> <!-- Height of a large promoted ongoing notification in the status bar --> - <dimen name="notification_max_height_for_promoted_ongoing">272dp</dimen> + <dimen name="notification_max_height_for_promoted_ongoing">218dp</dimen> <!-- Height of a heads up notification in the status bar for legacy custom views --> <dimen name="notification_max_heads_up_height_legacy">128dp</dimen> @@ -1157,6 +1157,8 @@ <dimen name="smart_action_button_icon_size">18dp</dimen> <dimen name="smart_action_button_icon_padding">8dp</dimen> <dimen name="smart_action_button_outline_stroke_width">2dp</dimen> + <dimen name="notification_2025_smart_reply_button_corner_radius">18dp</dimen> + <dimen name="notification_2025_smart_reply_button_min_height">48dp</dimen> <!-- Magic Action params. --> <!-- Corner radius = half of min_height to create rounded sides. --> @@ -2166,7 +2168,7 @@ <dimen name="volume_dialog_background_top_margin">-28dp</dimen> <dimen name="volume_dialog_window_margin">14dp</dimen> - <dimen name="volume_dialog_components_spacing">8dp</dimen> + <dimen name="volume_dialog_components_spacing">10dp</dimen> <dimen name="volume_dialog_floating_sliders_spacing">8dp</dimen> <dimen name="volume_dialog_floating_sliders_vertical_padding">10dp</dimen> <dimen name="volume_dialog_floating_sliders_vertical_padding_negative"> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index c06c17a0844f..8c1fd65d96d4 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2357,8 +2357,8 @@ <string name="system_multitasking_lhs">Use split screen with app on the left</string> <!-- User visible title for the keyboard shortcut that switches from split screen to full screen [CHAR LIMIT=70] --> <string name="system_multitasking_full_screen">Use full screen</string> - <!-- User visible title for the keyboard shortcut that switches to desktop view [CHAR LIMIT=70] --> - <string name="system_multitasking_desktop_view">Use desktop view</string> + <!-- User visible title for the keyboard shortcut that switches to desktop windowing [CHAR LIMIT=70] --> + <string name="system_multitasking_desktop_view">Use desktop windowing</string> <!-- User visible title for the keyboard shortcut that switches to app on right or below while using split screen [CHAR LIMIT=70] --> <string name="system_multitasking_splitscreen_focus_rhs">Switch to app on right or below while using split screen</string> <!-- User visible title for the keyboard shortcut that switches to app on left or above while using split screen [CHAR LIMIT=70] --> diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt index 1549b699eee6..763b1072f968 100644 --- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt +++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt @@ -21,7 +21,6 @@ import android.content.Context import android.content.Intent import android.content.IntentFilter import android.content.res.Resources -import android.graphics.RectF import android.os.Trace import android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS import android.provider.Settings.Global.ZEN_MODE_OFF @@ -60,6 +59,7 @@ import com.android.systemui.plugins.clocks.ClockEventListener import com.android.systemui.plugins.clocks.ClockFaceController import com.android.systemui.plugins.clocks.ClockMessageBuffers import com.android.systemui.plugins.clocks.ClockTickRate +import com.android.systemui.plugins.clocks.VRectF import com.android.systemui.plugins.clocks.WeatherData import com.android.systemui.plugins.clocks.ZenData import com.android.systemui.plugins.clocks.ZenData.ZenMode @@ -250,7 +250,7 @@ constructor( private var largeClockOnSecondaryDisplay = false val dozeAmount = MutableStateFlow(0f) - val onClockBoundsChanged = MutableStateFlow<RectF?>(null) + val onClockBoundsChanged = MutableStateFlow<VRectF>(VRectF.ZERO) private fun isDarkTheme(): Boolean { val isLightTheme = TypedValue() @@ -315,7 +315,7 @@ constructor( private val clockListener = object : ClockEventListener { - override fun onBoundsChanged(bounds: RectF) { + override fun onBoundsChanged(bounds: VRectF) { onClockBoundsChanged.value = bounds } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java index ec97b8a96c1f..b8726101602c 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java @@ -21,6 +21,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.res.ColorStateList; import android.content.res.Resources; +import android.hardware.input.InputManager; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.Log; @@ -219,6 +220,7 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> private final KeyguardKeyboardInteractor mKeyguardKeyboardInteractor; private final BouncerHapticPlayer mBouncerHapticPlayer; private final UserActivityNotifier mUserActivityNotifier; + private final InputManager mInputManager; @Inject public Factory(KeyguardUpdateMonitor keyguardUpdateMonitor, @@ -235,7 +237,8 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> UiEventLogger uiEventLogger, KeyguardKeyboardInteractor keyguardKeyboardInteractor, BouncerHapticPlayer bouncerHapticPlayer, - UserActivityNotifier userActivityNotifier) { + UserActivityNotifier userActivityNotifier, + InputManager inputManager) { mKeyguardUpdateMonitor = keyguardUpdateMonitor; mLockPatternUtils = lockPatternUtils; mLatencyTracker = latencyTracker; @@ -254,6 +257,7 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> mKeyguardKeyboardInteractor = keyguardKeyboardInteractor; mBouncerHapticPlayer = bouncerHapticPlayer; mUserActivityNotifier = userActivityNotifier; + mInputManager = inputManager; } /** Create a new {@link KeyguardInputViewController}. */ @@ -285,22 +289,23 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> emergencyButtonController, mFalsingCollector, mDevicePostureController, mFeatureFlags, mSelectedUserInteractor, mUiEventLogger, mKeyguardKeyboardInteractor, mBouncerHapticPlayer, - mUserActivityNotifier); + mUserActivityNotifier, mInputManager); } else if (keyguardInputView instanceof KeyguardSimPinView) { return new KeyguardSimPinViewController((KeyguardSimPinView) keyguardInputView, mKeyguardUpdateMonitor, securityMode, mLockPatternUtils, keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker, mTelephonyManager, mFalsingCollector, emergencyButtonController, mFeatureFlags, mSelectedUserInteractor, - mKeyguardKeyboardInteractor, mBouncerHapticPlayer, mUserActivityNotifier); + mKeyguardKeyboardInteractor, mBouncerHapticPlayer, mUserActivityNotifier, + mInputManager); } else if (keyguardInputView instanceof KeyguardSimPukView) { return new KeyguardSimPukViewController((KeyguardSimPukView) keyguardInputView, mKeyguardUpdateMonitor, securityMode, mLockPatternUtils, keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker, mTelephonyManager, mFalsingCollector, emergencyButtonController, mFeatureFlags, mSelectedUserInteractor, - mKeyguardKeyboardInteractor, mBouncerHapticPlayer, mUserActivityNotifier - ); + mKeyguardKeyboardInteractor, mBouncerHapticPlayer, mUserActivityNotifier, + mInputManager); } throw new RuntimeException("Unable to find controller for " + keyguardInputView); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java index 0e9d8fec9717..ec9aedfc7551 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java @@ -18,12 +18,14 @@ package com.android.keyguard; import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED; +import static com.android.internal.widget.flags.Flags.hideLastCharWithPhysicalInput; import static com.android.systemui.Flags.pinInputFieldStyledFocusState; import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow; import android.graphics.drawable.Drawable; import android.graphics.drawable.GradientDrawable; import android.graphics.drawable.StateListDrawable; +import android.hardware.input.InputManager; import android.util.TypedValue; import android.view.KeyEvent; import android.view.MotionEvent; @@ -43,11 +45,13 @@ import com.android.systemui.res.R; import com.android.systemui.user.domain.interactor.SelectedUserInteractor; public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinBasedInputView> - extends KeyguardAbsKeyInputViewController<T> { + extends KeyguardAbsKeyInputViewController<T> implements InputManager.InputDeviceListener { private final FalsingCollector mFalsingCollector; private final KeyguardKeyboardInteractor mKeyguardKeyboardInteractor; protected PasswordTextView mPasswordEntry; + private Boolean mShowAnimations; + private InputManager mInputManager; private final OnKeyListener mOnKeyListener = (v, keyCode, event) -> { if (event.getAction() == KeyEvent.ACTION_DOWN) { @@ -79,7 +83,8 @@ public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinB SelectedUserInteractor selectedUserInteractor, KeyguardKeyboardInteractor keyguardKeyboardInteractor, BouncerHapticPlayer bouncerHapticPlayer, - UserActivityNotifier userActivityNotifier) { + UserActivityNotifier userActivityNotifier, + InputManager inputManager) { super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback, messageAreaControllerFactory, latencyTracker, falsingCollector, emergencyButtonController, featureFlags, selectedUserInteractor, @@ -87,6 +92,51 @@ public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinB mFalsingCollector = falsingCollector; mKeyguardKeyboardInteractor = keyguardKeyboardInteractor; mPasswordEntry = mView.findViewById(mView.getPasswordTextViewId()); + mInputManager = inputManager; + mShowAnimations = null; + } + + private void updateAnimations(Boolean showAnimations) { + if (!hideLastCharWithPhysicalInput()) return; + + if (showAnimations == null) { + showAnimations = !mLockPatternUtils + .isPinEnhancedPrivacyEnabled(mSelectedUserInteractor.getSelectedUserId()); + } + if (mShowAnimations != null && showAnimations.equals(mShowAnimations)) return; + mShowAnimations = showAnimations; + + for (NumPadKey button : mView.getButtons()) { + button.setAnimationEnabled(mShowAnimations); + } + mPasswordEntry.setShowPassword(mShowAnimations); + } + + @Override + public void onInputDeviceAdded(int deviceId) { + if (!hideLastCharWithPhysicalInput()) return; + + // If we were showing animations before maybe the new device is a keyboard. + if (mShowAnimations) { + updateAnimations(null); + } + } + + @Override + public void onInputDeviceRemoved(int deviceId) { + if (!hideLastCharWithPhysicalInput()) return; + + // If we were hiding animations because of a keyboard the keyboard may have been unplugged. + if (!mShowAnimations) { + updateAnimations(null); + } + } + + @Override + public void onInputDeviceChanged(int deviceId) { + if (!hideLastCharWithPhysicalInput()) return; + + updateAnimations(null); } @Override @@ -95,7 +145,13 @@ public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinB boolean showAnimations = !mLockPatternUtils .isPinEnhancedPrivacyEnabled(mSelectedUserInteractor.getSelectedUserId()); - mPasswordEntry.setShowPassword(showAnimations); + if (hideLastCharWithPhysicalInput()) { + mInputManager.registerInputDeviceListener(this, null); + updateAnimations(showAnimations); + } else { + mPasswordEntry.setShowPassword(showAnimations); + } + for (NumPadKey button : mView.getButtons()) { button.setOnTouchListener((v, event) -> { if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { @@ -103,7 +159,9 @@ public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinB } return false; }); - button.setAnimationEnabled(showAnimations); + if (!hideLastCharWithPhysicalInput()) { + button.setAnimationEnabled(showAnimations); + } button.setBouncerHapticHelper(mBouncerHapticPlayer); } mPasswordEntry.setOnKeyListener(mOnKeyListener); @@ -191,6 +249,10 @@ public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinB protected void onViewDetached() { super.onViewDetached(); + if (hideLastCharWithPhysicalInput()) { + mInputManager.unregisterInputDeviceListener(this); + } + for (NumPadKey button : mView.getButtons()) { button.setOnTouchListener(null); button.setBouncerHapticHelper(null); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java index 9ae4cc6a4b4f..eefcab38ecd3 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java @@ -18,6 +18,7 @@ package com.android.keyguard; import static com.android.systemui.flags.Flags.LOCKSCREEN_ENABLE_LANDSCAPE; +import android.hardware.input.InputManager; import android.view.View; import com.android.internal.logging.UiEvent; @@ -63,11 +64,13 @@ public class KeyguardPinViewController SelectedUserInteractor selectedUserInteractor, UiEventLogger uiEventLogger, KeyguardKeyboardInteractor keyguardKeyboardInteractor, BouncerHapticPlayer bouncerHapticPlayer, - UserActivityNotifier userActivityNotifier) { + UserActivityNotifier userActivityNotifier, + InputManager inputManager) { super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback, messageAreaControllerFactory, latencyTracker, emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor, - keyguardKeyboardInteractor, bouncerHapticPlayer, userActivityNotifier); + keyguardKeyboardInteractor, bouncerHapticPlayer, userActivityNotifier, inputManager + ); mKeyguardUpdateMonitor = keyguardUpdateMonitor; mPostureController = postureController; mLockPatternUtils = lockPatternUtils; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java index 24f77d77dbe1..a5bb62c04d00 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java @@ -29,6 +29,7 @@ import android.content.res.ColorStateList; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Color; +import android.hardware.input.InputManager; import android.telephony.PinResult; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; @@ -97,11 +98,13 @@ public class KeyguardSimPinViewController SelectedUserInteractor selectedUserInteractor, KeyguardKeyboardInteractor keyguardKeyboardInteractor, BouncerHapticPlayer bouncerHapticPlayer, - UserActivityNotifier userActivityNotifier) { + UserActivityNotifier userActivityNotifier, + InputManager inputManager) { super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback, messageAreaControllerFactory, latencyTracker, emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor, - keyguardKeyboardInteractor, bouncerHapticPlayer, userActivityNotifier); + keyguardKeyboardInteractor, bouncerHapticPlayer, userActivityNotifier, inputManager + ); mKeyguardUpdateMonitor = keyguardUpdateMonitor; mTelephonyManager = telephonyManager; mSimImageView = mView.findViewById(R.id.keyguard_sim); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java index e17e8cc05f7e..adede3dc058d 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java @@ -25,6 +25,7 @@ import android.content.res.ColorStateList; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Color; +import android.hardware.input.InputManager; import android.telephony.PinResult; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; @@ -95,11 +96,13 @@ public class KeyguardSimPukViewController SelectedUserInteractor selectedUserInteractor, KeyguardKeyboardInteractor keyguardKeyboardInteractor, BouncerHapticPlayer bouncerHapticPlayer, - UserActivityNotifier userActivityNotifier) { + UserActivityNotifier userActivityNotifier, + InputManager inputManager) { super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback, messageAreaControllerFactory, latencyTracker, emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor, - keyguardKeyboardInteractor, bouncerHapticPlayer, userActivityNotifier); + keyguardKeyboardInteractor, bouncerHapticPlayer, userActivityNotifier, inputManager + ); mKeyguardUpdateMonitor = keyguardUpdateMonitor; mTelephonyManager = telephonyManager; mSimImageView = mView.findViewById(R.id.keyguard_sim); diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java index 4a8e4ed3f6f1..f72087efc03e 100644 --- a/packages/SystemUI/src/com/android/systemui/Dependency.java +++ b/packages/SystemUI/src/com/android/systemui/Dependency.java @@ -32,6 +32,7 @@ import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.fragments.FragmentService; +import com.android.systemui.media.NotificationMediaManager; import com.android.systemui.model.SysUiState; import com.android.systemui.navigationbar.NavigationBarController; import com.android.systemui.navigationbar.NavigationModeController; @@ -42,7 +43,6 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.recents.LauncherProxyService; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager; import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager; import com.android.systemui.statusbar.notification.stack.AmbientState; diff --git a/packages/SystemUI/src/com/android/systemui/KairosActivatable.kt b/packages/SystemUI/src/com/android/systemui/KairosActivatable.kt index 5e29ba91ce42..442d4abb23f7 100644 --- a/packages/SystemUI/src/com/android/systemui/KairosActivatable.kt +++ b/packages/SystemUI/src/com/android/systemui/KairosActivatable.kt @@ -19,23 +19,28 @@ package com.android.systemui import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.kairos.BuildScope +import com.android.systemui.kairos.BuildSpec import com.android.systemui.kairos.Events import com.android.systemui.kairos.EventsLoop import com.android.systemui.kairos.ExperimentalKairosApi import com.android.systemui.kairos.Incremental import com.android.systemui.kairos.IncrementalLoop import com.android.systemui.kairos.KairosNetwork +import com.android.systemui.kairos.RootKairosNetwork import com.android.systemui.kairos.State import com.android.systemui.kairos.StateLoop +import com.android.systemui.kairos.TransactionScope +import com.android.systemui.kairos.activateSpec +import com.android.systemui.kairos.effect import com.android.systemui.kairos.launchKairosNetwork import com.android.systemui.kairos.launchScope import dagger.Binds import dagger.Module -import dagger.Provides import dagger.multibindings.ClassKey import dagger.multibindings.IntoMap import dagger.multibindings.Multibinds import javax.inject.Inject +import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -176,21 +181,40 @@ private class KairosBuilderImpl @Inject constructor() : KairosBuilder { @SysUISingleton @ExperimentalKairosApi class KairosCoreStartable -@Inject -constructor( - @Application private val appScope: CoroutineScope, - private val kairosNetwork: KairosNetwork, +private constructor( + private val appScope: CoroutineScope, private val activatables: dagger.Lazy<Set<@JvmSuppressWildcards KairosActivatable>>, -) : CoreStartable { + private val unwrappedNetwork: RootKairosNetwork, +) : CoreStartable, KairosNetwork by unwrappedNetwork { + + @Inject + constructor( + @Application appScope: CoroutineScope, + activatables: dagger.Lazy<Set<@JvmSuppressWildcards KairosActivatable>>, + ) : this(appScope, activatables, appScope.launchKairosNetwork()) + + private val started = CompletableDeferred<Unit>() + override fun start() { appScope.launch { - kairosNetwork.activateSpec { + unwrappedNetwork.activateSpec { for (activatable in activatables.get()) { launchScope { activatable.run { activate() } } } + effect { started.complete(Unit) } } } } + + override suspend fun activateSpec(spec: BuildSpec<*>) { + started.await() + unwrappedNetwork.activateSpec(spec) + } + + override suspend fun <R> transact(block: TransactionScope.() -> R): R { + started.await() + return unwrappedNetwork.transact(block) + } } @Module @@ -203,10 +227,5 @@ interface KairosCoreStartableModule { @Multibinds fun kairosActivatables(): Set<@JvmSuppressWildcards KairosActivatable> - companion object { - @Provides - @SysUISingleton - fun provideKairosNetwork(@Application scope: CoroutineScope): KairosNetwork = - scope.launchKairosNetwork() - } + @Binds fun bindKairosNetwork(impl: KairosCoreStartable): KairosNetwork } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java index 08559f2eca8d..fb3bc620ee68 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java @@ -63,7 +63,7 @@ import com.android.systemui.accessibility.hearingaid.HearingDevicesListAdapter.H import com.android.systemui.animation.DialogTransitionAnimator; import com.android.systemui.bluetooth.qsdialog.ActiveHearingDeviceItemFactory; import com.android.systemui.bluetooth.qsdialog.AvailableHearingDeviceItemFactory; -import com.android.systemui.bluetooth.qsdialog.ConnectedDeviceItemFactory; +import com.android.systemui.bluetooth.qsdialog.ConnectedHearingDeviceItemFactory; import com.android.systemui.bluetooth.qsdialog.DeviceItem; import com.android.systemui.bluetooth.qsdialog.DeviceItemFactory; import com.android.systemui.bluetooth.qsdialog.DeviceItemType; @@ -71,6 +71,7 @@ import com.android.systemui.bluetooth.qsdialog.SavedHearingDeviceItemFactory; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.qs.shared.QSSettingsPackageRepository; import com.android.systemui.res.R; import com.android.systemui.statusbar.phone.SystemUIDialog; @@ -111,6 +112,7 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate, private final HearingDevicesUiEventLogger mUiEventLogger; private final boolean mShowPairNewDevice; private final int mLaunchSourceId; + private final QSSettingsPackageRepository mQSSettingsPackageRepository; private SystemUIDialog mDialog; private HearingDevicesListAdapter mDeviceListAdapter; @@ -142,12 +144,7 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate, private final List<DeviceItemFactory> mHearingDeviceItemFactoryList = List.of( new ActiveHearingDeviceItemFactory(), new AvailableHearingDeviceItemFactory(), - // TODO(b/331305850): setHearingAidInfo() for connected but not connect to profile - // hearing device only called from - // settings/bluetooth/DeviceListPreferenceFragment#handleLeScanResult, so we don't know - // it is connected but not yet connect to profile hearing device in systemui. - // Show all connected but not connect to profile bluetooth device for now. - new ConnectedDeviceItemFactory(), + new ConnectedHearingDeviceItemFactory(), new SavedHearingDeviceItemFactory() ); @@ -171,7 +168,8 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate, @Main Executor mainExecutor, @Background Executor bgExecutor, AudioManager audioManager, - HearingDevicesUiEventLogger uiEventLogger) { + HearingDevicesUiEventLogger uiEventLogger, + QSSettingsPackageRepository qsSettingsPackageRepository) { mShowPairNewDevice = showPairNewDevice; mSystemUIDialogFactory = systemUIDialogFactory; mActivityStarter = activityStarter; @@ -183,6 +181,7 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate, mProfileManager = localBluetoothManager.getProfileManager(); mUiEventLogger = uiEventLogger; mLaunchSourceId = launchSourceId; + mQSSettingsPackageRepository = qsSettingsPackageRepository; } @Override @@ -198,11 +197,11 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate, public void onDeviceItemGearClicked(@NonNull DeviceItem deviceItem, @NonNull View view) { mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_GEAR_CLICK, mLaunchSourceId); dismissDialogIfExists(); - Intent intent = new Intent(ACTION_BLUETOOTH_DEVICE_DETAILS); Bundle bundle = new Bundle(); bundle.putString(KEY_BLUETOOTH_ADDRESS, deviceItem.getCachedBluetoothDevice().getAddress()); - intent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, bundle); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + Intent intent = new Intent(ACTION_BLUETOOTH_DEVICE_DETAILS) + .setPackage(mQSSettingsPackageRepository.getSettingsPackageName()) + .putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, bundle); mActivityStarter.postStartActivityDismissingKeyguard(intent, /* delay= */ 0, mDialogTransitionAnimator.createActivityTransitionController(view)); } @@ -401,8 +400,8 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate, pairButton.setOnClickListener(v -> { mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_PAIR, mLaunchSourceId); dismissDialogIfExists(); - final Intent intent = new Intent(Settings.ACTION_HEARING_DEVICE_PAIRING_SETTINGS); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + final Intent intent = new Intent(Settings.ACTION_HEARING_DEVICE_PAIRING_SETTINGS) + .setPackage(mQSSettingsPackageRepository.getSettingsPackageName()); mActivityStarter.postStartActivityDismissingKeyguard(intent, /* delay= */ 0, mDialogTransitionAnimator.createActivityTransitionController(dialog)); }); @@ -523,8 +522,9 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate, com.android.internal.R.color.materialColorOnPrimaryContainer)); } text.setText(item.getToolName()); - Intent intent = item.getToolIntent(); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + Intent intent = item.getToolIntent() + .setPackage(mQSSettingsPackageRepository.getSettingsPackageName()); + view.setOnClickListener(v -> { final String name = intent.getComponent() != null ? intent.getComponent().flattenToString() diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsViewModel.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsViewModel.kt index 5863a9385234..7d8752ef7222 100644 --- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsViewModel.kt @@ -21,18 +21,14 @@ import com.android.systemui.plugins.qs.TileDetailsViewModel class BluetoothDetailsViewModel( private val onSettingsClick: () -> Unit, val detailsContentViewModel: BluetoothDetailsContentViewModel, -) : TileDetailsViewModel() { +) : TileDetailsViewModel { override fun clickOnSettingsButton() { onSettingsClick() } - override fun getTitle(): String { - // TODO: b/378513956 Update the placeholder text - return "Bluetooth" - } + // TODO: b/378513956 Update the placeholder text + override val title = "Bluetooth" - override fun getSubTitle(): String { - // TODO: b/378513956 Update the placeholder text - return "Tap to connect or disconnect a device" - } + // TODO: b/378513956 Update the placeholder text + override val subTitle = "Tap to connect or disconnect a device" } diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt index 858cc00b86b9..208e498126c1 100644 --- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt @@ -214,7 +214,7 @@ internal class AvailableHearingDeviceItemFactory : AvailableMediaDeviceItemFacto } } -internal class ConnectedDeviceItemFactory : DeviceItemFactory() { +internal open class ConnectedDeviceItemFactory : DeviceItemFactory() { override fun isFilterMatched( context: Context, cachedDevice: CachedBluetoothDevice, @@ -238,6 +238,19 @@ internal class ConnectedDeviceItemFactory : DeviceItemFactory() { } } +internal class ConnectedHearingDeviceItemFactory : ConnectedDeviceItemFactory() { + override fun isFilterMatched( + context: Context, + cachedDevice: CachedBluetoothDevice, + isOngoingCall: Boolean, + audioSharingAvailable: Boolean, + ): Boolean { + return cachedDevice.isHearingDevice && + cachedDevice.bondState == BluetoothDevice.BOND_BONDED && + cachedDevice.device.isConnected + } +} + internal open class SavedDeviceItemFactory : DeviceItemFactory() { override fun isFilterMatched( context: Context, @@ -274,7 +287,7 @@ internal class SavedHearingDeviceItemFactory : SavedDeviceItemFactory() { context, cachedDevice.getDevice(), ) && - cachedDevice.isHearingAidDevice && + cachedDevice.isHearingDevice && cachedDevice.bondState == BluetoothDevice.BOND_BONDED && !cachedDevice.isConnected } diff --git a/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt b/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt index 52204b84346d..6aeb35b3b158 100644 --- a/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt +++ b/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt @@ -19,6 +19,7 @@ package com.android.systemui.brightness.ui.compose import android.content.Context import android.view.MotionEvent import androidx.annotation.VisibleForTesting +import androidx.compose.animation.animateColorAsState import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.AnimationVector1D import androidx.compose.animation.core.VectorConverter @@ -40,6 +41,7 @@ import androidx.compose.material3.SliderDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf @@ -323,7 +325,7 @@ private fun Modifier.sliderBackground(color: Color) = drawWithCache { fun BrightnessSliderContainer( viewModel: BrightnessSliderViewModel, modifier: Modifier = Modifier, - containerColor: Color = colorResource(R.color.shade_scrim_background_dark), + containerColors: ContainerColors, ) { val gamma = viewModel.currentBrightness.value if (gamma == BrightnessSliderViewModel.initialValue.value) { // Ignore initial negative value. @@ -344,6 +346,16 @@ fun BrightnessSliderContainer( DisposableEffect(Unit) { onDispose { viewModel.setIsDragging(false) } } + var dragging by remember { mutableStateOf(false) } + + // Use dragging instead of viewModel.showMirror so the color starts changing as soon as the + // dragging state changes. If not, we may be waiting for the background to finish fading in + // when stopping dragging + val containerColor by + animateColorAsState( + if (dragging) containerColors.mirrorColor else containerColors.idleColor + ) + Box( modifier = modifier @@ -360,10 +372,12 @@ fun BrightnessSliderContainer( onRestrictedClick = viewModel::showPolicyRestrictionDialog, onDrag = { viewModel.setIsDragging(true) + dragging = true coroutineScope.launch { viewModel.onDrag(Drag.Dragging(GammaBrightness(it))) } }, onStop = { viewModel.setIsDragging(false) + dragging = false coroutineScope.launch { viewModel.onDrag(Drag.Stopped(GammaBrightness(it))) } }, modifier = @@ -392,6 +406,15 @@ fun BrightnessSliderContainer( } } +data class ContainerColors(val idleColor: Color, val mirrorColor: Color) { + companion object { + fun singleColor(color: Color) = ContainerColors(color, color) + + val defaultContainerColor: Color + @Composable @ReadOnlyComposable get() = colorResource(R.color.shade_panel_fallback) + } +} + private object Dimensions { val SliderBackgroundFrameSize = DpSize(10.dp, 6.dp) val SliderBackgroundRoundedCorner = 24.dp diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/TouchHandlingView.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/TouchHandlingView.kt index 42f1b738ec20..6c3535a42a6e 100644 --- a/packages/SystemUI/src/com/android/systemui/common/ui/view/TouchHandlingView.kt +++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/TouchHandlingView.kt @@ -27,17 +27,17 @@ import android.view.ViewConfiguration import android.view.accessibility.AccessibilityNodeInfo import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction import androidx.core.view.accessibility.AccessibilityNodeInfoCompat +import com.android.systemui.Flags.doubleTapToSleep import com.android.systemui.log.TouchHandlingViewLogger import com.android.systemui.shade.TouchLogger -import kotlin.math.pow -import kotlin.math.sqrt import kotlinx.coroutines.DisposableHandle /** - * View designed to handle long-presses. + * View designed to handle long-presses and double taps. * - * The view will not handle any long pressed by default. To set it up, set up a listener and, when - * ready to start consuming long-presses, set [setLongPressHandlingEnabled] to `true`. + * The view will not handle any gestures by default. To set it up, set up a listener and, when ready + * to start consuming gestures, set the gesture's enable function ([setLongPressHandlingEnabled], + * [setDoublePressHandlingEnabled]) to `true`. */ class TouchHandlingView( context: Context, @@ -62,6 +62,9 @@ class TouchHandlingView( /** Notifies that the gesture was too short for a long press, it is actually a click. */ fun onSingleTapDetected(view: View, x: Int, y: Int) = Unit + + /** Notifies that a double tap has been detected by the given view. */ + fun onDoubleTapDetected(view: View) = Unit } var listener: Listener? = null @@ -70,6 +73,7 @@ class TouchHandlingView( private val interactionHandler: TouchHandlingViewInteractionHandler by lazy { TouchHandlingViewInteractionHandler( + context = context, postDelayed = { block, timeoutMs -> val dispatchToken = Any() @@ -84,6 +88,9 @@ class TouchHandlingView( onSingleTapDetected = { x, y -> listener?.onSingleTapDetected(this@TouchHandlingView, x = x, y = y) }, + onDoubleTapDetected = { + if (doubleTapToSleep()) listener?.onDoubleTapDetected(this@TouchHandlingView) + }, longPressDuration = longPressDuration, allowedTouchSlop = allowedTouchSlop, logger = logger, @@ -100,13 +107,17 @@ class TouchHandlingView( interactionHandler.isLongPressHandlingEnabled = isEnabled } + fun setDoublePressHandlingEnabled(isEnabled: Boolean) { + interactionHandler.isDoubleTapHandlingEnabled = isEnabled + } + override fun dispatchTouchEvent(event: MotionEvent): Boolean { return TouchLogger.logDispatchTouch("long_press", event, super.dispatchTouchEvent(event)) } @SuppressLint("ClickableViewAccessibility") - override fun onTouchEvent(event: MotionEvent?): Boolean { - return interactionHandler.onTouchEvent(event?.toModel()) + override fun onTouchEvent(event: MotionEvent): Boolean { + return interactionHandler.onTouchEvent(event) } private fun setupAccessibilityDelegate() { @@ -154,33 +165,3 @@ class TouchHandlingView( } } } - -private fun MotionEvent.toModel(): TouchHandlingViewInteractionHandler.MotionEventModel { - return when (actionMasked) { - MotionEvent.ACTION_DOWN -> - TouchHandlingViewInteractionHandler.MotionEventModel.Down(x = x.toInt(), y = y.toInt()) - MotionEvent.ACTION_MOVE -> - TouchHandlingViewInteractionHandler.MotionEventModel.Move( - distanceMoved = distanceMoved() - ) - MotionEvent.ACTION_UP -> - TouchHandlingViewInteractionHandler.MotionEventModel.Up( - distanceMoved = distanceMoved(), - gestureDuration = gestureDuration(), - ) - MotionEvent.ACTION_CANCEL -> TouchHandlingViewInteractionHandler.MotionEventModel.Cancel - else -> TouchHandlingViewInteractionHandler.MotionEventModel.Other - } -} - -private fun MotionEvent.distanceMoved(): Float { - return if (historySize > 0) { - sqrt((x - getHistoricalX(0)).pow(2) + (y - getHistoricalY(0)).pow(2)) - } else { - 0f - } -} - -private fun MotionEvent.gestureDuration(): Long { - return eventTime - downTime -} diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/TouchHandlingViewInteractionHandler.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/TouchHandlingViewInteractionHandler.kt index 5863fc644c8e..fe509d74edc0 100644 --- a/packages/SystemUI/src/com/android/systemui/common/ui/view/TouchHandlingViewInteractionHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/TouchHandlingViewInteractionHandler.kt @@ -17,12 +17,20 @@ package com.android.systemui.common.ui.view +import android.content.Context import android.graphics.Point +import android.view.GestureDetector +import android.view.MotionEvent +import android.view.ViewConfiguration import com.android.systemui.log.TouchHandlingViewLogger +import kotlin.math.pow +import kotlin.math.sqrt +import kotlin.properties.Delegates import kotlinx.coroutines.DisposableHandle /** Encapsulates logic to handle complex touch interactions with a [TouchHandlingView]. */ class TouchHandlingViewInteractionHandler( + context: Context, /** * Callback to run the given [Runnable] with the given delay, returning a [DisposableHandle] * allowing the delayed runnable to be canceled before it is run. @@ -34,6 +42,8 @@ class TouchHandlingViewInteractionHandler( private val onLongPressDetected: (x: Int, y: Int) -> Unit, /** Callback reporting the a single tap gesture was detected at the given coordinates. */ private val onSingleTapDetected: (x: Int, y: Int) -> Unit, + /** Callback reporting that a double tap gesture was detected. */ + private val onDoubleTapDetected: () -> Unit, /** Time for the touch to be considered a long-press in ms */ var longPressDuration: () -> Long, /** @@ -58,48 +68,98 @@ class TouchHandlingViewInteractionHandler( } var isLongPressHandlingEnabled: Boolean = false + var isDoubleTapHandlingEnabled: Boolean = false var scheduledLongPressHandle: DisposableHandle? = null + private var doubleTapAwaitingUp: Boolean = false + private var lastDoubleTapDownEventTime: Long? = null + /** Record coordinate for last DOWN event for single tap */ val lastEventDownCoordinate = Point(-1, -1) - fun onTouchEvent(event: MotionEventModel?): Boolean { - if (!isLongPressHandlingEnabled) { - return false - } - return when (event) { - is MotionEventModel.Down -> { - scheduleLongPress(event.x, event.y) - lastEventDownCoordinate.x = event.x - lastEventDownCoordinate.y = event.y - true + private val gestureDetector = + GestureDetector( + context, + object : GestureDetector.SimpleOnGestureListener() { + override fun onDoubleTap(event: MotionEvent): Boolean { + if (isDoubleTapHandlingEnabled) { + doubleTapAwaitingUp = true + lastDoubleTapDownEventTime = event.eventTime + return true + } + return false + } + }, + ) + + fun onTouchEvent(event: MotionEvent): Boolean { + if (isDoubleTapHandlingEnabled) { + gestureDetector.onTouchEvent(event) + if (event.actionMasked == MotionEvent.ACTION_UP && doubleTapAwaitingUp) { + lastDoubleTapDownEventTime?.let { time -> + if ( + event.eventTime - time < ViewConfiguration.getDoubleTapTimeout() + ) { + cancelScheduledLongPress() + onDoubleTapDetected() + } + } + doubleTapAwaitingUp = false + } else if (event.actionMasked == MotionEvent.ACTION_CANCEL && doubleTapAwaitingUp) { + doubleTapAwaitingUp = false } - is MotionEventModel.Move -> { - if (event.distanceMoved > allowedTouchSlop) { - logger?.cancelingLongPressDueToTouchSlop(event.distanceMoved, allowedTouchSlop) + } + + if (isLongPressHandlingEnabled) { + val motionEventModel = event.toModel() + + return when (motionEventModel) { + is MotionEventModel.Down -> { + scheduleLongPress(motionEventModel.x, motionEventModel.y) + lastEventDownCoordinate.x = motionEventModel.x + lastEventDownCoordinate.y = motionEventModel.y + true + } + + is MotionEventModel.Move -> { + if (motionEventModel.distanceMoved > allowedTouchSlop) { + logger?.cancelingLongPressDueToTouchSlop( + motionEventModel.distanceMoved, + allowedTouchSlop, + ) + cancelScheduledLongPress() + } + false + } + + is MotionEventModel.Up -> { + logger?.onUpEvent( + motionEventModel.distanceMoved, + allowedTouchSlop, + motionEventModel.gestureDuration, + ) cancelScheduledLongPress() + if ( + motionEventModel.distanceMoved <= allowedTouchSlop && + motionEventModel.gestureDuration < longPressDuration() + ) { + logger?.dispatchingSingleTap() + dispatchSingleTap(lastEventDownCoordinate.x, lastEventDownCoordinate.y) + } + false } - false - } - is MotionEventModel.Up -> { - logger?.onUpEvent(event.distanceMoved, allowedTouchSlop, event.gestureDuration) - cancelScheduledLongPress() - if ( - event.distanceMoved <= allowedTouchSlop && - event.gestureDuration < longPressDuration() - ) { - logger?.dispatchingSingleTap() - dispatchSingleTap(lastEventDownCoordinate.x, lastEventDownCoordinate.y) + + is MotionEventModel.Cancel -> { + logger?.motionEventCancelled() + cancelScheduledLongPress() + false } - false - } - is MotionEventModel.Cancel -> { - logger?.motionEventCancelled() - cancelScheduledLongPress() - false + + else -> false } - else -> false } + + return false } private fun scheduleLongPress(x: Int, y: Int) { @@ -134,4 +194,30 @@ class TouchHandlingViewInteractionHandler( onSingleTapDetected(x, y) } + + private fun MotionEvent.toModel(): MotionEventModel { + return when (actionMasked) { + MotionEvent.ACTION_DOWN -> MotionEventModel.Down(x = x.toInt(), y = y.toInt()) + MotionEvent.ACTION_MOVE -> MotionEventModel.Move(distanceMoved = distanceMoved()) + MotionEvent.ACTION_UP -> + MotionEventModel.Up( + distanceMoved = distanceMoved(), + gestureDuration = gestureDuration(), + ) + MotionEvent.ACTION_CANCEL -> MotionEventModel.Cancel + else -> MotionEventModel.Other + } + } + + private fun MotionEvent.distanceMoved(): Float { + return if (historySize > 0) { + sqrt((x - getHistoricalX(0)).pow(2) + (y - getHistoricalY(0)).pow(2)) + } else { + 0f + } + } + + private fun MotionEvent.gestureDuration(): Long { + return eventTime - downTime + } } diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt index 140db7b7a0b7..62a98d7a48ea 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt @@ -377,12 +377,15 @@ constructor( MutableStateFlow(false) } - val inAllowedKeyguardState = - keyguardTransitionInteractor.startedKeyguardTransitionStep.map { - it.to == KeyguardState.LOCKSCREEN || it.to == KeyguardState.GLANCEABLE_HUB - } - - allOf(inAllowedDeviceState, inAllowedKeyguardState) + if (v2FlagEnabled()) { + val inAllowedKeyguardState = + keyguardTransitionInteractor.startedKeyguardTransitionStep.map { + it.to == KeyguardState.LOCKSCREEN || it.to == KeyguardState.GLANCEABLE_HUB + } + allOf(inAllowedDeviceState, inAllowedKeyguardState) + } else { + inAllowedDeviceState + } } companion object { diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java index a25faa3a7aec..11b42a8eafd6 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java @@ -36,6 +36,8 @@ import com.android.systemui.dock.DockManager; import com.android.systemui.dock.DockManagerImpl; import com.android.systemui.doze.DozeHost; import com.android.systemui.education.dagger.ContextualEducationModule; +import com.android.systemui.topwindoweffects.dagger.SqueezeEffectRepositoryModule; +import com.android.systemui.topwindoweffects.dagger.TopLevelWindowEffectsModule; import com.android.systemui.emergency.EmergencyGestureModule; import com.android.systemui.inputdevice.tutorial.KeyboardTouchpadTutorialModule; import com.android.systemui.keyboard.shortcut.ShortcutHelperModule; @@ -160,12 +162,14 @@ import javax.inject.Named; StatusBarPhoneModule.class, SystemActionsModule.class, ShadeModule.class, + SqueezeEffectRepositoryModule.class, StartCentralSurfacesModule.class, SceneContainerFrameworkModule.class, SysUICoroutinesModule.class, SysUIUnfoldStartableModule.class, UnfoldTransitionModule.Startables.class, ToastModule.class, + TopLevelWindowEffectsModule.class, TouchpadTutorialModule.class, VolumeModule.class, WallpaperModule.class, diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricAuthInteractor.kt index 69da67e055fe..1e7bec257432 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricAuthInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricAuthInteractor.kt @@ -68,10 +68,4 @@ constructor( emptyFlow() } } - - /** Triggered if a face failure occurs regardless of the mode. */ - val faceFailure: Flow<FailedFaceAuthenticationStatus> = - deviceEntryFaceAuthInteractor.authenticationStatus.filterIsInstance< - FailedFaceAuthenticationStatus - >() } diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt index 38e0503440f9..09936839c590 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt @@ -22,7 +22,6 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dump.DumpManager import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository -import com.android.systemui.keyguard.domain.interactor.KeyguardBypassInteractor import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.power.shared.model.WakeSleepReason import com.android.systemui.util.kotlin.FlowDumperImpl @@ -49,8 +48,6 @@ class DeviceEntryHapticsInteractor constructor( biometricSettingsRepository: BiometricSettingsRepository, deviceEntryBiometricAuthInteractor: DeviceEntryBiometricAuthInteractor, - deviceEntryFaceAuthInteractor: DeviceEntryFaceAuthInteractor, - keyguardBypassInteractor: KeyguardBypassInteractor, deviceEntryFingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor, deviceEntrySourceInteractor: DeviceEntrySourceInteractor, fingerprintPropertyRepository: FingerprintPropertyRepository, @@ -83,7 +80,12 @@ constructor( emit(recentPowerButtonPressThresholdMs * -1L - 1L) } - private val playHapticsOnDeviceEntry: Flow<Boolean> = + /** + * Indicates when success haptics should play when the device is entered. This always occurs on + * successful fingerprint authentications. It also occurs on successful face authentication but + * only if the lockscreen is bypassed. + */ + val playSuccessHapticOnDeviceEntry: Flow<Unit> = deviceEntrySourceInteractor.deviceEntryFromBiometricSource .sample( combine( @@ -93,29 +95,17 @@ constructor( ::Triple, ) ) - .map { (sideFpsEnrolled, powerButtonDown, lastPowerButtonWakeup) -> + .filter { (sideFpsEnrolled, powerButtonDown, lastPowerButtonWakeup) -> val sideFpsAllowsHaptic = !powerButtonDown && systemClock.uptimeMillis() - lastPowerButtonWakeup > recentPowerButtonPressThresholdMs val allowHaptic = !sideFpsEnrolled || sideFpsAllowsHaptic if (!allowHaptic) { - logger.d( - "Skip success entry haptic from power button. Recent power button press or button is down." - ) + logger.d("Skip success haptic. Recent power button press or button is down.") } allowHaptic } - - private val playHapticsOnFaceAuthSuccessAndBypassDisabled: Flow<Boolean> = - deviceEntryFaceAuthInteractor.isAuthenticated - .filter { it } - .sample(keyguardBypassInteractor.isBypassAvailable) - .map { !it } - - val playSuccessHaptic: Flow<Unit> = - merge(playHapticsOnDeviceEntry, playHapticsOnFaceAuthSuccessAndBypassDisabled) - .filter { it } // map to Unit .map {} .dumpWhileCollecting("playSuccessHaptic") @@ -123,7 +113,7 @@ constructor( private val playErrorHapticForBiometricFailure: Flow<Unit> = merge( deviceEntryFingerprintAuthInteractor.fingerprintFailure, - deviceEntryBiometricAuthInteractor.faceFailure, + deviceEntryBiometricAuthInteractor.faceOnlyFaceFailure, ) // map to Unit .map {} diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/FakePerDisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/FakePerDisplayRepository.kt index 083191c8ecde..a56710ee3772 100644 --- a/packages/SystemUI/src/com/android/systemui/display/data/repository/FakePerDisplayRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/FakePerDisplayRepository.kt @@ -32,9 +32,6 @@ class FakePerDisplayRepository<T> : PerDisplayRepository<T> { return instances[displayId] } - override val displayIds: Set<Int> - get() = instances.keys - override val debugName: String get() = "FakePerDisplayRepository" } diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepository.kt index d27e33e53dbb..d1d013542fbf 100644 --- a/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayRepository.kt @@ -86,9 +86,6 @@ interface PerDisplayRepository<T> { /** Gets the cached instance or create a new one for a given display. */ operator fun get(displayId: Int): T? - /** List of display ids for which this repository has an instance. */ - val displayIds: Set<Int> - /** Debug name for this repository, mainly for tracing and logging. */ val debugName: String } @@ -122,9 +119,6 @@ constructor( backgroundApplicationScope.launch("$debugName#start") { start() } } - override val displayIds: Set<Int> - get() = perDisplayInstances.keys - private suspend fun start() { dumpManager.registerNormalDumpable("PerDisplayRepository-${debugName}", this) displayRepository.displayIds.collectLatest { displayIds -> @@ -199,8 +193,6 @@ class DefaultDisplayOnlyInstanceRepositoryImpl<T>( private val lazyDefaultDisplayInstance by lazy { instanceProvider.createInstance(Display.DEFAULT_DISPLAY) } - override val displayIds: Set<Int> = setOf(Display.DEFAULT_DISPLAY) - override fun get(displayId: Int): T? = lazyDefaultDisplayInstance } @@ -214,7 +206,5 @@ class DefaultDisplayOnlyInstanceRepositoryImpl<T>( */ class SingleInstanceRepositoryImpl<T>(override val debugName: String, private val instance: T) : PerDisplayRepository<T> { - override val displayIds: Set<Int> = setOf(Display.DEFAULT_DISPLAY) - override fun get(displayId: Int): T? = instance } diff --git a/packages/SystemUI/src/com/android/systemui/keyevent/data/repository/KeyEventRepository.kt b/packages/SystemUI/src/com/android/systemui/keyevent/data/repository/KeyEventRepository.kt index 9da9a7328659..32257df40d02 100644 --- a/packages/SystemUI/src/com/android/systemui/keyevent/data/repository/KeyEventRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyevent/data/repository/KeyEventRepository.kt @@ -18,7 +18,7 @@ package com.android.systemui.keyevent.data.repository import android.view.KeyEvent import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.statusbar.CommandQueue import javax.inject.Inject @@ -29,6 +29,9 @@ import kotlinx.coroutines.flow.Flow interface KeyEventRepository { /** Observable for whether the power button key is pressed/down or not. */ val isPowerButtonDown: Flow<Boolean> + + /** Observable for when the power button is being pressed but till the duration of long press */ + val isPowerButtonLongPressed: Flow<Boolean> } @SysUISingleton @@ -51,6 +54,21 @@ constructor( awaitClose { commandQueue.removeCallback(callback) } } + override val isPowerButtonLongPressed: Flow<Boolean> = conflatedCallbackFlow { + val callback = + object : CommandQueue.Callbacks { + override fun handleSystemKey(event: KeyEvent) { + if (event.keyCode == KeyEvent.KEYCODE_POWER) { + trySendWithFailureLogging(event.action == KeyEvent.ACTION_DOWN + && event.isLongPress, TAG, "updated isPowerButtonLongPressed") + } + } + } + trySendWithFailureLogging(false, TAG, "init isPowerButtonLongPressed") + commandQueue.addCallback(callback) + awaitClose { commandQueue.removeCallback(callback) } + } + companion object { private const val TAG = "KeyEventRepositoryImpl" } diff --git a/packages/SystemUI/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractor.kt index 9949fa589cd5..ec9bbfb1bc77 100644 --- a/packages/SystemUI/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractor.kt @@ -32,4 +32,5 @@ constructor( repository: KeyEventRepository, ) { val isPowerButtonDown = repository.isPowerButtonDown + val isPowerButtonLongPressed = repository.isPowerButtonLongPressed } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java index 3b85b571023f..9ade5036b9a8 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java @@ -51,10 +51,10 @@ import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.systemui.SystemUIAppComponentFactoryBase; import com.android.systemui.dagger.qualifiers.Background; +import com.android.systemui.media.NotificationMediaManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.res.R; import com.android.systemui.settings.UserTracker; -import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.KeyguardBypassController; diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractor.kt index 705eaa22aa9a..55534c4f1444 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractor.kt @@ -20,11 +20,14 @@ package com.android.systemui.keyguard.domain.interactor import android.content.Context import android.content.Intent import android.content.IntentFilter +import android.os.PowerManager +import android.provider.Settings import android.view.accessibility.AccessibilityManager import androidx.annotation.VisibleForTesting import com.android.app.tracing.coroutines.launchTraced as launch import com.android.internal.logging.UiEvent import com.android.internal.logging.UiEventLogger +import com.android.systemui.Flags.doubleTapToSleep import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application @@ -36,10 +39,13 @@ import com.android.systemui.res.R import com.android.systemui.shade.PulsingGestureListener import com.android.systemui.shade.ShadeDisplayAware import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper +import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepository +import com.android.systemui.util.time.SystemClock import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow @@ -66,10 +72,13 @@ constructor( private val accessibilityManager: AccessibilityManagerWrapper, private val pulsingGestureListener: PulsingGestureListener, private val faceAuthInteractor: DeviceEntryFaceAuthInteractor, + private val secureSettingsRepository: UserAwareSecureSettingsRepository, + private val powerManager: PowerManager, + private val systemClock: SystemClock, ) { /** Whether the long-press handling feature should be enabled. */ val isLongPressHandlingEnabled: StateFlow<Boolean> = - if (isFeatureEnabled()) { + if (isLongPressFeatureEnabled()) { combine( transitionInteractor.isFinishedIn(KeyguardState.LOCKSCREEN), repository.isQuickSettingsVisible, @@ -85,6 +94,30 @@ constructor( initialValue = false, ) + /** Whether the double tap handling handling feature should be enabled. */ + val isDoubleTapHandlingEnabled: StateFlow<Boolean> = + if (isDoubleTapFeatureEnabled()) { + combine( + transitionInteractor.transitionValue(KeyguardState.LOCKSCREEN), + repository.isQuickSettingsVisible, + isDoubleTapSettingEnabled(), + ) { + isFullyTransitionedToLockScreen, + isQuickSettingsVisible, + isDoubleTapSettingEnabled -> + isFullyTransitionedToLockScreen == 1f && + !isQuickSettingsVisible && + isDoubleTapSettingEnabled + } + } else { + flowOf(false) + } + .stateIn( + scope = scope, + started = SharingStarted.WhileSubscribed(), + initialValue = false, + ) + private val _isMenuVisible = MutableStateFlow(false) /** Model for whether the menu should be shown. */ val isMenuVisible: StateFlow<Boolean> = @@ -116,7 +149,7 @@ constructor( private var delayedHideMenuJob: Job? = null init { - if (isFeatureEnabled()) { + if (isLongPressFeatureEnabled()) { broadcastDispatcher .broadcastFlow(IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) .onEach { hideMenu() } @@ -175,17 +208,30 @@ constructor( /** Notifies that the lockscreen has been double clicked. */ fun onDoubleClick() { - pulsingGestureListener.onDoubleTapEvent() + if (isDoubleTapHandlingEnabled.value) { + powerManager.goToSleep(systemClock.uptimeMillis()) + } else { + pulsingGestureListener.onDoubleTapEvent() + } + } + + private fun isDoubleTapSettingEnabled(): Flow<Boolean> { + return secureSettingsRepository.boolSetting(Settings.Secure.DOUBLE_TAP_TO_SLEEP) } private fun showSettings() { _shouldOpenSettings.value = true } - private fun isFeatureEnabled(): Boolean { + private fun isLongPressFeatureEnabled(): Boolean { return context.resources.getBoolean(R.bool.long_press_keyguard_customize_lockscreen_enabled) } + private fun isDoubleTapFeatureEnabled(): Boolean { + return doubleTapToSleep() && + context.resources.getBoolean(com.android.internal.R.bool.config_supportDoubleTapSleep) + } + /** Updates application state to ask to show the menu. */ private fun showMenu() { _isMenuVisible.value = true diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt index 17e14c3e83da..70a827d5e45b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt @@ -48,7 +48,7 @@ object DeviceEntryIconViewBinder { /** * Updates UI for: * - device entry containing view (parent view for the below views) - * - long-press handling view (transparent, no UI) + * - touch handling view (transparent, no UI) * - foreground icon view (lock/unlock/fingerprint) * - background view (optional) */ diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewSmartspaceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewSmartspaceViewBinder.kt index 741b149f29da..92b9da6790d1 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewSmartspaceViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewSmartspaceViewBinder.kt @@ -31,6 +31,37 @@ import com.android.systemui.plugins.clocks.ClockPreviewConfig object KeyguardPreviewSmartspaceViewBinder { @JvmStatic + fun bind(parentView: View, viewModel: KeyguardPreviewSmartspaceViewModel) { + if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) { + val largeDateView = + parentView.findViewById<View>( + com.android.systemui.shared.R.id.date_smartspace_view_large + ) + val smallDateView = + parentView.findViewById<View>(com.android.systemui.shared.R.id.date_smartspace_view) + parentView.repeatWhenAttached { + repeatOnLifecycle(Lifecycle.State.STARTED) { + launch("$TAG#viewModel.selectedClockSize") { + viewModel.previewingClockSize.collect { + when (it) { + ClockSizeSetting.DYNAMIC -> { + smallDateView?.visibility = View.GONE + largeDateView?.visibility = View.VISIBLE + } + + ClockSizeSetting.SMALL -> { + smallDateView?.visibility = View.VISIBLE + largeDateView?.visibility = View.GONE + } + } + } + } + } + } + } + } + + @JvmStatic fun bind( smartspace: View, viewModel: KeyguardPreviewSmartspaceViewModel, @@ -44,6 +75,7 @@ object KeyguardPreviewSmartspaceViewBinder { when (it) { ClockSizeSetting.DYNAMIC -> viewModel.getLargeClockSmartspaceTopPadding(clockPreviewConfig) + ClockSizeSetting.SMALL -> viewModel.getSmallClockSmartspaceTopPadding(clockPreviewConfig) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt index 45801ba3517a..aeb327035c79 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt @@ -333,7 +333,7 @@ object KeyguardRootViewBinder { if (deviceEntryHapticsInteractor != null && vibratorHelper != null) { launch { - deviceEntryHapticsInteractor.playSuccessHaptic.collect { + deviceEntryHapticsInteractor.playSuccessHapticOnDeviceEntry.collect { if (msdlFeedback()) { msdlPlayer?.playToken( MSDLToken.UNLOCK, @@ -474,7 +474,7 @@ object KeyguardRootViewBinder { val transition = blueprintViewModel.currentTransition.value val shouldAnimate = transition != null && transition.config.type.animateNotifChanges if (prevTransition == transition && shouldAnimate) { - logger.w("Skipping; layout during transition") + logger.w("Skipping onNotificationContainerBoundsChanged during transition") return } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt index e81d5354ec6e..5ef2d6fd3256 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt @@ -29,6 +29,7 @@ import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.plugins.clocks.VRectF import com.android.systemui.res.R import com.android.systemui.shared.R as sharedR import kotlinx.coroutines.DisposableHandle @@ -135,7 +136,7 @@ object KeyguardSmartspaceViewBinder { } } - if (clockBounds == null) return@collect + if (clockBounds == VRectF.ZERO) return@collect if (isLargeClock) { val largeDateHeight = keyguardRootView diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardTouchViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardTouchViewBinder.kt index 195413a80f4b..485e1ce5b2f7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardTouchViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardTouchViewBinder.kt @@ -75,6 +75,13 @@ object KeyguardTouchViewBinder { onSingleTap(x, y) } + + override fun onDoubleTapDetected(view: View) { + if (falsingManager.isFalseDoubleTap()) { + return + } + viewModel.onDoubleClick() + } } view.repeatWhenAttached { @@ -90,9 +97,20 @@ object KeyguardTouchViewBinder { } } } + launch("$TAG#viewModel.isDoubleTapHandlingEnabled") { + viewModel.isDoubleTapHandlingEnabled.collect { isEnabled -> + view.setDoublePressHandlingEnabled(isEnabled) + view.contentDescription = + if (isEnabled) { + view.resources.getString(R.string.accessibility_desc_lock_screen) + } else { + null + } + } + } } } } - private const val TAG = "KeyguardLongPressViewBinder" + private const val TAG = "KeyguardTouchViewBinder" } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt index 242926b3e1d1..d749e3c11378 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt @@ -68,6 +68,7 @@ import com.android.systemui.monet.ColorScheme import com.android.systemui.monet.Style import com.android.systemui.plugins.clocks.ClockController import com.android.systemui.plugins.clocks.ClockPreviewConfig +import com.android.systemui.plugins.clocks.ContextExt.getId import com.android.systemui.plugins.clocks.ThemeConfig import com.android.systemui.plugins.clocks.WeatherData import com.android.systemui.res.R @@ -126,6 +127,7 @@ constructor( private val displayId = bundle.getInt(KEY_DISPLAY_ID, DEFAULT_DISPLAY) private val display: Display? = displayManager.getDisplay(displayId) + /** * Returns a key that should make the KeyguardPreviewRenderer unique and if two of them have the * same key they will be treated as the same KeyguardPreviewRenderer. Primary this is used to @@ -144,6 +146,8 @@ constructor( get() = checkNotNull(host.surfacePackage) private var smartSpaceView: View? = null + private var largeDateView: View? = null + private var smallDateView: View? = null private val disposables = DisposableHandles() private var isDestroyed = false @@ -181,7 +185,7 @@ constructor( ContextThemeWrapper(context.createDisplayContext(it), context.getTheme()) } ?: context - val rootView = FrameLayout(previewContext) + val rootView = ConstraintLayout(previewContext) setupKeyguardRootView(previewContext, rootView) @@ -252,6 +256,24 @@ constructor( fun onClockSizeSelected(clockSize: ClockSizeSetting) { smartspaceViewModel.setOverrideClockSize(clockSize) + if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) { + when (clockSize) { + ClockSizeSetting.DYNAMIC -> { + largeDateView?.post { + smallDateView?.visibility = View.GONE + largeDateView?.visibility = View.VISIBLE + } + } + + ClockSizeSetting.SMALL -> { + largeDateView?.post { + smallDateView?.visibility = View.VISIBLE + largeDateView?.visibility = View.GONE + } + } + } + smartSpaceView?.post { smartSpaceView?.visibility = View.GONE } + } } fun destroy() { @@ -280,7 +302,7 @@ constructor( * * The end padding is as follows: Below clock padding end */ - private fun setUpSmartspace(previewContext: Context, parentView: ViewGroup) { + private fun setUpSmartspace(previewContext: Context, parentView: ConstraintLayout) { if ( !lockscreenSmartspaceController.isEnabled || !lockscreenSmartspaceController.isDateWeatherDecoupled @@ -292,40 +314,90 @@ constructor( parentView.removeView(smartSpaceView) } - smartSpaceView = - lockscreenSmartspaceController.buildAndConnectDateView( - parent = parentView, - isLargeClock = false, - ) + if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) { + val cs = ConstraintSet() + cs.clone(parentView) + cs.apply { + val largeClockViewId = previewContext.getId("lockscreen_clock_view_large") + val smallClockViewId = previewContext.getId("lockscreen_clock_view") + largeDateView = + lockscreenSmartspaceController + .buildAndConnectDateView(parentView, true) + ?.also { view -> + constrainWidth(view.id, ConstraintSet.WRAP_CONTENT) + constrainHeight(view.id, ConstraintSet.WRAP_CONTENT) + connect(view.id, START, largeClockViewId, START) + connect(view.id, ConstraintSet.END, largeClockViewId, ConstraintSet.END) + connect( + view.id, + TOP, + largeClockViewId, + ConstraintSet.BOTTOM, + smartspaceViewModel.getDateWeatherEndPadding(previewContext), + ) + } + smallDateView = + lockscreenSmartspaceController + .buildAndConnectDateView(parentView, false) + ?.also { view -> + constrainWidth(view.id, ConstraintSet.WRAP_CONTENT) + constrainHeight(view.id, ConstraintSet.WRAP_CONTENT) + connect( + view.id, + START, + smallClockViewId, + ConstraintSet.END, + context.resources.getDimensionPixelSize( + R.dimen.smartspace_padding_horizontal + ), + ) + connect(view.id, TOP, smallClockViewId, TOP) + connect( + view.id, + ConstraintSet.BOTTOM, + smallClockViewId, + ConstraintSet.BOTTOM, + ) + } + parentView.addView(largeDateView) + parentView.addView(smallDateView) + } + cs.applyTo(parentView) + } else { + smartSpaceView = + lockscreenSmartspaceController.buildAndConnectDateView( + parent = parentView, + isLargeClock = false, + ) - val topPadding: Int = - smartspaceViewModel.getLargeClockSmartspaceTopPadding( - ClockPreviewConfig( - previewContext, - getPreviewShadeLayoutWide(display!!), - SceneContainerFlag.isEnabled, + val topPadding: Int = + smartspaceViewModel.getLargeClockSmartspaceTopPadding( + ClockPreviewConfig( + previewContext, + getPreviewShadeLayoutWide(display!!), + SceneContainerFlag.isEnabled, + ) ) - ) - val startPadding: Int = smartspaceViewModel.getDateWeatherStartPadding(previewContext) - val endPadding: Int = smartspaceViewModel.getDateWeatherEndPadding(previewContext) - - smartSpaceView?.let { - it.setPaddingRelative(startPadding, topPadding, endPadding, 0) - it.isClickable = false - it.isInvisible = true - parentView.addView( - it, - FrameLayout.LayoutParams( - FrameLayout.LayoutParams.MATCH_PARENT, - FrameLayout.LayoutParams.WRAP_CONTENT, - ), - ) + val startPadding: Int = smartspaceViewModel.getDateWeatherStartPadding(previewContext) + val endPadding: Int = smartspaceViewModel.getDateWeatherEndPadding(previewContext) + + smartSpaceView?.let { + it.setPaddingRelative(startPadding, topPadding, endPadding, 0) + it.isClickable = false + it.isInvisible = true + parentView.addView( + it, + FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, + FrameLayout.LayoutParams.WRAP_CONTENT, + ), + ) + } + smartSpaceView?.alpha = if (shouldHighlightSelectedAffordance) DIM_ALPHA else 1.0f } - - smartSpaceView?.alpha = if (shouldHighlightSelectedAffordance) DIM_ALPHA else 1.0f } - private fun setupKeyguardRootView(previewContext: Context, rootView: FrameLayout) { + private fun setupKeyguardRootView(previewContext: Context, rootView: ConstraintLayout) { val keyguardRootView = KeyguardRootView(previewContext, null) rootView.addView( keyguardRootView, @@ -341,6 +413,13 @@ constructor( if (!shouldHideClock) { setUpClock(previewContext, rootView) + if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) { + setUpSmartspace(previewContext, keyguardRootView) + KeyguardPreviewSmartspaceViewBinder.bind( + keyguardRootView, + smartspaceViewModel, + ) + } KeyguardPreviewClockViewBinder.bind( keyguardRootView, clockViewModel, @@ -354,19 +433,22 @@ constructor( ) } - setUpSmartspace(previewContext, rootView) - - smartSpaceView?.let { - KeyguardPreviewSmartspaceViewBinder.bind( - it, - smartspaceViewModel, - clockPreviewConfig = - ClockPreviewConfig( - previewContext, - getPreviewShadeLayoutWide(display!!), - SceneContainerFlag.isEnabled, - ), - ) + if (!com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) { + setUpSmartspace(previewContext, keyguardRootView) + smartSpaceView?.let { + KeyguardPreviewSmartspaceViewBinder.bind( + it, + smartspaceViewModel, + clockPreviewConfig = + ClockPreviewConfig( + previewContext, + getPreviewShadeLayoutWide(display!!), + SceneContainerFlag.isEnabled, + lockId = null, + udfpsTop = null, + ), + ) + } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt index c4a7e1ed95e1..55fac3c7644b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt @@ -36,6 +36,7 @@ import com.android.systemui.keyguard.ui.viewmodel.DreamingToGlanceableHubTransit import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToAodTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToDreamingTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToLockscreenTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToOccludedTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.GoneToAodTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.GoneToDozingTransitionViewModel @@ -272,6 +273,12 @@ abstract class DeviceEntryIconTransitionModule { @Binds @IntoSet + abstract fun glanceableHubToLockscreen( + impl: GlanceableHubToLockscreenTransitionViewModel + ): DeviceEntryIconTransition + + @Binds + @IntoSet abstract fun occludedToGlanceableHub( impl: OccludedToGlanceableHubTransitionViewModel ): DeviceEntryIconTransition diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt index 0ccb24a9858a..20fc88446ce3 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt @@ -61,6 +61,7 @@ constructor( primaryBouncerToLockscreenTransitionViewModel: PrimaryBouncerToLockscreenTransitionViewModel, lockscreenToDozingTransitionViewModel: LockscreenToDozingTransitionViewModel, glanceableHubToAodTransitionViewModel: GlanceableHubToAodTransitionViewModel, + glanceableHubToLockscreenTransitionViewModel: GlanceableHubToLockscreenTransitionViewModel, ) { val color: Flow<Int> = deviceEntryIconViewModel.useBackgroundProtection.flatMapLatest { useBackground -> @@ -108,6 +109,7 @@ constructor( .deviceEntryBackgroundViewAlpha, lockscreenToDozingTransitionViewModel.deviceEntryBackgroundViewAlpha, glanceableHubToAodTransitionViewModel.deviceEntryBackgroundViewAlpha, + glanceableHubToLockscreenTransitionViewModel.deviceEntryBackgroundViewAlpha, ) .merge() .onStart { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt index b4b4c82c59b9..bcbe66642d11 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt @@ -27,6 +27,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.StateToValue +import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import com.android.systemui.keyguard.ui.transitions.GlanceableHubTransition import com.android.systemui.res.R import com.android.systemui.scene.shared.model.Scenes @@ -49,7 +50,7 @@ constructor( @ShadeDisplayAware configurationInteractor: ConfigurationInteractor, animationFlow: KeyguardTransitionAnimationFlow, private val blurFactory: GlanceableHubBlurComponent.Factory, -) : GlanceableHubTransition { +) : GlanceableHubTransition, DeviceEntryIconTransition { private val transitionAnimation = animationFlow .setup( @@ -102,4 +103,8 @@ constructor( val notificationTranslationX: Flow<Float> = keyguardTranslationX.map { it.value }.filterNotNull() + + val deviceEntryBackgroundViewAlpha: Flow<Float> = keyguardAlpha + + override val deviceEntryParentViewAlpha: Flow<Float> = keyguardAlpha } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt index d4676bc9c146..6d8a943d3e28 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt @@ -132,6 +132,8 @@ constructor( private val occludedToAodTransitionViewModel: OccludedToAodTransitionViewModel, private val occludedToDozingTransitionViewModel: OccludedToDozingTransitionViewModel, private val occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel, + private val occludedToPrimaryBouncerTransitionViewModel: + OccludedToPrimaryBouncerTransitionViewModel, private val offToLockscreenTransitionViewModel: OffToLockscreenTransitionViewModel, private val primaryBouncerToAodTransitionViewModel: PrimaryBouncerToAodTransitionViewModel, private val primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel, @@ -288,6 +290,7 @@ constructor( occludedToAodTransitionViewModel.lockscreenAlpha, occludedToDozingTransitionViewModel.lockscreenAlpha, occludedToLockscreenTransitionViewModel.lockscreenAlpha, + occludedToPrimaryBouncerTransitionViewModel.lockscreenAlpha, offToLockscreenTransitionViewModel.lockscreenAlpha, primaryBouncerToAodTransitionViewModel.lockscreenAlpha, primaryBouncerToGoneTransitionViewModel.lockscreenAlpha, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardTouchHandlingViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardTouchHandlingViewModel.kt index 1d2edc6d406b..d4e7af48adfe 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardTouchHandlingViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardTouchHandlingViewModel.kt @@ -33,6 +33,9 @@ constructor( /** Whether the long-press handling feature should be enabled. */ val isLongPressHandlingEnabled: Flow<Boolean> = interactor.isLongPressHandlingEnabled + /** Whether the double tap handling feature should be enabled. */ + val isDoubleTapHandlingEnabled: Flow<Boolean> = interactor.isDoubleTapHandlingEnabled + /** Notifies that the user has long-pressed on the lock screen. * * @param isA11yAction: Whether the action was performed as an a11y action diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToPrimaryBouncerTransitionViewModel.kt index f14a5a282e88..67c3071db390 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToPrimaryBouncerTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToPrimaryBouncerTransitionViewModel.kt @@ -45,6 +45,12 @@ constructor( ) .setupWithoutSceneContainer(edge = Edge.create(OCCLUDED, PRIMARY_BOUNCER)) + /** + * Reasserts that lockscreen content should not be visible. It is possible the keyguard alpha is + * set to 1f if coming from an expanded shade that collapsed to launch an occluding activity. + */ + val lockscreenAlpha: Flow<Float> = transitionAnimation.immediatelyTransitionTo(0f) + override val windowBlurRadius: Flow<Float> = shadeDependentFlows.transitionFlow( flowWhenShadeIsExpanded = diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java index faa6c52162ce..a85b9b04c1ce 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java @@ -327,7 +327,8 @@ public class LogModule { @SysUISingleton @KeyguardBlueprintLog public static LogBuffer provideKeyguardBlueprintLog(LogBufferFactory factory) { - return factory.create("KeyguardBlueprintLog", 100); + // TODO(b/389987229): Reduce back to 100 + return factory.create("KeyguardBlueprintLog", 1000); } /** @@ -337,7 +338,8 @@ public class LogModule { @SysUISingleton @KeyguardClockLog public static LogBuffer provideKeyguardClockLog(LogBufferFactory factory) { - return factory.create("KeyguardClockLog", 100); + // TODO(b/389987229): Reduce back to 100 + return factory.create("KeyguardClockLog", 1000); } /** @@ -347,7 +349,8 @@ public class LogModule { @SysUISingleton @KeyguardSmallClockLog public static LogBuffer provideKeyguardSmallClockLog(LogBufferFactory factory) { - return factory.create("KeyguardSmallClockLog", 100); + // TODO(b/389987229): Reduce back to 100 + return factory.create("KeyguardSmallClockLog", 1000); } /** @@ -357,7 +360,8 @@ public class LogModule { @SysUISingleton @KeyguardLargeClockLog public static LogBuffer provideKeyguardLargeClockLog(LogBufferFactory factory) { - return factory.create("KeyguardLargeClockLog", 100); + // TODO(b/389987229): Reduce back to 100 + return factory.create("KeyguardLargeClockLog", 1000); } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/media/NotificationMediaManager.java index 18f4b4ab9b88..db4c7a5b2ee5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java +++ b/packages/SystemUI/src/com/android/systemui/media/NotificationMediaManager.java @@ -11,9 +11,9 @@ * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and - * limitations under the License + * limitations under the License. */ -package com.android.systemui.statusbar; +package com.android.systemui.media; import static com.android.systemui.Flags.mediaControlsUserInitiatedDeleteintent; @@ -40,6 +40,8 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.media.controls.domain.pipeline.MediaDataManager; import com.android.systemui.media.controls.shared.model.MediaData; import com.android.systemui.media.controls.shared.model.SmartspaceMediaData; +import com.android.systemui.statusbar.NotificationPresenter; +import com.android.systemui.statusbar.StatusBarIconView; import com.android.systemui.statusbar.dagger.CentralSurfacesModule; import com.android.systemui.statusbar.notification.collection.NotifCollection; import com.android.systemui.statusbar.notification.collection.NotifPipeline; diff --git a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java index e7c2a454e16c..b71d8c995e51 100644 --- a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java +++ b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java @@ -17,6 +17,7 @@ package com.android.systemui.media; import android.annotation.Nullable; +import android.content.ContentProvider; import android.content.ContentResolver; import android.content.Context; import android.content.pm.PackageManager.NameNotFoundException; @@ -126,6 +127,8 @@ public class RingtonePlayer implements CoreStartable { Log.d(TAG, "play(token=" + token + ", uri=" + uri + ", uid=" + Binder.getCallingUid() + ")"); } + enforceUriUserId(uri); + Client client; synchronized (mClients) { client = mClients.get(token); @@ -207,6 +210,7 @@ public class RingtonePlayer implements CoreStartable { @Override public String getTitle(Uri uri) { + enforceUriUserId(uri); final UserHandle user = Binder.getCallingUserHandle(); return Ringtone.getTitle(getContextForUser(user), uri, false /*followSettingsUri*/, false /*allowRemote*/); @@ -214,6 +218,7 @@ public class RingtonePlayer implements CoreStartable { @Override public ParcelFileDescriptor openRingtone(Uri uri) { + enforceUriUserId(uri); final UserHandle user = Binder.getCallingUserHandle(); final ContentResolver resolver = getContextForUser(user).getContentResolver(); @@ -241,6 +246,28 @@ public class RingtonePlayer implements CoreStartable { } }; + /** + * Must be called from the Binder calling thread. + * Ensures caller is from the same userId as the content they're trying to access. + * @param uri the URI to check + * @throws SecurityException when in a non-system call and userId in uri differs from the + * caller's userId + */ + private void enforceUriUserId(Uri uri) throws SecurityException { + final int uriUserId = ContentProvider.getUserIdFromUri(uri, UserHandle.myUserId()); + // for a non-system call, verify the URI to play belongs to the same user as the caller + if (UserHandle.isApp(Binder.getCallingUid()) && (UserHandle.myUserId() != uriUserId)) { + final String errorMessage = "Illegal access to uri=" + uri + + " content associated with user=" + uriUserId + + ", current userID: " + UserHandle.myUserId(); + if (android.media.audio.Flags.ringtoneUserUriCheck()) { + throw new SecurityException(errorMessage); + } else { + Log.e(TAG, errorMessage, new Exception()); + } + } + } + private Context getContextForUser(UserHandle user) { try { return mContext.createPackageContextAsUser(mContext.getPackageName(), 0, user); diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt index 46cf0a63e93d..309b6751176c 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt @@ -66,6 +66,7 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager +import com.android.systemui.media.NotificationMediaManager.isPlayingState import com.android.systemui.media.controls.domain.pipeline.MediaDataManager.Companion.isMediaNotification import com.android.systemui.media.controls.domain.resume.MediaResumeListener import com.android.systemui.media.controls.domain.resume.ResumeMediaBrowser @@ -83,7 +84,6 @@ import com.android.systemui.media.controls.util.MediaDataUtils import com.android.systemui.media.controls.util.MediaFlags import com.android.systemui.media.controls.util.MediaUiEventLogger import com.android.systemui.res.R -import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState import com.android.systemui.statusbar.notification.row.HybridGroupManager import com.android.systemui.util.Assert import com.android.systemui.util.Utils diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaActions.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaActions.kt index 5fef81f4596a..da462e6713c6 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaActions.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaActions.kt @@ -30,6 +30,8 @@ import android.service.notification.StatusBarNotification import android.util.Log import androidx.media.utils.MediaConstants import com.android.systemui.Flags +import com.android.systemui.media.NotificationMediaManager.isConnectingState +import com.android.systemui.media.NotificationMediaManager.isPlayingState import com.android.systemui.media.controls.domain.pipeline.LegacyMediaDataManagerImpl.Companion.MAX_COMPACT_ACTIONS import com.android.systemui.media.controls.domain.pipeline.LegacyMediaDataManagerImpl.Companion.MAX_NOTIFICATION_ACTIONS import com.android.systemui.media.controls.shared.MediaControlDrawables @@ -38,8 +40,6 @@ import com.android.systemui.media.controls.shared.model.MediaButton import com.android.systemui.media.controls.shared.model.MediaNotificationAction import com.android.systemui.plugins.ActivityStarter import com.android.systemui.res.R -import com.android.systemui.statusbar.NotificationMediaManager.isConnectingState -import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState import com.android.systemui.util.kotlin.logI private const val TAG = "MediaActions" diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt index 8bb7303a8386..dbd2250a75b0 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt @@ -51,6 +51,7 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.graphics.ImageLoader +import com.android.systemui.media.NotificationMediaManager.isPlayingState import com.android.systemui.media.controls.shared.model.MediaAction import com.android.systemui.media.controls.shared.model.MediaButton import com.android.systemui.media.controls.shared.model.MediaData @@ -60,7 +61,6 @@ import com.android.systemui.media.controls.util.MediaControllerFactory import com.android.systemui.media.controls.util.MediaDataUtils import com.android.systemui.media.controls.util.MediaFlags import com.android.systemui.res.R -import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState import com.android.systemui.statusbar.notification.row.HybridGroupManager import com.android.systemui.util.kotlin.logD import java.util.concurrent.ConcurrentHashMap diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt index fe852ce7979f..94df4b353c94 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt @@ -66,6 +66,7 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager +import com.android.systemui.media.NotificationMediaManager.isPlayingState import com.android.systemui.media.controls.data.repository.MediaDataRepository import com.android.systemui.media.controls.domain.pipeline.MediaDataManager.Companion.isMediaNotification import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor @@ -85,7 +86,6 @@ import com.android.systemui.media.controls.util.MediaFlags import com.android.systemui.media.controls.util.MediaUiEventLogger import com.android.systemui.res.R import com.android.systemui.scene.shared.flag.SceneContainerFlag -import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState import com.android.systemui.statusbar.notification.row.HybridGroupManager import com.android.systemui.util.Assert import com.android.systemui.util.Utils diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt index 49b53c2d78ae..dfb32e66dae5 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt @@ -37,6 +37,7 @@ import com.android.settingslib.media.LocalMediaManager import com.android.settingslib.media.MediaDevice import com.android.settingslib.media.PhoneMediaDevice import com.android.settingslib.media.flags.Flags +import com.android.systemui.Flags.mediaControlsDeviceManagerBackgroundExecution import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.media.controls.shared.MediaControlDrawables @@ -94,8 +95,39 @@ constructor( data: MediaData, immediately: Boolean, receivedSmartspaceCardLatency: Int, - isSsReactivated: Boolean + isSsReactivated: Boolean, ) { + if (mediaControlsDeviceManagerBackgroundExecution()) { + bgExecutor.execute { onMediaLoaded(key, oldKey, data) } + } else { + onMediaLoaded(key, oldKey, data) + } + } + + override fun onMediaDataRemoved(key: String, userInitiated: Boolean) { + if (mediaControlsDeviceManagerBackgroundExecution()) { + bgExecutor.execute { onMediaRemoved(key, userInitiated) } + } else { + onMediaRemoved(key, userInitiated) + } + } + + fun dump(pw: PrintWriter) { + with(pw) { + println("MediaDeviceManager state:") + entries.forEach { (key, entry) -> + println(" key=$key") + entry.dump(pw) + } + } + } + + @MainThread + private fun processDevice(key: String, oldKey: String?, device: MediaDeviceData?) { + listeners.forEach { it.onMediaDeviceChanged(key, oldKey, device) } + } + + private fun onMediaLoaded(key: String, oldKey: String?, data: MediaData) { if (oldKey != null && oldKey != key) { val oldEntry = entries.remove(oldKey) oldEntry?.stop() @@ -104,9 +136,13 @@ constructor( if (entry == null || entry.token != data.token) { entry?.stop() if (data.device != null) { - // If we were already provided device info (e.g. from RCN), keep that and don't - // listen for updates, but process once to push updates to listeners - processDevice(key, oldKey, data.device) + // If we were already provided device info (e.g. from RCN), keep that and + // don't listen for updates, but process once to push updates to listeners + if (mediaControlsDeviceManagerBackgroundExecution()) { + fgExecutor.execute { processDevice(key, oldKey, data.device) } + } else { + processDevice(key, oldKey, data.device) + } return } val controller = data.token?.let { controllerFactory.create(it) } @@ -120,27 +156,18 @@ constructor( } } - override fun onMediaDataRemoved(key: String, userInitiated: Boolean) { + private fun onMediaRemoved(key: String, userInitiated: Boolean) { val token = entries.remove(key) token?.stop() - token?.let { listeners.forEach { it.onKeyRemoved(key, userInitiated) } } - } - - fun dump(pw: PrintWriter) { - with(pw) { - println("MediaDeviceManager state:") - entries.forEach { (key, entry) -> - println(" key=$key") - entry.dump(pw) + if (mediaControlsDeviceManagerBackgroundExecution()) { + fgExecutor.execute { + token?.let { listeners.forEach { it.onKeyRemoved(key, userInitiated) } } } + } else { + token?.let { listeners.forEach { it.onKeyRemoved(key, userInitiated) } } } } - @MainThread - private fun processDevice(key: String, oldKey: String?, device: MediaDeviceData?) { - listeners.forEach { it.onMediaDeviceChanged(key, oldKey, device) } - } - interface Listener { /** Called when the route has changed for a given notification. */ fun onMediaDeviceChanged(key: String, oldKey: String?, data: MediaDeviceData?) @@ -260,7 +287,7 @@ constructor( override fun onAboutToConnectDeviceAdded( deviceAddress: String, deviceName: String, - deviceIcon: Drawable? + deviceIcon: Drawable?, ) { aboutToConnectDeviceOverride = AboutToConnectDevice( @@ -270,8 +297,8 @@ constructor( /* enabled */ enabled = true, /* icon */ deviceIcon, /* name */ deviceName, - /* showBroadcastButton */ showBroadcastButton = false - ) + /* showBroadcastButton */ showBroadcastButton = false, + ), ) updateCurrent() } @@ -292,7 +319,7 @@ constructor( override fun onBroadcastMetadataChanged( broadcastId: Int, - metadata: BluetoothLeBroadcastMetadata + metadata: BluetoothLeBroadcastMetadata, ) { logger.logBroadcastMetadataChanged(broadcastId, metadata.toString()) updateCurrent() @@ -352,14 +379,14 @@ constructor( // route. connectedDevice?.copy( name = it.name ?: connectedDevice.name, - icon = icon + icon = icon, ) } ?: MediaDeviceData( enabled = false, icon = MediaControlDrawables.getHomeDevices(context), name = context.getString(R.string.media_seamless_other_device), - showBroadcastButton = false + showBroadcastButton = false, ) logger.logRemoteDevice(routingSession?.name, connectedDevice) } else { @@ -398,7 +425,7 @@ constructor( device?.iconWithoutBackground, name, id = device?.id, - showBroadcastButton = false + showBroadcastButton = false, ) } } @@ -415,7 +442,7 @@ constructor( icon = iconWithoutBackground, name = name, id = id, - showBroadcastButton = false + showBroadcastButton = false, ) private fun getLeAudioBroadcastDeviceData(): MediaDeviceData { @@ -425,7 +452,7 @@ constructor( icon = MediaControlDrawables.getLeAudioSharing(context), name = context.getString(R.string.audio_sharing_description), intent = null, - showBroadcastButton = false + showBroadcastButton = false, ) } else { MediaDeviceData( @@ -433,7 +460,7 @@ constructor( icon = MediaControlDrawables.getAntenna(context), name = broadcastDescription, intent = null, - showBroadcastButton = true + showBroadcastButton = true, ) } } @@ -449,7 +476,7 @@ constructor( device, controller, routingSession?.name, - selectedRoutes?.firstOrNull()?.name + selectedRoutes?.firstOrNull()?.name, ) if (controller == null) { @@ -514,7 +541,7 @@ constructor( MediaDataUtils.getAppLabel( context, localMediaManager.packageName, - context.getString(R.string.bt_le_audio_broadcast_dialog_unknown_name) + context.getString(R.string.bt_le_audio_broadcast_dialog_unknown_name), ) val isCurrentBroadcastedApp = TextUtils.equals(mediaApp, currentBroadcastedApp) if (isCurrentBroadcastedApp) { @@ -538,5 +565,5 @@ constructor( */ private data class AboutToConnectDevice( val fullMediaDevice: MediaDevice? = null, - val backupMediaDeviceData: MediaDeviceData? = null + val backupMediaDeviceData: MediaDeviceData? = null, ) diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListener.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListener.kt index 684a560b0502..be4e6cc59f76 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListener.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListener.kt @@ -25,12 +25,12 @@ import com.android.internal.annotations.VisibleForTesting import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.media.NotificationMediaManager.isPlayingState import com.android.systemui.media.controls.shared.model.MediaData import com.android.systemui.media.controls.shared.model.SmartspaceMediaData import com.android.systemui.media.controls.util.MediaControllerFactory import com.android.systemui.media.controls.util.MediaFlags import com.android.systemui.plugins.statusbar.StatusBarStateController -import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState import com.android.systemui.statusbar.SysuiStatusBarStateController import com.android.systemui.util.concurrency.DelayableExecutor import com.android.systemui.util.time.SystemClock diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModel.kt index a1f0cc33c065..8744c5c9a838 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModel.kt @@ -39,8 +39,8 @@ import androidx.lifecycle.MutableLiveData import com.android.systemui.Flags import com.android.systemui.classifier.Classifier.MEDIA_SEEKBAR import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.media.NotificationMediaManager import com.android.systemui.plugins.FalsingManager -import com.android.systemui.statusbar.NotificationMediaManager import com.android.systemui.util.concurrency.RepeatableExecutor import java.util.Locale import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapterBase.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapterBase.java index c58ba377fb68..ac1672db9375 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapterBase.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapterBase.java @@ -351,8 +351,9 @@ public abstract class MediaOutputAdapterBase extends RecyclerView.Adapter<Recycl @VisibleForTesting void showCustomEndSessionDialog(MediaDevice device) { MediaSessionReleaseDialog mediaSessionReleaseDialog = new MediaSessionReleaseDialog( - mContext, () -> transferOutput(device), mController.getColorButtonBackground(), - mController.getColorItemContent()); + mContext, () -> transferOutput(device), + mController.getColorSchemeLegacy().getColorButtonBackground(), + mController.getColorSchemeLegacy().getColorItemContent()); mediaSessionReleaseDialog.show(); } diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapterLegacy.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapterLegacy.java index 300a3578bb8f..6ab4a52dc919 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapterLegacy.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapterLegacy.java @@ -50,9 +50,11 @@ import androidx.recyclerview.widget.RecyclerView; import com.android.media.flags.Flags; import com.android.settingslib.media.InputMediaDevice; import com.android.settingslib.media.MediaDevice; -import com.android.settingslib.utils.ThreadUtils; +import com.android.systemui.dagger.qualifiers.Background; +import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.res.R; +import java.util.concurrent.Executor; /** * A RecyclerView adapter for the legacy UI media output dialog device list. */ @@ -61,14 +63,20 @@ public class MediaOutputAdapterLegacy extends MediaOutputAdapterBase { private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static final int UNMUTE_DEFAULT_VOLUME = 2; - private static final float DEVICE_DISABLED_ALPHA = 0.5f; - private static final float DEVICE_ACTIVE_ALPHA = 1f; + @VisibleForTesting static final float DEVICE_DISABLED_ALPHA = 0.5f; + @VisibleForTesting static final float DEVICE_ACTIVE_ALPHA = 1f; + private final Executor mMainExecutor; + private final Executor mBackgroundExecutor; View mHolderView; - private boolean mIsInitVolumeFirstTime; - public MediaOutputAdapterLegacy(MediaSwitchingController controller) { + public MediaOutputAdapterLegacy( + MediaSwitchingController controller, + @Main Executor mainExecutor, + @Background Executor backgroundExecutor + ) { super(controller); - mIsInitVolumeFirstTime = true; + mMainExecutor = mainExecutor; + mBackgroundExecutor = backgroundExecutor; } @Override @@ -181,9 +189,9 @@ public class MediaOutputAdapterLegacy extends MediaOutputAdapterBase { mEndTouchArea.setVisibility(View.GONE); mEndClickIcon.setVisibility(View.GONE); mContainerLayout.setOnClickListener(null); - mTitleText.setTextColor(mController.getColorItemContent()); - mSubTitleText.setTextColor(mController.getColorItemContent()); - mVolumeValueText.setTextColor(mController.getColorItemContent()); + mTitleText.setTextColor(mController.getColorSchemeLegacy().getColorItemContent()); + mSubTitleText.setTextColor(mController.getColorSchemeLegacy().getColorItemContent()); + mVolumeValueText.setTextColor(mController.getColorSchemeLegacy().getColorItemContent()); mIconAreaLayout.setBackground(null); updateIconAreaClickListener(null); updateSeekBarProgressColor(); @@ -193,14 +201,14 @@ public class MediaOutputAdapterLegacy extends MediaOutputAdapterBase { /** Binds a ViewHolder for a "Connect a device" item. */ void onBindPairNewDevice() { - mTitleText.setTextColor(mController.getColorItemContent()); + mTitleText.setTextColor(mController.getColorSchemeLegacy().getColorItemContent()); mCheckBox.setVisibility(View.GONE); updateTitle(mContext.getText(R.string.media_output_dialog_pairing_new)); updateItemBackground(ConnectionState.DISCONNECTED); final Drawable addDrawable = mContext.getDrawable(R.drawable.ic_add); mTitleIcon.setImageDrawable(addDrawable); - mTitleIcon.setImageTintList( - ColorStateList.valueOf(mController.getColorItemContent())); + mTitleIcon.setImageTintList(ColorStateList.valueOf( + mController.getColorSchemeLegacy().getColorItemContent())); mContainerLayout.setOnClickListener(mController::launchBluetoothPairing); } @@ -251,10 +259,9 @@ public class MediaOutputAdapterLegacy extends MediaOutputAdapterBase { updateSeekbarProgressBackground(); } } - boolean isCurrentSeekbarInvisible = mSeekBar.getVisibility() == View.GONE; mSeekBar.setVisibility(showSeekBar ? View.VISIBLE : View.GONE); if (showSeekBar) { - initSeekbar(device, isCurrentSeekbarInvisible); + initSeekbar(device); updateContainerContentA11yImportance(false /* isImportant */); mSeekBar.setContentDescription(contentDescription); } else { @@ -264,9 +271,8 @@ public class MediaOutputAdapterLegacy extends MediaOutputAdapterBase { void updateGroupSeekBar(String contentDescription) { updateSeekbarProgressBackground(); - boolean isCurrentSeekbarInvisible = mSeekBar.getVisibility() == View.GONE; mSeekBar.setVisibility(View.VISIBLE); - initGroupSeekbar(isCurrentSeekbarInvisible); + initGroupSeekbar(); updateContainerContentA11yImportance(false /* isImportant */); mSeekBar.setContentDescription(contentDescription); } @@ -297,8 +303,8 @@ public class MediaOutputAdapterLegacy extends MediaOutputAdapterBase { protected void updateLoadingIndicator(ConnectionState connectionState) { if (connectionState == ConnectionState.CONNECTING) { mProgressBar.setVisibility(View.VISIBLE); - mProgressBar.getIndeterminateDrawable().setTintList( - ColorStateList.valueOf(mController.getColorItemContent())); + mProgressBar.getIndeterminateDrawable().setTintList(ColorStateList.valueOf( + mController.getColorSchemeLegacy().getColorItemContent())); } else { mProgressBar.setVisibility(View.GONE); } @@ -318,8 +324,8 @@ public class MediaOutputAdapterLegacy extends MediaOutputAdapterBase { // Connected or connecting state has a darker background. int backgroundColor = isConnected || isConnecting - ? mController.getColorConnectedItemBackground() - : mController.getColorItemBackground(); + ? mController.getColorSchemeLegacy().getColorConnectedItemBackground() + : mController.getColorSchemeLegacy().getColorItemBackground(); mItemLayout.setBackgroundTintList(ColorStateList.valueOf(backgroundColor)); } @@ -332,13 +338,13 @@ public class MediaOutputAdapterLegacy extends MediaOutputAdapterBase { } private void updateSeekBarProgressColor() { - mSeekBar.setProgressTintList( - ColorStateList.valueOf(mController.getColorSeekbarProgress())); + mSeekBar.setProgressTintList(ColorStateList.valueOf( + mController.getColorSchemeLegacy().getColorSeekbarProgress())); final Drawable contrastDotDrawable = ((LayerDrawable) mSeekBar.getProgressDrawable()).findDrawableByLayerId( R.id.contrast_dot); - contrastDotDrawable.setTintList( - ColorStateList.valueOf(mController.getColorItemContent())); + contrastDotDrawable.setTintList(ColorStateList.valueOf( + mController.getColorSchemeLegacy().getColorItemContent())); } void updateSeekbarProgressBackground() { @@ -354,31 +360,21 @@ public class MediaOutputAdapterLegacy extends MediaOutputAdapterBase { mActiveRadius, 0, 0}); } - private void initializeSeekbarVolume( - @Nullable MediaDevice device, int currentVolume, - boolean isCurrentSeekbarInvisible) { + private void initializeSeekbarVolume(@Nullable MediaDevice device, int currentVolume) { if (!isDragging()) { if (mSeekBar.getVolume() != currentVolume && (mLatestUpdateVolume == -1 || currentVolume == mLatestUpdateVolume)) { // Update only if volume of device and value of volume bar doesn't match. // Check if response volume match with the latest request, to ignore obsolete // response - if (isCurrentSeekbarInvisible && !mIsInitVolumeFirstTime) { + if (!mVolumeAnimator.isStarted()) { if (currentVolume == 0) { updateMutedVolumeIcon(device); } else { updateUnmutedVolumeIcon(device); } - } else { - if (!mVolumeAnimator.isStarted()) { - if (currentVolume == 0) { - updateMutedVolumeIcon(device); - } else { - updateUnmutedVolumeIcon(device); - } - mSeekBar.setVolume(currentVolume); - mLatestUpdateVolume = -1; - } + mSeekBar.setVolume(currentVolume); + mLatestUpdateVolume = -1; } } else if (currentVolume == 0) { mSeekBar.resetVolume(); @@ -388,12 +384,9 @@ public class MediaOutputAdapterLegacy extends MediaOutputAdapterBase { mLatestUpdateVolume = -1; } } - if (mIsInitVolumeFirstTime) { - mIsInitVolumeFirstTime = false; - } } - void initSeekbar(@NonNull MediaDevice device, boolean isCurrentSeekbarInvisible) { + void initSeekbar(@NonNull MediaDevice device) { SeekBarVolumeControl volumeControl = new SeekBarVolumeControl() { @Override public int getVolume() { @@ -422,7 +415,7 @@ public class MediaOutputAdapterLegacy extends MediaOutputAdapterBase { } mSeekBar.setMaxVolume(device.getMaxVolume()); final int currentVolume = device.getCurrentVolume(); - initializeSeekbarVolume(device, currentVolume, isCurrentSeekbarInvisible); + initializeSeekbarVolume(device, currentVolume); mSeekBar.setOnSeekBarChangeListener(new MediaSeekBarChangedListener( device, volumeControl) { @@ -435,7 +428,7 @@ public class MediaOutputAdapterLegacy extends MediaOutputAdapterBase { } // Initializes the seekbar for a group of devices. - void initGroupSeekbar(boolean isCurrentSeekbarInvisible) { + void initGroupSeekbar() { SeekBarVolumeControl volumeControl = new SeekBarVolumeControl() { @Override public int getVolume() { @@ -462,7 +455,7 @@ public class MediaOutputAdapterLegacy extends MediaOutputAdapterBase { mSeekBar.setMaxVolume(mController.getSessionVolumeMax()); final int currentVolume = mController.getSessionVolume(); - initializeSeekbarVolume(null, currentVolume, isCurrentSeekbarInvisible); + initializeSeekbarVolume(null, currentVolume); mSeekBar.setOnSeekBarChangeListener(new MediaSeekBarChangedListener( null, volumeControl) { @Override @@ -503,9 +496,10 @@ public class MediaOutputAdapterLegacy extends MediaOutputAdapterBase { boolean isInputMediaDevice = device instanceof InputMediaDevice; int id = getDrawableId(isInputMediaDevice, isMutedVolumeIcon); mTitleIcon.setImageDrawable(mContext.getDrawable(id)); - mTitleIcon.setImageTintList(ColorStateList.valueOf(mController.getColorItemContent())); - mIconAreaLayout.setBackgroundTintList( - ColorStateList.valueOf(mController.getColorSeekbarProgress())); + mTitleIcon.setImageTintList(ColorStateList.valueOf( + mController.getColorSchemeLegacy().getColorItemContent())); + mIconAreaLayout.setBackgroundTintList(ColorStateList.valueOf( + mController.getColorSchemeLegacy().getColorSeekbarProgress())); } @VisibleForTesting @@ -534,8 +528,8 @@ public class MediaOutputAdapterLegacy extends MediaOutputAdapterBase { mStatusIcon.setVisibility(View.GONE); } else { mStatusIcon.setImageDrawable(deviceStatusIcon); - mStatusIcon.setImageTintList( - ColorStateList.valueOf(mController.getColorItemContent())); + mStatusIcon.setImageTintList(ColorStateList.valueOf( + mController.getColorSchemeLegacy().getColorItemContent())); if (deviceStatusIcon instanceof AnimatedVectorDrawable) { ((AnimatedVectorDrawable) deviceStatusIcon).start(); } @@ -585,9 +579,10 @@ public class MediaOutputAdapterLegacy extends MediaOutputAdapterBase { private void updateEndAreaWithIcon(View.OnClickListener clickListener, @DrawableRes int iconDrawableId, @StringRes int accessibilityStringId) { - updateEndAreaColor(mController.getColorSeekbarProgress()); + updateEndAreaColor(mController.getColorSchemeLegacy().getColorSeekbarProgress()); mEndClickIcon.setImageTintList( - ColorStateList.valueOf(mController.getColorItemContent())); + ColorStateList.valueOf( + mController.getColorSchemeLegacy().getColorItemContent())); mEndClickIcon.setOnClickListener(clickListener); Drawable drawable = mContext.getDrawable(iconDrawableId); mEndClickIcon.setImageDrawable(drawable); @@ -600,8 +595,9 @@ public class MediaOutputAdapterLegacy extends MediaOutputAdapterBase { private void updateEndAreaForGroupCheckBox(@NonNull MediaDevice device, @NonNull GroupStatus groupStatus) { boolean isEnabled = isGroupCheckboxEnabled(groupStatus); - updateEndAreaColor(groupStatus.selected() ? mController.getColorSeekbarProgress() - : mController.getColorItemBackground()); + updateEndAreaColor(groupStatus.selected() + ? mController.getColorSchemeLegacy().getColorSeekbarProgress() + : mController.getColorSchemeLegacy().getColorItemBackground()); mCheckBox.setContentDescription(mContext.getString( groupStatus.selected() ? R.string.accessibility_remove_device_from_group : R.string.accessibility_add_device_to_group)); @@ -611,7 +607,7 @@ public class MediaOutputAdapterLegacy extends MediaOutputAdapterBase { isEnabled ? (buttonView, isChecked) -> onGroupActionTriggered( !groupStatus.selected(), device) : null); mCheckBox.setEnabled(isEnabled); - setCheckBoxColor(mCheckBox, mController.getColorItemContent()); + setCheckBoxColor(mCheckBox, mController.getColorSchemeLegacy().getColorItemContent()); } private void setCheckBoxColor(CheckBox checkBox, int color) { @@ -714,15 +710,15 @@ public class MediaOutputAdapterLegacy extends MediaOutputAdapterBase { } protected void setUpDeviceIcon(@NonNull MediaDevice device) { - ThreadUtils.postOnBackgroundThread(() -> { + mBackgroundExecutor.execute(() -> { Icon icon = mController.getDeviceIconCompat(device).toIcon(mContext); - ThreadUtils.postOnMainThread(() -> { + mMainExecutor.execute(() -> { if (!TextUtils.equals(mDeviceId, device.getId())) { return; } mTitleIcon.setImageIcon(icon); - mTitleIcon.setImageTintList( - ColorStateList.valueOf(mController.getColorItemContent())); + mTitleIcon.setImageTintList(ColorStateList.valueOf( + mController.getColorSchemeLegacy().getColorItemContent())); }); }); } @@ -807,7 +803,7 @@ public class MediaOutputAdapterLegacy extends MediaOutputAdapterBase { } void onBind(String groupDividerTitle) { - mTitleText.setTextColor(mController.getColorItemContent()); + mTitleText.setTextColor(mController.getColorSchemeLegacy().getColorItemContent()); mTitleText.setText(groupDividerTitle); } } diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java index d791361d555f..49d09cf64c8e 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java @@ -40,7 +40,6 @@ import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.view.ViewTreeObserver; import android.view.Window; import android.view.WindowInsets; import android.view.WindowManager; @@ -93,13 +92,10 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog private ImageView mAppResourceIcon; private ImageView mBroadcastIcon; private RecyclerView mDevicesRecyclerView; - private LinearLayout mDeviceListLayout; + private ViewGroup mDeviceListLayout; private LinearLayout mMediaMetadataSectionLayout; private Button mDoneButton; private Button mStopButton; - private int mListMaxHeight; - private int mItemHeight; - private int mListPaddingTop; private WallpaperColors mWallpaperColors; private boolean mShouldLaunchLeBroadcastDialog; private boolean mIsLeBroadcastCallbackRegistered; @@ -109,17 +105,6 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog protected Executor mExecutor; - private final ViewTreeObserver.OnGlobalLayoutListener mDeviceListLayoutListener = () -> { - ViewGroup.LayoutParams params = mDeviceListLayout.getLayoutParams(); - int totalItemsHeight = mAdapter.getItemCount() * mItemHeight - + mListPaddingTop; - int correctHeight = Math.min(totalItemsHeight, mListMaxHeight); - // Set max height for list - if (correctHeight != params.height) { - params.height = correctHeight; - mDeviceListLayout.setLayoutParams(params); - } - }; private final BluetoothLeBroadcast.Callback mBroadcastCallback = new BluetoothLeBroadcast.Callback() { @@ -220,12 +205,6 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog mBroadcastSender = broadcastSender; mMediaSwitchingController = mediaSwitchingController; mLayoutManager = new LayoutManagerWrapper(mContext); - mListMaxHeight = context.getResources().getDimensionPixelSize( - R.dimen.media_output_dialog_list_max_height); - mItemHeight = context.getResources().getDimensionPixelSize( - R.dimen.media_output_dialog_list_item_height); - mListPaddingTop = mContext.getResources().getDimensionPixelSize( - R.dimen.media_output_dialog_list_padding_top); mExecutor = Executors.newSingleThreadExecutor(); mIncludePlaybackAndAppMetadata = includePlaybackAndAppMetadata; } @@ -258,8 +237,6 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog mAppResourceIcon = mDialogView.requireViewById(R.id.app_source_icon); mBroadcastIcon = mDialogView.requireViewById(R.id.broadcast_icon); - mDeviceListLayout.getViewTreeObserver().addOnGlobalLayoutListener( - mDeviceListLayoutListener); // Init device list mLayoutManager.setAutoMeasureEnabled(true); mDevicesRecyclerView.setLayoutManager(mLayoutManager); @@ -342,7 +319,8 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog WallpaperColors wallpaperColors = WallpaperColors.fromBitmap(icon.getBitmap()); colorSetUpdated = !wallpaperColors.equals(mWallpaperColors); if (colorSetUpdated) { - mMediaSwitchingController.setCurrentColorScheme(wallpaperColors, isDarkThemeOn); + mMediaSwitchingController.updateCurrentColorScheme(wallpaperColors, + isDarkThemeOn); updateButtonBackgroundColorFilter(); updateDialogBackgroundColor(); } @@ -359,7 +337,8 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog mAppResourceIcon.setVisibility(View.GONE); } else if (appSourceIcon != null) { Icon appIcon = appSourceIcon.toIcon(mContext); - mAppResourceIcon.setColorFilter(mMediaSwitchingController.getColorItemContent()); + mAppResourceIcon.setColorFilter( + mMediaSwitchingController.getColorSchemeLegacy().getColorItemContent()); mAppResourceIcon.setImageIcon(appIcon); } else { Drawable appIconDrawable = mMediaSwitchingController.getAppSourceIconFromPackage(); @@ -369,12 +348,6 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog mAppResourceIcon.setVisibility(View.GONE); } } - if (mHeaderIcon.getVisibility() == View.VISIBLE) { - final int size = getHeaderIconSize(); - final int padding = mContext.getResources().getDimensionPixelSize( - R.dimen.media_output_dialog_header_icon_padding); - mHeaderIcon.setLayoutParams(new LinearLayout.LayoutParams(size + padding, size)); - } if (!mIncludePlaybackAndAppMetadata) { mHeaderTitle.setVisibility(View.GONE); @@ -419,18 +392,19 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog private void updateButtonBackgroundColorFilter() { ColorFilter buttonColorFilter = new PorterDuffColorFilter( - mMediaSwitchingController.getColorButtonBackground(), + mMediaSwitchingController.getColorSchemeLegacy().getColorButtonBackground(), PorterDuff.Mode.SRC_IN); mDoneButton.getBackground().setColorFilter(buttonColorFilter); mStopButton.getBackground().setColorFilter(buttonColorFilter); - mDoneButton.setTextColor(mMediaSwitchingController.getColorPositiveButtonText()); + mDoneButton.setTextColor( + mMediaSwitchingController.getColorSchemeLegacy().getColorPositiveButtonText()); } private void updateDialogBackgroundColor() { - getDialogView() - .getBackground() - .setTint(mMediaSwitchingController.getColorDialogBackground()); - mDeviceListLayout.setBackgroundColor(mMediaSwitchingController.getColorDialogBackground()); + getDialogView().getBackground().setTint( + mMediaSwitchingController.getColorSchemeLegacy().getColorDialogBackground()); + mDeviceListLayout.setBackgroundColor( + mMediaSwitchingController.getColorSchemeLegacy().getColorDialogBackground()); } public void handleLeBroadcastStarted() { @@ -520,8 +494,6 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog abstract IconCompat getHeaderIcon(); - abstract int getHeaderIconSize(); - abstract CharSequence getHeaderText(); abstract CharSequence getHeaderSubtitle(); diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java index 9ade9e275ca1..791a61cc73ec 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java @@ -52,6 +52,8 @@ import com.android.systemui.statusbar.phone.SystemUIDialog; import com.google.zxing.WriterException; +import java.util.concurrent.Executor; + /** * Dialog for media output broadcast. */ @@ -239,13 +241,16 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog { Context context, boolean aboveStatusbar, BroadcastSender broadcastSender, - MediaSwitchingController mediaSwitchingController) { + MediaSwitchingController mediaSwitchingController, + Executor mainExecutor, + Executor backgroundExecutor) { super( context, broadcastSender, mediaSwitchingController, /* includePlaybackAndAppMetadata */ true); - mAdapter = new MediaOutputAdapterLegacy(mMediaSwitchingController); + mAdapter = new MediaOutputAdapterLegacy(mMediaSwitchingController, mainExecutor, + backgroundExecutor); // TODO(b/226710953): Move the part to MediaOutputBaseDialog for every class // that extends MediaOutputBaseDialog if (!aboveStatusbar) { @@ -295,12 +300,6 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog { } @Override - int getHeaderIconSize() { - return mContext.getResources().getDimensionPixelSize( - R.dimen.media_output_dialog_header_album_icon_size); - } - - @Override CharSequence getHeaderText() { return mMediaSwitchingController.getHeaderTitle(); } diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogManager.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogManager.kt index 2e7e66f5b384..81c85a6ad22d 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogManager.kt @@ -20,6 +20,9 @@ import android.content.Context import android.view.View import com.android.systemui.animation.DialogTransitionAnimator import com.android.systemui.broadcast.BroadcastSender +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.dagger.qualifiers.Main +import java.util.concurrent.Executor import javax.inject.Inject /** Manager to create and show a [MediaOutputBroadcastDialog]. */ @@ -29,7 +32,9 @@ constructor( private val context: Context, private val broadcastSender: BroadcastSender, private val dialogTransitionAnimator: DialogTransitionAnimator, - private val mediaSwitchingControllerFactory: MediaSwitchingController.Factory + private val mediaSwitchingControllerFactory: MediaSwitchingController.Factory, + @Main private val mainExecutor: Executor, + @Background private val backgroundExecutor: Executor, ) { var mediaOutputBroadcastDialog: MediaOutputBroadcastDialog? = null @@ -47,7 +52,14 @@ constructor( /* token */ null, ) val dialog = - MediaOutputBroadcastDialog(context, aboveStatusBar, broadcastSender, controller) + MediaOutputBroadcastDialog( + context, + aboveStatusBar, + broadcastSender, + controller, + mainExecutor, + backgroundExecutor, + ) mediaOutputBroadcastDialog = dialog // Show the dialog. diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputColorSchemeLegacy.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputColorSchemeLegacy.kt new file mode 100644 index 000000000000..7f0fa463811b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputColorSchemeLegacy.kt @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.media.dialog + +import android.content.Context +import com.android.settingslib.Utils +import com.android.systemui.monet.ColorScheme +import com.android.systemui.res.R + +abstract class MediaOutputColorSchemeLegacy { + companion object Factory { + @JvmStatic + fun fromSystemColors(context: Context): MediaOutputColorSchemeLegacy { + return MediaOutputColorSchemeLegacySystem(context) + } + + @JvmStatic + fun fromDynamicColors( + colorScheme: ColorScheme, + isDarkTheme: Boolean, + ): MediaOutputColorSchemeLegacy { + return MediaOutputColorSchemeLegacyDynamic(colorScheme, isDarkTheme) + } + } + + abstract fun getColorConnectedItemBackground(): Int + + abstract fun getColorPositiveButtonText(): Int + + abstract fun getColorDialogBackground(): Int + + abstract fun getColorItemContent(): Int + + abstract fun getColorSeekbarProgress(): Int + + abstract fun getColorButtonBackground(): Int + + abstract fun getColorItemBackground(): Int +} + +class MediaOutputColorSchemeLegacySystem(private val mContext: Context) : + MediaOutputColorSchemeLegacy() { + + override fun getColorConnectedItemBackground() = + Utils.getColorStateListDefaultColor( + mContext, + R.color.media_dialog_connected_item_background, + ) + + override fun getColorPositiveButtonText() = + Utils.getColorStateListDefaultColor(mContext, R.color.media_dialog_solid_button_text) + + override fun getColorDialogBackground() = + Utils.getColorStateListDefaultColor(mContext, R.color.media_dialog_background) + + override fun getColorItemContent() = + Utils.getColorStateListDefaultColor(mContext, R.color.media_dialog_item_main_content) + + override fun getColorSeekbarProgress() = + Utils.getColorStateListDefaultColor(mContext, R.color.media_dialog_seekbar_progress) + + override fun getColorButtonBackground() = + Utils.getColorStateListDefaultColor(mContext, R.color.media_dialog_button_background) + + override fun getColorItemBackground() = + Utils.getColorStateListDefaultColor(mContext, R.color.media_dialog_item_background) +} + +class MediaOutputColorSchemeLegacyDynamic(colorScheme: ColorScheme, isDarkTheme: Boolean) : + MediaOutputColorSchemeLegacy() { + private var mColorItemContent: Int + private var mColorSeekbarProgress: Int + private var mColorButtonBackground: Int + private var mColorItemBackground: Int + private var mColorConnectedItemBackground: Int + private var mColorPositiveButtonText: Int + private var mColorDialogBackground: Int + + init { + if (isDarkTheme) { + mColorItemContent = colorScheme.accent1.s100 // A1-100 + mColorSeekbarProgress = colorScheme.accent2.s600 // A2-600 + mColorButtonBackground = colorScheme.accent1.s300 // A1-300 + mColorItemBackground = colorScheme.neutral2.s800 // N2-800 + mColorConnectedItemBackground = colorScheme.accent2.s800 // A2-800 + mColorPositiveButtonText = colorScheme.accent2.s800 // A2-800 + mColorDialogBackground = colorScheme.neutral1.s900 // N1-900 + } else { + mColorItemContent = colorScheme.accent1.s800 // A1-800 + mColorSeekbarProgress = colorScheme.accent1.s300 // A1-300 + mColorButtonBackground = colorScheme.accent1.s600 // A1-600 + mColorItemBackground = colorScheme.accent2.s50 // A2-50 + mColorConnectedItemBackground = colorScheme.accent1.s100 // A1-100 + mColorPositiveButtonText = colorScheme.neutral1.s50 // N1-50 + mColorDialogBackground = colorScheme.backgroundColor + } + } + + override fun getColorConnectedItemBackground() = mColorConnectedItemBackground + + override fun getColorPositiveButtonText() = mColorPositiveButtonText + + override fun getColorDialogBackground() = mColorDialogBackground + + override fun getColorItemContent() = mColorItemContent + + override fun getColorSeekbarProgress() = mColorSeekbarProgress + + override fun getColorButtonBackground() = mColorButtonBackground + + override fun getColorItemBackground() = mColorItemBackground +} diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java index 2e602be4556e..163ff248b9df 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java @@ -34,6 +34,8 @@ import com.android.systemui.broadcast.BroadcastSender; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.res.R; +import java.util.concurrent.Executor; + /** * Dialog for media output transferring. */ @@ -49,11 +51,14 @@ public class MediaOutputDialog extends MediaOutputBaseDialog { MediaSwitchingController mediaSwitchingController, DialogTransitionAnimator dialogTransitionAnimator, UiEventLogger uiEventLogger, + Executor mainExecutor, + Executor backgroundExecutor, boolean includePlaybackAndAppMetadata) { super(context, broadcastSender, mediaSwitchingController, includePlaybackAndAppMetadata); mDialogTransitionAnimator = dialogTransitionAnimator; mUiEventLogger = uiEventLogger; - mAdapter = new MediaOutputAdapterLegacy(mMediaSwitchingController); + mAdapter = new MediaOutputAdapterLegacy(mMediaSwitchingController, mainExecutor, + backgroundExecutor); if (!aboveStatusbar) { getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY); } @@ -76,12 +81,6 @@ public class MediaOutputDialog extends MediaOutputBaseDialog { } @Override - int getHeaderIconSize() { - return mContext.getResources().getDimensionPixelSize( - R.dimen.media_output_dialog_header_album_icon_size); - } - - @Override CharSequence getHeaderText() { return mMediaSwitchingController.getHeaderTitle(); } diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt index 4e9451a838ad..d3a81a53b6d3 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt @@ -25,6 +25,9 @@ import com.android.internal.logging.UiEventLogger import com.android.systemui.animation.DialogCuj import com.android.systemui.animation.DialogTransitionAnimator import com.android.systemui.broadcast.BroadcastSender +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.dagger.qualifiers.Main +import java.util.concurrent.Executor import javax.inject.Inject /** Manager to create and show a [MediaOutputDialog]. */ @@ -37,6 +40,9 @@ constructor( private val dialogTransitionAnimator: DialogTransitionAnimator, private val mediaSwitchingControllerFactory: MediaSwitchingController.Factory, ) { + @Inject @Main lateinit var mainExecutor: Executor + @Inject @Background lateinit var backgroundExecutor: Executor + companion object { const val INTERACTION_JANK_TAG = "media_output" var mediaOutputDialog: MediaOutputDialog? = null @@ -51,7 +57,7 @@ constructor( aboveStatusBar: Boolean, view: View? = null, userHandle: UserHandle? = null, - token: MediaSession.Token? = null + token: MediaSession.Token? = null, ) { createAndShowWithController( packageName, @@ -62,8 +68,8 @@ constructor( it, DialogCuj( InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, - INTERACTION_JANK_TAG - ) + INTERACTION_JANK_TAG, + ), ) }, userHandle = userHandle, @@ -128,15 +134,14 @@ constructor( controller, dialogTransitionAnimator, uiEventLogger, - includePlaybackAndAppMetadata + mainExecutor, + backgroundExecutor, + includePlaybackAndAppMetadata, ) // Show the dialog. if (dialogTransitionAnimatorController != null) { - dialogTransitionAnimator.show( - mediaOutputDialog, - dialogTransitionAnimatorController, - ) + dialogTransitionAnimator.show(mediaOutputDialog, dialogTransitionAnimatorController) } else { mediaOutputDialog.show() } diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java index 9d375809786a..f79693138e24 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java @@ -83,6 +83,8 @@ import com.android.settingslib.utils.ThreadUtils; import com.android.systemui.animation.ActivityTransitionAnimator; import com.android.systemui.animation.DialogTransitionAnimator; import com.android.systemui.broadcast.BroadcastSender; +import com.android.systemui.dagger.qualifiers.Background; +import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.media.dialog.MediaItem.MediaItemType; import com.android.systemui.media.nearby.NearbyMediaDevicesManager; @@ -114,6 +116,8 @@ import java.util.concurrent.Executor; import java.util.function.Function; import java.util.stream.Collectors; +import javax.inject.Inject; + /** * Controller for a dialog that allows users to switch media output and input devices, control * volume, connect to new devices, etc. @@ -141,7 +145,7 @@ public class MediaSwitchingController @VisibleForTesting final List<MediaDevice> mGroupMediaDevices = new CopyOnWriteArrayList<>(); final List<MediaDevice> mCachedMediaDevices = new CopyOnWriteArrayList<>(); - private final List<MediaItem> mOutputMediaItemList = new CopyOnWriteArrayList<>(); + private final OutputMediaItemListProxy mOutputMediaItemListProxy; private final List<MediaItem> mInputMediaItemList = new CopyOnWriteArrayList<>(); private final AudioManager mAudioManager; private final PowerExemptionManager mPowerExemptionManager; @@ -149,7 +153,8 @@ public class MediaSwitchingController private final NearbyMediaDevicesManager mNearbyMediaDevicesManager; private final Map<String, Integer> mNearbyDeviceInfoMap = new ConcurrentHashMap<>(); private final MediaSession.Token mToken; - + @Inject @Main Executor mMainExecutor; + @Inject @Background Executor mBackgroundExecutor; @VisibleForTesting boolean mIsRefreshing = false; @VisibleForTesting @@ -163,17 +168,10 @@ public class MediaSwitchingController @VisibleForTesting MediaOutputMetricLogger mMetricLogger; private int mCurrentState; - - private int mColorItemContent; - private int mColorSeekbarProgress; - private int mColorButtonBackground; - private int mColorItemBackground; - private int mColorConnectedItemBackground; - private int mColorPositiveButtonText; - private int mColorDialogBackground; private FeatureFlags mFeatureFlags; private UserTracker mUserTracker; private VolumePanelGlobalStateInteractor mVolumePanelGlobalStateInteractor; + @NonNull private MediaOutputColorSchemeLegacy mMediaOutputColorSchemeLegacy; public enum BroadcastNotifyDialog { ACTION_FIRST_LAUNCH, @@ -228,22 +226,10 @@ public class MediaSwitchingController InfoMediaManager.createInstance(mContext, packageName, userHandle, lbm, token); mLocalMediaManager = new LocalMediaManager(mContext, lbm, imm, packageName); mMetricLogger = new MediaOutputMetricLogger(mContext, mPackageName); + mOutputMediaItemListProxy = new OutputMediaItemListProxy(); mDialogTransitionAnimator = dialogTransitionAnimator; mNearbyMediaDevicesManager = nearbyMediaDevicesManager; - mColorItemContent = Utils.getColorStateListDefaultColor(mContext, - R.color.media_dialog_item_main_content); - mColorSeekbarProgress = Utils.getColorStateListDefaultColor(mContext, - R.color.media_dialog_seekbar_progress); - mColorButtonBackground = Utils.getColorStateListDefaultColor(mContext, - R.color.media_dialog_button_background); - mColorItemBackground = Utils.getColorStateListDefaultColor(mContext, - R.color.media_dialog_item_background); - mColorConnectedItemBackground = Utils.getColorStateListDefaultColor(mContext, - R.color.media_dialog_connected_item_background); - mColorPositiveButtonText = Utils.getColorStateListDefaultColor(mContext, - R.color.media_dialog_solid_button_text); - mColorDialogBackground = Utils.getColorStateListDefaultColor(mContext, - R.color.media_dialog_background); + mMediaOutputColorSchemeLegacy = MediaOutputColorSchemeLegacy.fromSystemColors(mContext); if (enableInputRouting()) { mInputRouteManager = new InputRouteManager(mContext, audioManager); @@ -260,7 +246,7 @@ public class MediaSwitchingController protected void start(@NonNull Callback cb) { synchronized (mMediaDevicesLock) { mCachedMediaDevices.clear(); - mOutputMediaItemList.clear(); + mOutputMediaItemListProxy.clear(); } mNearbyDeviceInfoMap.clear(); if (mNearbyMediaDevicesManager != null) { @@ -306,7 +292,7 @@ public class MediaSwitchingController mLocalMediaManager.stopScan(); synchronized (mMediaDevicesLock) { mCachedMediaDevices.clear(); - mOutputMediaItemList.clear(); + mOutputMediaItemListProxy.clear(); } if (mNearbyMediaDevicesManager != null) { mNearbyMediaDevicesManager.unregisterNearbyDevicesCallback(this); @@ -348,7 +334,7 @@ public class MediaSwitchingController @Override public void onDeviceListUpdate(List<MediaDevice> devices) { - boolean isListEmpty = mOutputMediaItemList.isEmpty(); + boolean isListEmpty = mOutputMediaItemListProxy.isEmpty(); if (isListEmpty || !mIsRefreshing) { buildMediaItems(devices); mCallback.onDeviceListChanged(); @@ -362,11 +348,12 @@ public class MediaSwitchingController } @Override - public void onSelectedDeviceStateChanged(MediaDevice device, - @LocalMediaManager.MediaDeviceState int state) { + public void onSelectedDeviceStateChanged( + MediaDevice device, @LocalMediaManager.MediaDeviceState int state) { mCallback.onRouteChanged(); mMetricLogger.logOutputItemSuccess( - device.toString(), new ArrayList<>(mOutputMediaItemList)); + device.toString(), + new ArrayList<>(mOutputMediaItemListProxy.getOutputMediaItemList())); } @Override @@ -377,7 +364,8 @@ public class MediaSwitchingController @Override public void onRequestFailed(int reason) { mCallback.onRouteChanged(); - mMetricLogger.logOutputItemFailure(new ArrayList<>(mOutputMediaItemList), reason); + mMetricLogger.logOutputItemFailure( + new ArrayList<>(mOutputMediaItemListProxy.getOutputMediaItemList()), reason); } /** @@ -396,7 +384,7 @@ public class MediaSwitchingController } try { synchronized (mMediaDevicesLock) { - mOutputMediaItemList.removeIf((MediaItem::isMutingExpectedDevice)); + mOutputMediaItemListProxy.removeMutingExpectedDevices(); } mAudioManager.cancelMuteAwaitConnection(mAudioManager.getMutingExpectedDevice()); } catch (Exception e) { @@ -568,26 +556,15 @@ public class MediaSwitchingController return null; } - void setCurrentColorScheme(WallpaperColors wallpaperColors, boolean isDarkTheme) { - ColorScheme mCurrentColorScheme = new ColorScheme(wallpaperColors, + void updateCurrentColorScheme(WallpaperColors wallpaperColors, boolean isDarkTheme) { + ColorScheme currentColorScheme = new ColorScheme(wallpaperColors, isDarkTheme); - if (isDarkTheme) { - mColorItemContent = mCurrentColorScheme.getAccent1().getS100(); // A1-100 - mColorSeekbarProgress = mCurrentColorScheme.getAccent2().getS600(); // A2-600 - mColorButtonBackground = mCurrentColorScheme.getAccent1().getS300(); // A1-300 - mColorItemBackground = mCurrentColorScheme.getNeutral2().getS800(); // N2-800 - mColorConnectedItemBackground = mCurrentColorScheme.getAccent2().getS800(); // A2-800 - mColorPositiveButtonText = mCurrentColorScheme.getAccent2().getS800(); // A2-800 - mColorDialogBackground = mCurrentColorScheme.getNeutral1().getS900(); // N1-900 - } else { - mColorItemContent = mCurrentColorScheme.getAccent1().getS800(); // A1-800 - mColorSeekbarProgress = mCurrentColorScheme.getAccent1().getS300(); // A1-300 - mColorButtonBackground = mCurrentColorScheme.getAccent1().getS600(); // A1-600 - mColorItemBackground = mCurrentColorScheme.getAccent2().getS50(); // A2-50 - mColorConnectedItemBackground = mCurrentColorScheme.getAccent1().getS100(); // A1-100 - mColorPositiveButtonText = mCurrentColorScheme.getNeutral1().getS50(); // N1-50 - mColorDialogBackground = mCurrentColorScheme.getBackgroundColor(); - } + mMediaOutputColorSchemeLegacy = MediaOutputColorSchemeLegacy.fromDynamicColors( + currentColorScheme, isDarkTheme); + } + + MediaOutputColorSchemeLegacy getColorSchemeLegacy() { + return mMediaOutputColorSchemeLegacy; } public void refreshDataSetIfNeeded() { @@ -598,44 +575,16 @@ public class MediaSwitchingController } } - public int getColorConnectedItemBackground() { - return mColorConnectedItemBackground; - } - - public int getColorPositiveButtonText() { - return mColorPositiveButtonText; - } - - public int getColorDialogBackground() { - return mColorDialogBackground; - } - - public int getColorItemContent() { - return mColorItemContent; - } - - public int getColorSeekbarProgress() { - return mColorSeekbarProgress; - } - - public int getColorButtonBackground() { - return mColorButtonBackground; - } - - public int getColorItemBackground() { - return mColorItemBackground; - } - private void buildMediaItems(List<MediaDevice> devices) { synchronized (mMediaDevicesLock) { - List<MediaItem> updatedMediaItems = buildMediaItems(mOutputMediaItemList, devices); - mOutputMediaItemList.clear(); - mOutputMediaItemList.addAll(updatedMediaItems); + List<MediaItem> updatedMediaItems = + buildMediaItems(mOutputMediaItemListProxy.getOutputMediaItemList(), devices); + mOutputMediaItemListProxy.clearAndAddAll(updatedMediaItems); } } - protected List<MediaItem> buildMediaItems(List<MediaItem> oldMediaItems, - List<MediaDevice> devices) { + protected List<MediaItem> buildMediaItems( + List<MediaItem> oldMediaItems, List<MediaDevice> devices) { synchronized (mMediaDevicesLock) { if (!mLocalMediaManager.isPreferenceRouteListingExist()) { attachRangeInfo(devices); @@ -698,6 +647,27 @@ public class MediaSwitchingController List<MediaItem> finalMediaItems = targetMediaDevices.stream() .map(MediaItem::createDeviceMediaItem) .collect(Collectors.toList()); + + boolean shouldAddFirstSeenSelectedDevice = + com.android.media.flags.Flags.enableOutputSwitcherDeviceGrouping(); + + if (shouldAddFirstSeenSelectedDevice) { + finalMediaItems.clear(); + Set<String> selectedDevicesIds = getSelectedMediaDevice().stream() + .map(MediaDevice::getId) + .collect(Collectors.toSet()); + for (MediaDevice targetMediaDevice : targetMediaDevices) { + if (shouldAddFirstSeenSelectedDevice + && selectedDevicesIds.contains(targetMediaDevice.getId())) { + finalMediaItems.add(MediaItem.createDeviceMediaItem( + targetMediaDevice, /* isFirstDeviceInGroup */ true)); + shouldAddFirstSeenSelectedDevice = false; + } else { + finalMediaItems.add(MediaItem.createDeviceMediaItem( + targetMediaDevice, /* isFirstDeviceInGroup */ false)); + } + } + } dividerItems.forEach(finalMediaItems::add); attachConnectNewDeviceItemIfNeeded(finalMediaItems); return finalMediaItems; @@ -722,7 +692,8 @@ public class MediaSwitchingController * list. */ @GuardedBy("mMediaDevicesLock") - private List<MediaItem> categorizeMediaItemsLocked(MediaDevice connectedMediaDevice, + private List<MediaItem> categorizeMediaItemsLocked( + MediaDevice connectedMediaDevice, List<MediaDevice> devices, boolean needToHandleMutingExpectedDevice) { List<MediaItem> finalMediaItems = new ArrayList<>(); @@ -781,6 +752,14 @@ public class MediaSwitchingController } private void attachConnectNewDeviceItemIfNeeded(List<MediaItem> mediaItems) { + MediaItem connectNewDeviceItem = getConnectNewDeviceItem(); + if (connectNewDeviceItem != null) { + mediaItems.add(connectNewDeviceItem); + } + } + + @Nullable + private MediaItem getConnectNewDeviceItem() { boolean isSelectedDeviceNotAGroup = getSelectedMediaDevice().size() == 1; if (enableInputRouting()) { // When input routing is enabled, there are expected to be at least 2 total selected @@ -789,9 +768,9 @@ public class MediaSwitchingController } // Attach "Connect a device" item only when current output is not remote and not a group - if (!isCurrentConnectedDeviceRemote() && isSelectedDeviceNotAGroup) { - mediaItems.add(MediaItem.createPairNewDeviceMediaItem()); - } + return (!isCurrentConnectedDeviceRemote() && isSelectedDeviceNotAGroup) + ? MediaItem.createPairNewDeviceMediaItem() + : null; } private void attachRangeInfo(List<MediaDevice> devices) { @@ -880,13 +859,13 @@ public class MediaSwitchingController mediaItems.add( MediaItem.createGroupDividerMediaItem( mContext.getString(R.string.media_output_group_title))); - mediaItems.addAll(mOutputMediaItemList); + mediaItems.addAll(mOutputMediaItemListProxy.getOutputMediaItemList()); } public List<MediaItem> getMediaItemList() { // If input routing is not enabled, only return output media items. if (!enableInputRouting()) { - return mOutputMediaItemList; + return mOutputMediaItemListProxy.getOutputMediaItemList(); } // If input routing is enabled, return both output and input media items. @@ -992,7 +971,7 @@ public class MediaSwitchingController public boolean isAnyDeviceTransferring() { synchronized (mMediaDevicesLock) { - for (MediaItem mediaItem : mOutputMediaItemList) { + for (MediaItem mediaItem : mOutputMediaItemListProxy.getOutputMediaItemList()) { if (mediaItem.getMediaDevice().isPresent() && mediaItem.getMediaDevice().get().getState() == LocalMediaManager.MediaDeviceState.STATE_CONNECTING) { @@ -1032,8 +1011,11 @@ public class MediaSwitchingController startActivity(launchIntent, controller); } - void launchLeBroadcastNotifyDialog(View mediaOutputDialog, BroadcastSender broadcastSender, - BroadcastNotifyDialog action, final DialogInterface.OnClickListener listener) { + void launchLeBroadcastNotifyDialog( + View mediaOutputDialog, + BroadcastSender broadcastSender, + BroadcastNotifyDialog action, + final DialogInterface.OnClickListener listener) { final AlertDialog.Builder builder = new AlertDialog.Builder(mContext); switch (action) { case ACTION_FIRST_LAUNCH: @@ -1076,7 +1058,7 @@ public class MediaSwitchingController mVolumePanelGlobalStateInteractor, mUserTracker); MediaOutputBroadcastDialog dialog = new MediaOutputBroadcastDialog(mContext, true, - broadcastSender, controller); + broadcastSender, controller, mMainExecutor, mBackgroundExecutor); dialog.show(); } @@ -1263,8 +1245,8 @@ public class MediaSwitchingController return !sourceList.isEmpty(); } - boolean addSourceIntoSinkDeviceWithBluetoothLeAssistant(BluetoothDevice sink, - BluetoothLeBroadcastMetadata metadata, boolean isGroupOp) { + boolean addSourceIntoSinkDeviceWithBluetoothLeAssistant( + BluetoothDevice sink, BluetoothLeBroadcastMetadata metadata, boolean isGroupOp) { LocalBluetoothLeBroadcastAssistant assistant = mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastAssistantProfile(); if (assistant == null) { diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/OutputMediaItemListProxy.java b/packages/SystemUI/src/com/android/systemui/media/dialog/OutputMediaItemListProxy.java new file mode 100644 index 000000000000..1c9c0b102cb7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/OutputMediaItemListProxy.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.media.dialog; + +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +/** A proxy of holding the list of Output Switcher's output media items. */ +public class OutputMediaItemListProxy { + private final List<MediaItem> mOutputMediaItemList; + + public OutputMediaItemListProxy() { + mOutputMediaItemList = new CopyOnWriteArrayList<>(); + } + + /** Returns the list of output media items. */ + public List<MediaItem> getOutputMediaItemList() { + return mOutputMediaItemList; + } + + /** Updates the list of output media items with the given list. */ + public void clearAndAddAll(List<MediaItem> updatedMediaItems) { + mOutputMediaItemList.clear(); + mOutputMediaItemList.addAll(updatedMediaItems); + } + + /** Removes the media items with muting expected devices. */ + public void removeMutingExpectedDevices() { + mOutputMediaItemList.removeIf((MediaItem::isMutingExpectedDevice)); + } + + /** Clears the output media item list. */ + public void clear() { + mOutputMediaItemList.clear(); + } + + /** Returns whether the output media item list is empty. */ + public boolean isEmpty() { + return mOutputMediaItemList.isEmpty(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/media/remedia/domain/interactor/MediaInteractor.kt b/packages/SystemUI/src/com/android/systemui/media/remedia/domain/interactor/MediaInteractor.kt new file mode 100644 index 000000000000..afed141644ff --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/remedia/domain/interactor/MediaInteractor.kt @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.media.remedia.domain.interactor + +import com.android.systemui.media.remedia.domain.model.MediaSessionModel + +/** + * Defines interface for classes that can provide business logic in the domain of the media controls + * element. + */ +interface MediaInteractor { + + /** The list of sessions. Needs to be backed by a compose snapshot state. */ + val sessions: List<MediaSessionModel> + + /** Seek to [to], in milliseconds on the media session with the given [sessionKey]. */ + fun seek(sessionKey: Any, to: Long) + + /** Hide the representation of the media session with the given [sessionKey]. */ + fun hide(sessionKey: Any) + + /** Open media settings. */ + fun openMediaSettings() +} diff --git a/packages/SystemUI/src/com/android/systemui/media/remedia/domain/model/MediaActionModel.kt b/packages/SystemUI/src/com/android/systemui/media/remedia/domain/model/MediaActionModel.kt new file mode 100644 index 000000000000..02e4d7a966c2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/remedia/domain/model/MediaActionModel.kt @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.media.remedia.domain.model + +import com.android.systemui.common.shared.model.Icon + +sealed interface MediaActionModel { + data class Action(val icon: Icon, val onClick: (() -> Unit)?) : MediaActionModel + + data object ReserveSpace : MediaActionModel + + data object None : MediaActionModel +} diff --git a/packages/SystemUI/src/com/android/systemui/media/remedia/domain/model/MediaOutputDeviceModel.kt b/packages/SystemUI/src/com/android/systemui/media/remedia/domain/model/MediaOutputDeviceModel.kt new file mode 100644 index 000000000000..d581ae3e4e40 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/remedia/domain/model/MediaOutputDeviceModel.kt @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.media.remedia.domain.model + +import com.android.systemui.common.shared.model.Icon + +data class MediaOutputDeviceModel(val name: String, val icon: Icon, val isInProgress: Boolean) diff --git a/packages/SystemUI/src/com/android/systemui/media/remedia/domain/model/MediaSessionModel.kt b/packages/SystemUI/src/com/android/systemui/media/remedia/domain/model/MediaSessionModel.kt new file mode 100644 index 000000000000..e64ce73226f2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/remedia/domain/model/MediaSessionModel.kt @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.media.remedia.domain.model + +import androidx.compose.runtime.Stable +import androidx.compose.ui.graphics.ImageBitmap +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.media.remedia.shared.model.MediaCardActionButtonLayout +import com.android.systemui.media.remedia.shared.model.MediaColorScheme +import com.android.systemui.media.remedia.shared.model.MediaSessionState + +/** Data model representing a media session. */ +@Stable +interface MediaSessionModel { + /** Unique identifier. */ + val key: Any + + val appName: String + + val appIcon: Icon + + val background: ImageBitmap? + + val colorScheme: MediaColorScheme + + val title: String + + val subtitle: String + + val onClick: () -> Unit + + /** + * Whether the session is currently active. Under some UIs, only currently active session should + * be shown. + */ + val isActive: Boolean + + /** Whether the session can be hidden/dismissed by the user. */ + val canBeHidden: Boolean + + /** + * Whether the session currently supports scrubbing (e.g. moving to a different position iin the + * playback. + */ + val canBeScrubbed: Boolean + + val state: MediaSessionState + + /** The position of the playback within the current track. */ + val positionMs: Long + + /** The total duration of the current track. */ + val durationMs: Long + + val outputDevice: MediaOutputDeviceModel + + /** How to lay out the action buttons. */ + val actionButtonLayout: MediaCardActionButtonLayout + val playPauseAction: MediaActionModel + val leftAction: MediaActionModel + val rightAction: MediaActionModel + val additionalActions: List<MediaActionModel.Action> +} diff --git a/packages/SystemUI/src/com/android/systemui/media/remedia/shared/model/MediaActionState.kt b/packages/SystemUI/src/com/android/systemui/media/remedia/shared/model/MediaActionState.kt new file mode 100644 index 000000000000..c3ce5035accc --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/remedia/shared/model/MediaActionState.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.media.remedia.shared.model + +enum class MediaActionState { + Enabled, + Disabled, + Hidden, +} diff --git a/packages/SystemUI/src/com/android/systemui/media/remedia/shared/model/MediaCardActionButtonLayout.kt b/packages/SystemUI/src/com/android/systemui/media/remedia/shared/model/MediaCardActionButtonLayout.kt new file mode 100644 index 000000000000..554fb1f2fc43 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/remedia/shared/model/MediaCardActionButtonLayout.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.media.remedia.shared.model + +enum class MediaCardActionButtonLayout { + /** Shows the play/pause button and left/right buttons in privileged positions on the card */ + WithPlayPause, + /** Shows all action buttons along the bottom row. */ + SecondaryActionsOnly, +} diff --git a/packages/SystemUI/src/com/android/systemui/media/remedia/shared/model/MediaColorScheme.kt b/packages/SystemUI/src/com/android/systemui/media/remedia/shared/model/MediaColorScheme.kt new file mode 100644 index 000000000000..8dba170f0928 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/remedia/shared/model/MediaColorScheme.kt @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.media.remedia.shared.model + +import androidx.compose.ui.graphics.Color + +data class MediaColorScheme(val primary: Color, val onPrimary: Color) diff --git a/packages/SystemUI/src/com/android/systemui/media/remedia/ui/compose/DismissibleHorizontalPager.kt b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/compose/DismissibleHorizontalPager.kt new file mode 100644 index 000000000000..fea5b3267562 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/compose/DismissibleHorizontalPager.kt @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.media.remedia.ui.compose + +import androidx.compose.animation.core.Animatable +import androidx.compose.animation.core.AnimationVector1D +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxScope +import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.foundation.pager.PagerScope +import androidx.compose.foundation.pager.PagerState +import androidx.compose.foundation.pager.rememberPagerState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.input.nestedscroll.NestedScrollConnection +import androidx.compose.ui.input.nestedscroll.NestedScrollSource +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.Velocity +import androidx.compose.ui.unit.dp +import com.android.compose.modifiers.thenIf +import kotlinx.coroutines.launch + +/** State for a [DismissibleHorizontalPager] */ +class DismissibleHorizontalPagerState( + val isDismissible: Boolean, + val isScrollingEnabled: Boolean, + val pagerState: PagerState, + val offset: Animatable<Float, AnimationVector1D>, +) + +/** + * Returns a remembered [DismissibleHorizontalPagerState] that starts at [initialPage] and has + * [pageCount] total pages. + */ +@Composable +fun rememberDismissibleHorizontalPagerState( + isDismissible: Boolean = true, + isScrollingEnabled: Boolean = true, + initialPage: Int = 0, + pageCount: () -> Int, +): DismissibleHorizontalPagerState { + val pagerState = rememberPagerState(initialPage = initialPage, pageCount = pageCount) + val offset = remember { Animatable(0f) } + + return remember(isDismissible, isScrollingEnabled, pagerState, offset) { + DismissibleHorizontalPagerState( + isDismissible = isDismissible, + isScrollingEnabled = isScrollingEnabled, + pagerState = pagerState, + offset = offset, + ) + } +} + +/** + * A [HorizontalPager] that can be swiped-away to dismiss by the user when swiped farther left or + * right once fully scrolled to the left-most or right-most page, respectively. + */ +@Composable +fun DismissibleHorizontalPager( + state: DismissibleHorizontalPagerState, + onDismissed: () -> Unit, + modifier: Modifier = Modifier, + key: ((Int) -> Any)? = null, + pageSpacing: Dp = 0.dp, + indicator: @Composable BoxScope.() -> Unit, + pageContent: @Composable PagerScope.(page: Int) -> Unit, +) { + val scope = rememberCoroutineScope() + + val nestedScrollConnection = remember { + object : NestedScrollConnection { + override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { + return if (state.offset.value > 0f && available.x < 0f) { + scope.launch { state.offset.snapTo(state.offset.value + available.x) } + Offset(available.x, 0f) + } else if (state.offset.value < 0f && available.x > 0f) { + scope.launch { state.offset.snapTo(state.offset.value + available.x) } + Offset(available.x, 0f) + } else { + Offset.Zero + } + } + + override fun onPostScroll( + consumed: Offset, + available: Offset, + source: NestedScrollSource, + ): Offset { + return if (available.x > 0f) { + scope.launch { state.offset.snapTo(state.offset.value + available.x) } + Offset(available.x, 0f) + } else if (available.x < 0f) { + scope.launch { state.offset.snapTo(state.offset.value + available.x) } + Offset(available.x, 0f) + } else { + Offset.Zero + } + } + + override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity { + scope.launch { + state.offset.animateTo( + if (state.offset.value >= state.pagerState.layoutInfo.pageSize / 2f) { + state.pagerState.layoutInfo.pageSize * 2f + } else if ( + state.offset.value <= -state.pagerState.layoutInfo.pageSize / 2f + ) { + -state.pagerState.layoutInfo.pageSize * 2f + } else { + 0f + } + ) + if (state.offset.value != 0f) { + onDismissed() + } + } + return super.onPostFling(consumed, available) + } + } + } + + Box(modifier = modifier) { + HorizontalPager( + state = state.pagerState, + userScrollEnabled = state.isScrollingEnabled, + key = key, + pageSpacing = pageSpacing, + pageContent = pageContent, + modifier = + Modifier.thenIf(state.isDismissible) { + Modifier.nestedScroll(nestedScrollConnection).graphicsLayer { + translationX = state.offset.value + } + }, + ) + + indicator() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/media/remedia/ui/compose/Media.kt b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/compose/Media.kt index c9fb8e877009..9eb55a8eff2e 100644 --- a/packages/SystemUI/src/com/android/systemui/media/remedia/ui/compose/Media.kt +++ b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/compose/Media.kt @@ -20,6 +20,7 @@ package com.android.systemui.media.remedia.ui.compose import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.Crossfade +import androidx.compose.animation.animateColorAsState import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.RepeatMode @@ -36,6 +37,7 @@ import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.hoverable +import androidx.compose.foundation.indication import androidx.compose.foundation.interaction.DragInteraction import androidx.compose.foundation.interaction.Interaction import androidx.compose.foundation.interaction.MutableInteractionSource @@ -43,9 +45,9 @@ import androidx.compose.foundation.interaction.PressInteraction import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding @@ -63,14 +65,14 @@ import androidx.compose.material3.SliderColors import androidx.compose.material3.SliderDefaults.colors import androidx.compose.material3.SliderState import androidx.compose.material3.Text +import androidx.compose.material3.ripple import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.Stable import androidx.compose.runtime.getValue import androidx.compose.runtime.key import androidx.compose.runtime.mutableStateListOf -import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -90,6 +92,7 @@ import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.Layout +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.Dp @@ -105,20 +108,137 @@ import com.android.compose.animation.scene.SceneKey import com.android.compose.animation.scene.SceneTransitionLayout import com.android.compose.animation.scene.rememberMutableSceneTransitionLayoutState import com.android.compose.animation.scene.transitions -import com.android.compose.theme.LocalAndroidColorScheme +import com.android.compose.ui.graphics.painter.rememberDrawablePainter import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.ui.compose.Icon +import com.android.systemui.common.ui.compose.PagerDots import com.android.systemui.common.ui.compose.load import com.android.systemui.communal.ui.compose.extensions.detectLongPressGesture +import com.android.systemui.lifecycle.rememberViewModel +import com.android.systemui.media.remedia.shared.model.MediaCardActionButtonLayout +import com.android.systemui.media.remedia.shared.model.MediaColorScheme import com.android.systemui.media.remedia.shared.model.MediaSessionState import com.android.systemui.media.remedia.ui.viewmodel.MediaCardGutsViewModel import com.android.systemui.media.remedia.ui.viewmodel.MediaCardViewModel +import com.android.systemui.media.remedia.ui.viewmodel.MediaCarouselVisibility +import com.android.systemui.media.remedia.ui.viewmodel.MediaNavigationViewModel import com.android.systemui.media.remedia.ui.viewmodel.MediaOutputSwitcherChipViewModel import com.android.systemui.media.remedia.ui.viewmodel.MediaPlayPauseActionViewModel import com.android.systemui.media.remedia.ui.viewmodel.MediaSecondaryActionViewModel -import com.android.systemui.media.remedia.ui.viewmodel.MediaSeekBarViewModel +import com.android.systemui.media.remedia.ui.viewmodel.MediaViewModel import kotlin.math.max +/** + * Renders a media controls UI element. + * + * This composable supports a multitude of presentation styles/layouts controlled by the + * [presentationStyle] parameter. If the card carousel can be swiped away to dismiss by the user, + * the [onDismissed] callback will be invoked when/if that happens. + */ +@Composable +fun Media( + viewModelFactory: MediaViewModel.Factory, + presentationStyle: MediaPresentationStyle, + behavior: MediaUiBehavior, + onDismissed: () -> Unit, + modifier: Modifier = Modifier, +) { + val context = LocalContext.current + val viewModel: MediaViewModel = + rememberViewModel("Media.viewModel") { + viewModelFactory.create( + context = context, + carouselVisibility = behavior.carouselVisibility, + ) + } + + CardCarousel( + viewModel = viewModel, + presentationStyle = presentationStyle, + behavior = behavior, + onDismissed = onDismissed, + modifier = modifier, + ) +} + +/** + * Renders a media controls carousel of cards. + * + * This composable supports a multitude of presentation styles/layouts controlled by the + * [presentationStyle] parameter. The behavior is controlled by [behavior]. If + * [MediaUiBehavior.isCarouselDismissible] is `true`, the [onDismissed] callback will be invoked + * when/if that happens. + */ +@Composable +private fun CardCarousel( + viewModel: MediaViewModel, + presentationStyle: MediaPresentationStyle, + behavior: MediaUiBehavior, + onDismissed: () -> Unit, + modifier: Modifier = Modifier, +) { + AnimatedVisibility(visible = viewModel.isCarouselVisible, modifier = modifier) { + CardCarouselContent( + viewModel = viewModel, + presentationStyle = presentationStyle, + behavior = behavior, + onDismissed = onDismissed, + ) + } +} + +@Composable +private fun CardCarouselContent( + viewModel: MediaViewModel, + presentationStyle: MediaPresentationStyle, + behavior: MediaUiBehavior, + onDismissed: () -> Unit, + modifier: Modifier = Modifier, +) { + val pagerState = + rememberDismissibleHorizontalPagerState( + isDismissible = behavior.isCarouselDismissible, + isScrollingEnabled = behavior.isCarouselScrollingEnabled, + ) { + viewModel.cards.size + } + + val roundedCornerShape = RoundedCornerShape(32.dp) + + LaunchedEffect(pagerState.pagerState.currentPage) { + viewModel.onCardSelected(pagerState.pagerState.currentPage) + } + + DismissibleHorizontalPager( + state = pagerState, + onDismissed = onDismissed, + pageSpacing = 8.dp, + key = { index -> viewModel.cards[index].key }, + indicator = { + if (pagerState.pagerState.pageCount > 1) { + PagerDots( + pagerState = pagerState.pagerState, + activeColor = Color(0xffdee0ff), + nonActiveColor = Color(0xffa7a9ca), + dotSize = 6.dp, + spaceSize = 6.dp, + modifier = + Modifier.align(Alignment.BottomCenter).padding(8.dp).graphicsLayer { + translationX = pagerState.offset.value + }, + ) + } + }, + modifier = modifier.padding(8.dp).clip(roundedCornerShape), + ) { index -> + Card( + viewModel = viewModel.cards[index], + presentationStyle = presentationStyle, + modifier = Modifier.clip(roundedCornerShape), + ) + } +} + /** Renders the UI of a single media card. */ @Composable private fun Card( @@ -139,7 +259,7 @@ private fun Card( Box(modifier) { if (stlState.currentScene != Media.Scenes.Compact) { - CardBackground(imageLoader = viewModel.artLoader, modifier = Modifier.matchParentSize()) + CardBackground(image = viewModel.background, modifier = Modifier.matchParentSize()) } key(stlState) { @@ -158,6 +278,22 @@ private fun Card( } } +@Composable +private fun rememberAnimatedColorScheme(colorScheme: MediaColorScheme): AnimatedColorScheme { + val animatedPrimary by animateColorAsState(targetValue = colorScheme.primary) + val animatedOnPrimary by animateColorAsState(targetValue = colorScheme.onPrimary) + + return remember { + object : AnimatedColorScheme { + override val primary: Color + get() = animatedPrimary + + override val onPrimary: Color + get() = animatedOnPrimary + } + } +} + /** * Renders the foreground of a card, including all UI content and the internal "guts". * @@ -180,6 +316,8 @@ private fun ContentScope.CardForeground( val isGutsVisible = viewModel.guts.isVisible LaunchedEffect(isGutsVisible) { gutsAlphaAnimatable.animateTo(if (isGutsVisible) 1f else 0f) } + val colorScheme = rememberAnimatedColorScheme(viewModel.colorScheme) + // Use a custom layout to measure the content even if the content is being hidden because the // internal guts are showing. This is needed because only the content knows the size the of the // card and the guts are set to be the same size of the content. @@ -189,6 +327,7 @@ private fun ContentScope.CardForeground( viewModel = viewModel, threeRows = threeRows, fillHeight = fillHeight, + colorScheme = colorScheme, modifier = Modifier.graphicsLayer { compositingStrategy = CompositingStrategy.ModulateAlpha @@ -198,6 +337,7 @@ private fun ContentScope.CardForeground( CardGuts( viewModel = viewModel.guts, + colorScheme = colorScheme, modifier = Modifier.graphicsLayer { compositingStrategy = CompositingStrategy.ModulateAlpha @@ -232,29 +372,37 @@ private fun ContentScope.CardForegroundContent( viewModel: MediaCardViewModel, threeRows: Boolean, fillHeight: Boolean, + colorScheme: AnimatedColorScheme, modifier: Modifier = Modifier, ) { Column( modifier = - modifier - .combinedClickable( - onClick = viewModel.onClick, - onLongClick = viewModel.onLongClick, - onClickLabel = viewModel.onClickLabel, - ) - .padding(16.dp) + modifier.combinedClickable( + onClick = viewModel.onClick, + onLongClick = viewModel.onLongClick, + onClickLabel = viewModel.onClickLabel, + ) ) { // Always add the first/top row, regardless of presentation style. - Row(verticalAlignment = Alignment.CenterVertically) { + Box(modifier = Modifier.fillMaxWidth()) { // Icon. Icon( icon = viewModel.icon, - tint = LocalAndroidColorScheme.current.primaryFixed, - modifier = Modifier.size(24.dp).clip(CircleShape), + tint = colorScheme.primary, + modifier = + Modifier.align(Alignment.TopStart) + .padding(top = 16.dp, start = 16.dp) + .size(24.dp) + .clip(CircleShape), ) - Spacer(modifier = Modifier.weight(1f)) - viewModel.outputSwitcherChips.fastForEach { chip -> - OutputSwitcherChip(viewModel = chip, modifier = Modifier.padding(start = 8.dp)) + + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp), + modifier = Modifier.align(Alignment.TopEnd), + ) { + viewModel.outputSwitcherChips.fastForEach { chip -> + OutputSwitcherChip(viewModel = chip, colorScheme = colorScheme) + } } } @@ -271,7 +419,7 @@ private fun ContentScope.CardForegroundContent( // Second row. Row( verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.padding(top = 16.dp), + modifier = Modifier.padding(start = 16.dp, top = 16.dp, end = 16.dp), ) { Metadata( title = viewModel.title, @@ -280,14 +428,16 @@ private fun ContentScope.CardForegroundContent( modifier = Modifier.weight(1f).padding(end = 8.dp), ) - AnimatedVisibility(visible = viewModel.playPauseAction.isVisible) { - PlayPauseAction( - viewModel = viewModel.playPauseAction, - buttonWidth = 48.dp, - buttonColor = LocalAndroidColorScheme.current.primaryFixed, - iconColor = LocalAndroidColorScheme.current.onPrimaryFixed, - buttonCornerRadius = { isPlaying -> if (isPlaying) 16.dp else 48.dp }, - ) + if (viewModel.actionButtonLayout == MediaCardActionButtonLayout.WithPlayPause) { + AnimatedVisibility(visible = viewModel.playPauseAction != null) { + PlayPauseAction( + viewModel = checkNotNull(viewModel.playPauseAction), + buttonWidth = 48.dp, + buttonColor = colorScheme.primary, + iconColor = colorScheme.onPrimary, + buttonCornerRadius = { isPlaying -> if (isPlaying) 16.dp else 48.dp }, + ) + } } } @@ -295,9 +445,16 @@ private fun ContentScope.CardForegroundContent( Row( horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.padding(top = 24.dp), + modifier = Modifier.padding(start = 16.dp, top = 24.dp, end = 16.dp, bottom = 16.dp), ) { - Navigation(viewModel = viewModel.seekBar, isSeekBarVisible = true) + Navigation( + viewModel = viewModel.navigation, + isSeekBarVisible = true, + areActionsVisible = + viewModel.actionButtonLayout == MediaCardActionButtonLayout.WithPlayPause, + modifier = Modifier.weight(1f), + ) + viewModel.additionalActions.fastForEachIndexed { index, action -> SecondaryAction( viewModel = action, @@ -311,7 +468,7 @@ private fun ContentScope.CardForegroundContent( // Bottom row. Row( verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.padding(top = 36.dp), + modifier = Modifier.padding(start = 16.dp, top = 36.dp, end = 16.dp, bottom = 16.dp), ) { Metadata( title = viewModel.title, @@ -321,18 +478,34 @@ private fun ContentScope.CardForegroundContent( ) Navigation( - viewModel = viewModel.seekBar, + viewModel = viewModel.navigation, isSeekBarVisible = false, + areActionsVisible = + viewModel.actionButtonLayout == MediaCardActionButtonLayout.WithPlayPause, modifier = Modifier.padding(end = 8.dp), ) - PlayPauseAction( - viewModel = viewModel.playPauseAction, - buttonWidth = 48.dp, - buttonColor = LocalAndroidColorScheme.current.primaryFixed, - iconColor = LocalAndroidColorScheme.current.onPrimaryFixed, - buttonCornerRadius = { isPlaying -> if (isPlaying) 16.dp else 48.dp }, - ) + if ( + viewModel.actionButtonLayout == MediaCardActionButtonLayout.SecondaryActionsOnly + ) { + viewModel.additionalActions.fastForEachIndexed { index, action -> + SecondaryAction( + viewModel = action, + element = Media.Elements.additionalActionButton(index), + modifier = Modifier.padding(end = 8.dp), + ) + } + } + + AnimatedVisibility(visible = viewModel.playPauseAction != null) { + PlayPauseAction( + viewModel = checkNotNull(viewModel.playPauseAction), + buttonWidth = 48.dp, + buttonColor = colorScheme.primary, + iconColor = colorScheme.onPrimary, + buttonCornerRadius = { isPlaying -> if (isPlaying) 16.dp else 48.dp }, + ) + } } } } @@ -375,18 +548,18 @@ private fun ContentScope.CompactCardForeground( iconColor = MaterialTheme.colorScheme.onSurface, ) - val nextAction = (viewModel.seekBar as? MediaSeekBarViewModel.Showing)?.next - if (nextAction != null) { + val rightAction = (viewModel.navigation as? MediaNavigationViewModel.Showing)?.right + if (rightAction != null) { SecondaryAction( - viewModel = nextAction, + viewModel = rightAction, element = Media.Elements.NextButton, iconColor = MaterialTheme.colorScheme.onSurface, ) } - AnimatedVisibility(visible = viewModel.playPauseAction.isVisible) { + AnimatedVisibility(visible = viewModel.playPauseAction != null) { PlayPauseAction( - viewModel = viewModel.playPauseAction, + viewModel = checkNotNull(viewModel.playPauseAction), buttonWidth = 72.dp, buttonColor = MaterialTheme.colorScheme.primaryContainer, iconColor = MaterialTheme.colorScheme.onPrimaryContainer, @@ -398,47 +571,38 @@ private fun ContentScope.CompactCardForeground( /** Renders the background of a card, loading the artwork and showing an overlay on top of it. */ @Composable -private fun CardBackground(imageLoader: suspend () -> ImageBitmap, modifier: Modifier = Modifier) { - var image: ImageBitmap? by remember { mutableStateOf(null) } - LaunchedEffect(imageLoader) { - image = null - image = imageLoader() - } - - val gradientBaseColor = MaterialTheme.colorScheme.onSurface - Box( - modifier = - modifier.drawWithContent { - // Draw the content of the box (loaded art or placeholder). - drawContent() - - if (image != null) { - // Then draw the overlay. - drawRect( - brush = - Brush.radialGradient( - 0f to gradientBaseColor.copy(alpha = 0.65f), - 1f to gradientBaseColor.copy(alpha = 0.75f), - center = size.center, - radius = max(size.width, size.height) / 2, - ) - ) - } - } - ) { - image?.let { loadedImage -> +private fun CardBackground(image: ImageBitmap?, modifier: Modifier = Modifier) { + Crossfade(targetState = image, modifier = modifier) { imageOrNull -> + if (imageOrNull != null) { // Loaded art. + val gradientBaseColor = MaterialTheme.colorScheme.onSurface Image( - bitmap = loadedImage, + bitmap = imageOrNull, contentDescription = null, contentScale = ContentScale.Crop, - modifier = Modifier.matchParentSize(), + modifier = + Modifier.fillMaxSize().drawWithContent { + // Draw the content (loaded art). + drawContent() + + if (image != null) { + // Then draw the overlay. + drawRect( + brush = + Brush.radialGradient( + 0f to gradientBaseColor.copy(alpha = 0.65f), + 1f to gradientBaseColor.copy(alpha = 0.75f), + center = size.center, + radius = max(size.width, size.height) / 2, + ) + ) + } + }, ) + } else { + // Placeholder. + Box(Modifier.background(MaterialTheme.colorScheme.onSurface).fillMaxSize()) } - ?: run { - // Placeholder. - Box(Modifier.background(MaterialTheme.colorScheme.onSurface).matchParentSize()) - } } } @@ -449,22 +613,26 @@ private fun CardBackground(imageLoader: suspend () -> ImageBitmap, modifier: Mod * would otherwise be showing based on the view-model alone. This is meant for callers to decide * whether they'd like to show the seek bar in addition to the prev/next buttons or just show the * buttons. + * + * If [areActionsVisible] is `false`, the left/right buttons to the left and right of the seek bar + * will not be included in the layout. */ @Composable private fun ContentScope.Navigation( - viewModel: MediaSeekBarViewModel, + viewModel: MediaNavigationViewModel, isSeekBarVisible: Boolean, + areActionsVisible: Boolean, modifier: Modifier = Modifier, ) { when (viewModel) { - is MediaSeekBarViewModel.Showing -> { + is MediaNavigationViewModel.Showing -> { Row( horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically, modifier = modifier, ) { - viewModel.previous?.let { - SecondaryAction(viewModel = it, element = Media.Elements.PrevButton) + if (areActionsVisible) { + SecondaryAction(viewModel = viewModel.left, element = Media.Elements.PrevButton) } val interactionSource = remember { MutableInteractionSource() } @@ -499,13 +667,16 @@ private fun ContentScope.Navigation( } } - viewModel.next?.let { - SecondaryAction(viewModel = it, element = Media.Elements.NextButton) + if (areActionsVisible) { + SecondaryAction( + viewModel = viewModel.right, + element = Media.Elements.NextButton, + ) } } } - is MediaSeekBarViewModel.Hidden -> Unit + is MediaNavigationViewModel.Hidden -> Unit } } @@ -647,7 +818,11 @@ private fun SeekBarTrack( /** Renders the internal "guts" of a card. */ @Composable -private fun CardGuts(viewModel: MediaCardGutsViewModel, modifier: Modifier = Modifier) { +private fun CardGuts( + viewModel: MediaCardGutsViewModel, + colorScheme: AnimatedColorScheme, + modifier: Modifier = Modifier, +) { Box( modifier = modifier.pointerInput(Unit) { detectLongPressGesture { viewModel.onLongClick() } } @@ -682,7 +857,7 @@ private fun CardGuts(viewModel: MediaCardGutsViewModel, modifier: Modifier = Mod ) { Text( text = checkNotNull(viewModel.primaryAction.text), - color = LocalAndroidColorScheme.current.onPrimaryFixed, + color = colorScheme.onPrimary, ) } @@ -740,29 +915,51 @@ private fun ContentScope.Metadata( @Composable private fun OutputSwitcherChip( viewModel: MediaOutputSwitcherChipViewModel, + colorScheme: AnimatedColorScheme, modifier: Modifier = Modifier, ) { - PlatformButton( - onClick = viewModel.onClick, - colors = - ButtonDefaults.buttonColors( - containerColor = LocalAndroidColorScheme.current.primaryFixed - ), - contentPadding = PaddingValues(start = 8.dp, end = 12.dp, top = 4.dp, bottom = 4.dp), - modifier = modifier.height(24.dp), + // For accessibility reasons, the touch area for the chip needs to be at least 48dp in height. + // At the same time, the rounded corner chip should only be as tall as it needs to be to contain + // its contents and look like a nice design; also, the ripple effect should only be shown within + // the bounds of the chip. + // + // This is achieved by sharing this InteractionSource between the outer and inner composables. + // + // The outer composable hosts that clickable that writes user events into the InteractionSource. + // The inner composable consumes the user events from the InteractionSource and feeds them into + // its indication. + val clickInteractionSource = remember { MutableInteractionSource() } + Box( + modifier = + modifier + .height(48.dp) + .clickable(interactionSource = clickInteractionSource, indication = null) { + viewModel.onClick() + } + .padding(top = 16.dp, end = 16.dp, bottom = 8.dp) ) { - Icon( - icon = viewModel.icon, - tint = LocalAndroidColorScheme.current.onPrimaryFixed, - modifier = Modifier.size(16.dp), - ) - viewModel.text?.let { - Spacer(Modifier.size(4.dp)) - Text( - text = viewModel.text, - style = MaterialTheme.typography.bodySmall, - color = LocalAndroidColorScheme.current.onPrimaryFixed, + Row( + horizontalArrangement = Arrangement.spacedBy(4.dp), + verticalAlignment = Alignment.CenterVertically, + modifier = + Modifier.clip(RoundedCornerShape(12.dp)) + .background(colorScheme.primary) + .indication(clickInteractionSource, ripple()) + .padding(start = 8.dp, end = 12.dp, top = 4.dp, bottom = 4.dp), + ) { + Icon( + icon = viewModel.icon, + tint = colorScheme.onPrimary, + modifier = Modifier.size(16.dp), ) + + viewModel.text?.let { + Text( + text = viewModel.text, + style = MaterialTheme.typography.bodySmall, + color = colorScheme.onPrimary, + ) + } } } } @@ -785,7 +982,8 @@ private fun ContentScope.PlayPauseAction( // This element can be animated when switching between scenes inside a media card. Element(key = Media.Elements.PlayPauseButton, modifier = modifier) { PlatformButton( - onClick = viewModel.onClick, + onClick = viewModel.onClick ?: {}, + enabled = viewModel.onClick != null, colors = ButtonDefaults.buttonColors(containerColor = buttonColor), shape = RoundedCornerShape(cornerRadius), modifier = Modifier.size(width = buttonWidth, height = 48.dp), @@ -793,22 +991,29 @@ private fun ContentScope.PlayPauseAction( when (viewModel.state) { is MediaSessionState.Playing, is MediaSessionState.Paused -> { - // TODO(b/399860531): load this expensive-to-load animated vector drawable off - // the main thread. - val iconResource = checkNotNull(viewModel.icon) - Icon( - painter = - rememberAnimatedVectorPainter( - animatedImageVector = - AnimatedImageVector.animatedVectorResource( - id = iconResource.res - ), - atEnd = viewModel.state == MediaSessionState.Playing, - ), - contentDescription = iconResource.contentDescription?.load(), - tint = iconColor, - modifier = Modifier.size(24.dp), - ) + val painterOrNull = + when (viewModel.icon) { + // TODO(b/399860531): load this expensive-to-load animated vector + // drawable off the main thread. + is Icon.Resource -> + rememberAnimatedVectorPainter( + animatedImageVector = + AnimatedImageVector.animatedVectorResource( + id = viewModel.icon.res + ), + atEnd = viewModel.state == MediaSessionState.Playing, + ) + is Icon.Loaded -> rememberDrawablePainter(viewModel.icon.drawable) + null -> null + } + painterOrNull?.let { painter -> + Icon( + painter = painter, + contentDescription = viewModel.icon?.contentDescription?.load(), + tint = iconColor, + modifier = Modifier.size(24.dp), + ) + } } is MediaSessionState.Buffering -> { CircularProgressIndicator(color = iconColor, modifier = Modifier.size(24.dp)) @@ -831,7 +1036,7 @@ private fun ContentScope.SecondaryAction( element: ElementKey? = null, iconColor: Color = Color.White, ) { - if (element != null) { + if (viewModel !is MediaSecondaryActionViewModel.None && element != null) { Element(key = element, modifier = modifier) { SecondaryActionContent(viewModel = viewModel, iconColor = iconColor) } @@ -847,14 +1052,22 @@ private fun SecondaryActionContent( iconColor: Color, modifier: Modifier = Modifier, ) { - PlatformIconButton( - onClick = viewModel.onClick, - iconResource = (viewModel.icon as Icon.Resource).res, - contentDescription = viewModel.icon.contentDescription?.load(), - colors = IconButtonDefaults.iconButtonColors(contentColor = iconColor), - enabled = viewModel.isEnabled, - modifier = modifier.size(48.dp).padding(13.dp), - ) + val sharedModifier = modifier.size(48.dp).padding(13.dp) + when (viewModel) { + is MediaSecondaryActionViewModel.Action -> + PlatformIconButton( + onClick = viewModel.onClick ?: {}, + iconResource = (viewModel.icon as Icon.Resource).res, + contentDescription = viewModel.icon.contentDescription?.load(), + colors = IconButtonDefaults.iconButtonColors(contentColor = iconColor), + enabled = viewModel.onClick != null, + modifier = sharedModifier, + ) + + is MediaSecondaryActionViewModel.ReserveSpace -> Spacer(modifier = sharedModifier) + + is MediaSecondaryActionViewModel.None -> Unit + } } /** Enumerates all supported media presentation styles. */ @@ -867,6 +1080,19 @@ enum class MediaPresentationStyle { Compact, } +data class MediaUiBehavior( + val isCarouselDismissible: Boolean = true, + val isCarouselScrollingEnabled: Boolean = true, + val carouselVisibility: MediaCarouselVisibility = MediaCarouselVisibility.WhenNotEmpty, + val isFalsingProtectionNeeded: Boolean = false, +) + +@Stable +private interface AnimatedColorScheme { + val primary: Color + val onPrimary: Color +} + private object Media { /** diff --git a/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaCardViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaCardViewModel.kt index ecd6e6d094d9..833a04ddcb55 100644 --- a/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaCardViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaCardViewModel.kt @@ -19,6 +19,8 @@ package com.android.systemui.media.remedia.ui.viewmodel import androidx.compose.runtime.Stable import androidx.compose.ui.graphics.ImageBitmap import com.android.systemui.common.shared.model.Icon +import com.android.systemui.media.remedia.shared.model.MediaCardActionButtonLayout +import com.android.systemui.media.remedia.shared.model.MediaColorScheme /** Models UI state for a media card. */ @Stable @@ -31,20 +33,19 @@ interface MediaCardViewModel { val icon: Icon - /** - * A callback to load the artwork for the media shown on this card. This callback will be - * invoked on the main thread, it's up to the implementation to move the loading off the main - * thread. - */ - val artLoader: suspend () -> ImageBitmap + val background: ImageBitmap? + + val colorScheme: MediaColorScheme val title: String val subtitle: String - val playPauseAction: MediaPlayPauseActionViewModel + val actionButtonLayout: MediaCardActionButtonLayout + + val playPauseAction: MediaPlayPauseActionViewModel? - val seekBar: MediaSeekBarViewModel + val navigation: MediaNavigationViewModel val additionalActions: List<MediaSecondaryActionViewModel> @@ -53,7 +54,7 @@ interface MediaCardViewModel { val outputSwitcherChips: List<MediaOutputSwitcherChipViewModel> /** Simple icon-only version of the output switcher for use in compact UIs. */ - val outputSwitcherChipButton: MediaSecondaryActionViewModel + val outputSwitcherChipButton: MediaSecondaryActionViewModel.Action val onClick: () -> Unit diff --git a/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaCarouselVisibility.kt b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaCarouselVisibility.kt new file mode 100644 index 000000000000..53aa87ce0843 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaCarouselVisibility.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.media.remedia.ui.viewmodel + +/** Enumerates the known rules for media carousel visibility. */ +enum class MediaCarouselVisibility { + + /** The carousel should be shown as long as it has at least one card. */ + WhenNotEmpty, + + /** + * The carousel should be shown as long as it has at least one card that represents an active + * media session. In other words: if all cards in the carousel represent _inactive_ sessions, + * the carousel should _not_ be visible. + */ + WhenAnyCardIsActive, +} diff --git a/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaSeekBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaNavigationViewModel.kt index f1ced6bf908d..c34c7337290f 100644 --- a/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaSeekBarViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaNavigationViewModel.kt @@ -18,17 +18,26 @@ package com.android.systemui.media.remedia.ui.viewmodel import androidx.annotation.FloatRange -/** Models UI state for the seek bar. */ -sealed interface MediaSeekBarViewModel { +/** + * Models UI state for the navigation component of the UI (potentially containing the seek bar and + * the buttons to its left and right). + */ +sealed interface MediaNavigationViewModel { /** The seek bar should be showing. */ data class Showing( /** The progress to show on the seek bar, between `0` and `1`. */ @FloatRange(from = 0.0, to = 1.0) val progress: Float, - /** The previous button; or `null` if it should be absent in the UI. */ - val previous: MediaSecondaryActionViewModel?, - /** The next button; or `null` if it should be absent in the UI. */ - val next: MediaSecondaryActionViewModel?, + /** + * The action button to the left of the seek bar; or `null` if it should be absent in the + * UI. + */ + val left: MediaSecondaryActionViewModel, + /** + * The action button to the right of the seek bar; or `null` if it should be absent in the + * UI. + */ + val right: MediaSecondaryActionViewModel, /** * Whether the portion of the seek bar track before the thumb should show the squiggle * animation. @@ -50,8 +59,8 @@ sealed interface MediaSeekBarViewModel { * the seek bar). The position/progress should be committed. */ val onScrubFinished: () -> Unit, - ) : MediaSeekBarViewModel + ) : MediaNavigationViewModel /** The seek bar should be hidden. */ - data object Hidden : MediaSeekBarViewModel + data object Hidden : MediaNavigationViewModel } diff --git a/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaPlayPauseActionViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaPlayPauseActionViewModel.kt index 4cb11bc0b8d0..ecc92d778f8e 100644 --- a/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaPlayPauseActionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaPlayPauseActionViewModel.kt @@ -21,8 +21,7 @@ import com.android.systemui.media.remedia.shared.model.MediaSessionState /** Models UI state for the play/pause action button within media controls. */ data class MediaPlayPauseActionViewModel( - val isVisible: Boolean, val state: MediaSessionState, - val icon: Icon.Resource?, - val onClick: () -> Unit, + val icon: Icon?, + val onClick: (() -> Unit)?, ) diff --git a/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaSecondaryActionViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaSecondaryActionViewModel.kt index a4806800a7b1..d28ca7ab7121 100644 --- a/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaSecondaryActionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaSecondaryActionViewModel.kt @@ -19,8 +19,10 @@ package com.android.systemui.media.remedia.ui.viewmodel import com.android.systemui.common.shared.model.Icon /** Models UI state for a secondary action button within media controls. */ -data class MediaSecondaryActionViewModel( - val icon: Icon, - val isEnabled: Boolean, - val onClick: () -> Unit, -) +sealed interface MediaSecondaryActionViewModel { + data class Action(val icon: Icon, val onClick: (() -> Unit)?) : MediaSecondaryActionViewModel + + data object ReserveSpace : MediaSecondaryActionViewModel + + data object None : MediaSecondaryActionViewModel +} diff --git a/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaViewModel.kt new file mode 100644 index 000000000000..b4f3d2724e75 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/remedia/ui/viewmodel/MediaViewModel.kt @@ -0,0 +1,253 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.media.remedia.ui.viewmodel + +import android.content.Context +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.compose.ui.graphics.ImageBitmap +import com.android.systemui.common.shared.model.ContentDescription +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.lifecycle.ExclusiveActivatable +import com.android.systemui.media.remedia.domain.interactor.MediaInteractor +import com.android.systemui.media.remedia.domain.model.MediaActionModel +import com.android.systemui.media.remedia.shared.model.MediaColorScheme +import com.android.systemui.media.remedia.shared.model.MediaSessionState +import com.android.systemui.res.R +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlin.math.roundToLong +import kotlinx.coroutines.awaitCancellation + +/** Models UI state for a media element. */ +class MediaViewModel +@AssistedInject +constructor( + private val interactor: MediaInteractor, + @Assisted private val context: Context, + @Assisted private val carouselVisibility: MediaCarouselVisibility, +) : ExclusiveActivatable() { + + /** Whether the user is actively moving the thumb of the seek bar. */ + private var isScrubbing: Boolean by mutableStateOf(false) + /** The position of the thumb of the seek bar as the user is scrubbing it. */ + private var seekProgress: Float by mutableFloatStateOf(0f) + /** Whether the internal "guts" are visible. */ + private var isGutsVisible: Boolean by mutableStateOf(false) + /** The index of the currently-selected card. */ + private var selectedCardIndex: Int by mutableIntStateOf(0) + private set + + /** The current list of cards to show in the UI. */ + val cards: List<MediaCardViewModel> by derivedStateOf { + interactor.sessions.mapIndexed { sessionIndex, session -> + val isCurrentSessionAndScrubbing = isScrubbing && sessionIndex == selectedCardIndex + object : MediaCardViewModel { + override val key = session.key + override val icon = session.appIcon + override val background: ImageBitmap? + get() = session.background + + override val colorScheme: MediaColorScheme + get() = session.colorScheme + + override val title = session.title + override val subtitle = session.subtitle + override val actionButtonLayout = session.actionButtonLayout + override val playPauseAction = + session.playPauseAction.toPlayPauseActionViewModel(session.state) + override val additionalActions: List<MediaSecondaryActionViewModel> + get() { + return session.additionalActions.map { action -> + action.toSecondaryActionViewModel() + } + } + + override val navigation: MediaNavigationViewModel + get() { + return if (session.canBeScrubbed) { + MediaNavigationViewModel.Showing( + progress = + if (!isCurrentSessionAndScrubbing) { + session.positionMs.toFloat() / session.durationMs + } else { + seekProgress + }, + left = session.leftAction.toSecondaryActionViewModel(), + right = session.rightAction.toSecondaryActionViewModel(), + isSquiggly = + session.state != MediaSessionState.Paused && + !isCurrentSessionAndScrubbing, + isScrubbing = isCurrentSessionAndScrubbing, + onScrubChange = { progress -> + check(selectedCardIndex == sessionIndex) { + "Can't seek on a card that's not the selected card!" + } + isScrubbing = true + seekProgress = progress + }, + onScrubFinished = { + interactor.seek( + sessionKey = session.key, + to = (seekProgress * session.durationMs).roundToLong(), + ) + isScrubbing = false + }, + ) + } else { + MediaNavigationViewModel.Hidden + } + } + + override val guts: MediaCardGutsViewModel + get() { + return MediaCardGutsViewModel( + isVisible = isGutsVisible, + text = + if (session.canBeHidden) { + context.getString( + R.string.controls_media_close_session, + session.appName, + ) + } else { + context.getString(R.string.controls_media_active_session) + }, + primaryAction = + if (session.canBeHidden) { + MediaGutsButtonViewModel( + text = + context.getString( + R.string.controls_media_dismiss_button + ), + onClick = { + interactor.hide(session.key) + isGutsVisible = false + }, + ) + } else { + MediaGutsButtonViewModel( + text = context.getString(R.string.cancel), + onClick = { isGutsVisible = false }, + ) + }, + secondaryAction = + MediaGutsButtonViewModel( + text = context.getString(R.string.cancel), + onClick = { isGutsVisible = false }, + ) + .takeIf { session.canBeHidden }, + settingsButton = + MediaGutsSettingsButtonViewModel( + icon = + Icon.Resource( + res = R.drawable.ic_settings, + contentDescription = + ContentDescription.Resource( + res = R.string.controls_media_settings_button + ), + ), + onClick = { interactor.openMediaSettings() }, + ), + onLongClick = { isGutsVisible = false }, + ) + } + + override val outputSwitcherChips: List<MediaOutputSwitcherChipViewModel> + get() { + return listOf( + MediaOutputSwitcherChipViewModel( + icon = session.outputDevice.icon, + text = session.outputDevice.name, + onClick = { + // TODO(b/397989775): tell the UI to show the output switcher. + }, + ) + ) + } + + override val outputSwitcherChipButton: MediaSecondaryActionViewModel.Action + get() { + return MediaSecondaryActionViewModel.Action( + icon = session.outputDevice.icon, + onClick = { + // TODO(b/397989775): tell the UI to show the output switcher. + }, + ) + } + + override val onClick = session.onClick + override val onClickLabel = + context.getString(R.string.controls_media_playing_item_description) + override val onLongClick = { isGutsVisible = true } + } + } + } + + /** Whether the carousel should be visible. */ + val isCarouselVisible: Boolean + get() = + when (carouselVisibility) { + MediaCarouselVisibility.WhenNotEmpty -> interactor.sessions.isNotEmpty() + + MediaCarouselVisibility.WhenAnyCardIsActive -> + interactor.sessions.any { session -> session.isActive } + } + + /** Notifies that the card at [cardIndex] has been selected in the UI. */ + fun onCardSelected(cardIndex: Int) { + check(cardIndex >= 0 && cardIndex < cards.size) + selectedCardIndex = cardIndex + } + + override suspend fun onActivated(): Nothing { + awaitCancellation() + } + + private fun MediaActionModel.toPlayPauseActionViewModel( + mediaSessionState: MediaSessionState + ): MediaPlayPauseActionViewModel? { + return when (this) { + is MediaActionModel.Action -> + MediaPlayPauseActionViewModel( + state = mediaSessionState, + icon = icon, + onClick = onClick ?: {}, + ) + is MediaActionModel.None, + is MediaActionModel.ReserveSpace -> null + } + } + + private fun MediaActionModel.toSecondaryActionViewModel(): MediaSecondaryActionViewModel { + return when (this) { + is MediaActionModel.Action -> + MediaSecondaryActionViewModel.Action(icon = icon, onClick = onClick) + is MediaActionModel.ReserveSpace -> MediaSecondaryActionViewModel.ReserveSpace + is MediaActionModel.None -> MediaSecondaryActionViewModel.None + } + } + + @AssistedFactory + interface Factory { + fun create(context: Context, carouselVisibility: MediaCarouselVisibility): MediaViewModel + } +} diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt index d8fc52bcc55a..8dc27bf4ac3e 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt @@ -162,10 +162,6 @@ constructor( ): Boolean { return this@NoteTaskInitializer.handleKeyGestureEvent(event) } - - override fun isKeyGestureSupported(gestureType: Int): Boolean { - return this@NoteTaskInitializer.isKeyGestureSupported(gestureType) - } } /** @@ -225,10 +221,6 @@ constructor( return true } - private fun isKeyGestureSupported(gestureType: Int): Boolean { - return gestureType == KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES - } - companion object { val MULTI_PRESS_TIMEOUT = ViewConfiguration.getMultiPressTimeout().toLong() val LONG_PRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout().toLong() diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt index f1f5b267f9c1..8920c86282da 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt @@ -18,6 +18,7 @@ package com.android.systemui.qs.composefragment import android.annotation.SuppressLint import android.content.Context +import android.content.res.Configuration import android.graphics.PointF import android.graphics.Rect import android.os.Bundle @@ -48,6 +49,7 @@ import androidx.compose.foundation.layout.requiredHeightIn import androidx.compose.foundation.verticalScroll import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -58,6 +60,7 @@ import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.input.pointer.PointerEventPass import androidx.compose.ui.input.pointer.PointerInputChange @@ -69,6 +72,8 @@ import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.layout.positionInRoot import androidx.compose.ui.layout.positionOnScreen import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.CustomAccessibilityAction @@ -102,6 +107,7 @@ import com.android.mechanics.GestureContext import com.android.systemui.Dumpable import com.android.systemui.Flags import com.android.systemui.brightness.ui.compose.BrightnessSliderContainer +import com.android.systemui.brightness.ui.compose.ContainerColors import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.dump.DumpManager import com.android.systemui.keyboard.shortcut.ui.composable.InteractionsConfig @@ -249,7 +255,7 @@ constructor( @Composable private fun Content() { - PlatformTheme(isDarkTheme = true) { + PlatformTheme(isDarkTheme = true /* Delete AlwaysDarkMode when removing this */) { ProvideShortcutHelperIndication(interactionsConfig = interactionsConfig()) { // TODO(b/389985793): Make sure that there is no coroutine work or recompositions // happening when alwaysCompose is true but isQsVisibleAndAnyShadeExpanded is false. @@ -740,17 +746,26 @@ constructor( ) val BrightnessSlider = @Composable { - BrightnessSliderContainer( - viewModel = containerViewModel.brightnessSliderViewModel, - modifier = + AlwaysDarkMode { + Box( Modifier.systemGestureExclusionInShade( - enabled = { - layoutState.transitionState is - TransitionState.Idle - } - ) - .fillMaxWidth(), - ) + enabled = { + layoutState.transitionState is TransitionState.Idle + } + ) + ) { + BrightnessSliderContainer( + viewModel = + containerViewModel.brightnessSliderViewModel, + containerColors = + ContainerColors( + Color.Transparent, + ContainerColors.defaultContainerColor, + ), + modifier = Modifier.fillMaxWidth(), + ) + } + } } val TileGrid = @Composable { @@ -1226,3 +1241,28 @@ private fun interactionsConfig() = private inline val alwaysCompose get() = Flags.alwaysComposeQsUiFragment() + +/** + * Forces the configuration and themes to be dark theme. This is needed in order to have + * [colorResource] retrieve the dark mode colors. + * + * This should be removed when we remove the force dark mode in [PlatformTheme] at the root of the + * compose hierarchy. + */ +@Composable +private fun AlwaysDarkMode(content: @Composable () -> Unit) { + val currentConfig = LocalConfiguration.current + val darkConfig = + Configuration(currentConfig).apply { + uiMode = + (uiMode and (Configuration.UI_MODE_NIGHT_MASK.inv())) or + Configuration.UI_MODE_NIGHT_YES + } + val newContext = LocalContext.current.createConfigurationContext(darkConfig) + CompositionLocalProvider( + LocalConfiguration provides darkConfig, + LocalContext provides newContext, + ) { + content() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/ui/dialog/TileRequestDialogComposeDelegate.kt b/packages/SystemUI/src/com/android/systemui/qs/external/ui/dialog/TileRequestDialogComposeDelegate.kt index 446be9b9ebcb..59844c7ae664 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/ui/dialog/TileRequestDialogComposeDelegate.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/external/ui/dialog/TileRequestDialogComposeDelegate.kt @@ -85,6 +85,7 @@ constructor( LargeStaticTile( uiState = viewModel.uiState, + iconProvider = viewModel.iconProvider, modifier = Modifier.width( dimensionResource( diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/ui/viewmodel/TileRequestDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/external/ui/viewmodel/TileRequestDialogViewModel.kt index c756adc07ba4..dd281ccc9c50 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/ui/viewmodel/TileRequestDialogViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/external/ui/viewmodel/TileRequestDialogViewModel.kt @@ -26,6 +26,7 @@ import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.lifecycle.ExclusiveActivatable import com.android.systemui.plugins.qs.QSTile import com.android.systemui.qs.external.TileData +import com.android.systemui.qs.panels.ui.viewmodel.toIconProvider import com.android.systemui.qs.panels.ui.viewmodel.toUiState import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIcon import com.android.systemui.qs.tileimpl.QSTileImpl.ResourceIcon @@ -58,6 +59,8 @@ constructor( val uiState by derivedStateOf { state.toUiState(dialogContext.resources) } + val iconProvider by derivedStateOf { state.toIconProvider() } + override suspend fun onActivated(): Nothing { withContext(backgroundDispatcher) { tileData.icon diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileDetails.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileDetails.kt index 701f44e9981c..d40ecc9565ae 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileDetails.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileDetails.kt @@ -61,6 +61,9 @@ fun TileDetails(modifier: Modifier = Modifier, detailsViewModel: DetailsViewMode DisposableEffect(Unit) { onDispose { detailsViewModel.closeDetailedView() } } + val title = tileDetailedViewModel.title + val subTitle = tileDetailedViewModel.subTitle + Column( modifier = modifier @@ -90,7 +93,7 @@ fun TileDetails(modifier: Modifier = Modifier, detailsViewModel: DetailsViewMode ) } Text( - text = tileDetailedViewModel.getTitle(), + text = title, modifier = Modifier.align(Alignment.CenterVertically), textAlign = TextAlign.Center, style = MaterialTheme.typography.titleLarge, @@ -110,7 +113,7 @@ fun TileDetails(modifier: Modifier = Modifier, detailsViewModel: DetailsViewMode } } Text( - text = tileDetailedViewModel.getSubTitle(), + text = subTitle, modifier = Modifier.fillMaxWidth(), textAlign = TextAlign.Center, style = MaterialTheme.typography.titleSmall, diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt index a56fabcc7dc3..bf63c3858542 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt @@ -20,6 +20,7 @@ package com.android.systemui.qs.panels.ui.compose.infinitegrid import android.content.Context import android.content.res.Resources +import android.os.Trace import android.service.quicksettings.Tile.STATE_ACTIVE import android.service.quicksettings.Tile.STATE_INACTIVE import androidx.compose.animation.animateColorAsState @@ -47,8 +48,8 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.runtime.State -import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.produceState import androidx.compose.runtime.remember import androidx.compose.runtime.rememberUpdatedState @@ -59,7 +60,7 @@ import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.platform.LocalConfiguration -import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalResources import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.role @@ -69,6 +70,7 @@ import androidx.compose.ui.semantics.toggleableState import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import androidx.compose.ui.util.trace import com.android.app.tracing.coroutines.launchTraced as launch import com.android.compose.animation.Expandable import com.android.compose.animation.bounceable @@ -80,7 +82,6 @@ import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.haptics.msdl.qs.TileHapticsViewModel import com.android.systemui.haptics.msdl.qs.TileHapticsViewModelFactoryProvider import com.android.systemui.lifecycle.rememberViewModel -import com.android.systemui.plugins.qs.QSTile import com.android.systemui.qs.flags.QsDetailedView import com.android.systemui.qs.panels.ui.compose.BounceableInfo import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.InactiveCornerRadius @@ -88,9 +89,12 @@ import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.TileHeight import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.TileStartPadding import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.longPressLabel +import com.android.systemui.qs.panels.ui.viewmodel.AccessibilityUiState import com.android.systemui.qs.panels.ui.viewmodel.DetailsViewModel +import com.android.systemui.qs.panels.ui.viewmodel.IconProvider import com.android.systemui.qs.panels.ui.viewmodel.TileUiState import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel +import com.android.systemui.qs.panels.ui.viewmodel.toIconProvider import com.android.systemui.qs.panels.ui.viewmodel.toUiState import com.android.systemui.qs.tileimpl.QSTileImpl import com.android.systemui.qs.ui.compose.borderOnFocus @@ -119,6 +123,9 @@ fun TileLazyGrid( ) } +private val TileViewModel.traceName + get() = spec.toString().takeLast(Trace.MAX_SECTION_NAME_LEN) + @Composable fun Tile( tile: TileViewModel, @@ -130,105 +137,114 @@ fun Tile( modifier: Modifier = Modifier, detailsViewModel: DetailsViewModel?, ) { - val currentBounceableInfo by rememberUpdatedState(bounceableInfo) - val resources = resources() - - /* - * Use produce state because [QSTile.State] doesn't have well defined equals (due to - * inheritance). This way, even if tile.state changes, uiState may not change and lead to - * recomposition. - */ - val uiState by - produceState(tile.currentState.toUiState(resources), tile, resources) { - tile.state.collect { value = it.toUiState(resources) } - } + trace(tile.traceName) { + val currentBounceableInfo by rememberUpdatedState(bounceableInfo) + val resources = resources() + + /* + * Use produce state because [QSTile.State] doesn't have well defined equals (due to + * inheritance). This way, even if tile.state changes, uiState may not change and lead to + * recomposition. + */ + val uiState by + produceState(tile.currentState.toUiState(resources), tile, resources) { + tile.state.collect { value = it.toUiState(resources) } + } - val colors = TileDefaults.getColorForState(uiState, iconOnly) - val hapticsViewModel: TileHapticsViewModel? = - rememberViewModel(traceName = "TileHapticsViewModel") { - tileHapticsViewModelFactoryProvider.getHapticsViewModelFactory()?.create(tile) - } + val icon by + produceState(tile.currentState.toIconProvider(), tile) { + tile.state.collect { value = it.toIconProvider() } + } - // TODO(b/361789146): Draw the shapes instead of clipping - val tileShape by TileDefaults.animateTileShapeAsState(uiState.state) - val animatedColor by animateColorAsState(colors.background, label = "QSTileBackgroundColor") + val colors = TileDefaults.getColorForState(uiState, iconOnly) + val hapticsViewModel: TileHapticsViewModel? = + rememberViewModel(traceName = "TileHapticsViewModel") { + tileHapticsViewModelFactoryProvider.getHapticsViewModelFactory()?.create(tile) + } - TileExpandable( - color = { animatedColor }, - shape = tileShape, - squishiness = squishiness, - hapticsViewModel = hapticsViewModel, - modifier = - modifier - .borderOnFocus(color = MaterialTheme.colorScheme.secondary, tileShape.topEnd) - .fillMaxWidth() - .bounceable( - bounceable = currentBounceableInfo.bounceable, - previousBounceable = currentBounceableInfo.previousTile, - nextBounceable = currentBounceableInfo.nextTile, - orientation = Orientation.Horizontal, - bounceEnd = currentBounceableInfo.bounceEnd, - ), - ) { expandable -> - val longClick: (() -> Unit)? = - { - hapticsViewModel?.setTileInteractionState( - TileHapticsViewModel.TileInteractionState.LONG_CLICKED + // TODO(b/361789146): Draw the shapes instead of clipping + val tileShape by TileDefaults.animateTileShapeAsState(uiState.state) + val animatedColor by animateColorAsState(colors.background, label = "QSTileBackgroundColor") + + TileExpandable( + color = { animatedColor }, + shape = tileShape, + squishiness = squishiness, + hapticsViewModel = hapticsViewModel, + modifier = + modifier + .borderOnFocus(color = MaterialTheme.colorScheme.secondary, tileShape.topEnd) + .fillMaxWidth() + .bounceable( + bounceable = currentBounceableInfo.bounceable, + previousBounceable = currentBounceableInfo.previousTile, + nextBounceable = currentBounceableInfo.nextTile, + orientation = Orientation.Horizontal, + bounceEnd = currentBounceableInfo.bounceEnd, + ), + ) { expandable -> + val longClick: (() -> Unit)? = + { + hapticsViewModel?.setTileInteractionState( + TileHapticsViewModel.TileInteractionState.LONG_CLICKED + ) + tile.onLongClick(expandable) + } + .takeIf { uiState.handlesLongClick } + TileContainer( + onClick = { + var hasDetails = false + if (QsDetailedView.isEnabled) { + hasDetails = detailsViewModel?.onTileClicked(tile.spec) == true + } + if (!hasDetails) { + // For those tile's who doesn't have a detailed view, process with their + // `onClick` behavior. + tile.onClick(expandable) + hapticsViewModel?.setTileInteractionState( + TileHapticsViewModel.TileInteractionState.CLICKED + ) + if (uiState.accessibilityUiState.toggleableState != null) { + coroutineScope.launch { + currentBounceableInfo.bounceable.animateBounce() + } + } + } + }, + onLongClick = longClick, + accessibilityUiState = uiState.accessibilityUiState, + iconOnly = iconOnly, + ) { + val iconProvider: Context.() -> Icon = { getTileIcon(icon = icon) } + if (iconOnly) { + SmallTileContent( + iconProvider = iconProvider, + color = colors.icon, + modifier = Modifier.align(Alignment.Center), ) - tile.onLongClick(expandable) - } - .takeIf { uiState.handlesLongClick } - TileContainer( - onClick = { - var hasDetails = false - if (QsDetailedView.isEnabled) { - hasDetails = detailsViewModel?.onTileClicked(tile.spec) == true - } - if (!hasDetails) { - // For those tile's who doesn't have a detailed view, process with their - // `onClick` behavior. - tile.onClick(expandable) - hapticsViewModel?.setTileInteractionState( - TileHapticsViewModel.TileInteractionState.CLICKED + } else { + val iconShape by TileDefaults.animateIconShapeAsState(uiState.state) + val secondaryClick: (() -> Unit)? = + { + hapticsViewModel?.setTileInteractionState( + TileHapticsViewModel.TileInteractionState.CLICKED + ) + tile.onSecondaryClick() + } + .takeIf { uiState.handlesSecondaryClick } + LargeTileContent( + label = uiState.label, + secondaryLabel = uiState.secondaryLabel, + iconProvider = iconProvider, + sideDrawable = uiState.sideDrawable, + colors = colors, + iconShape = iconShape, + toggleClick = secondaryClick, + onLongClick = longClick, + accessibilityUiState = uiState.accessibilityUiState, + squishiness = squishiness, ) - if (uiState.accessibilityUiState.toggleableState != null) { - coroutineScope.launch { currentBounceableInfo.bounceable.animateBounce() } - } } - }, - onLongClick = longClick, - uiState = uiState, - iconOnly = iconOnly, - ) { - val iconProvider: Context.() -> Icon = { getTileIcon(icon = uiState.icon) } - if (iconOnly) { - SmallTileContent( - iconProvider = iconProvider, - color = colors.icon, - modifier = Modifier.align(Alignment.Center), - ) - } else { - val iconShape by TileDefaults.animateIconShapeAsState(uiState.state) - val secondaryClick: (() -> Unit)? = - { - hapticsViewModel?.setTileInteractionState( - TileHapticsViewModel.TileInteractionState.CLICKED - ) - tile.onSecondaryClick() - } - .takeIf { uiState.handlesSecondaryClick } - LargeTileContent( - label = uiState.label, - secondaryLabel = uiState.secondaryLabel, - iconProvider = iconProvider, - sideDrawable = uiState.sideDrawable, - colors = colors, - iconShape = iconShape, - toggleClick = secondaryClick, - onLongClick = longClick, - accessibilityUiState = uiState.accessibilityUiState, - squishiness = squishiness, - ) } } } @@ -257,7 +273,7 @@ private fun TileExpandable( fun TileContainer( onClick: () -> Unit, onLongClick: (() -> Unit)?, - uiState: TileUiState, + accessibilityUiState: AccessibilityUiState, iconOnly: Boolean, content: @Composable BoxScope.() -> Unit, ) { @@ -268,7 +284,7 @@ fun TileContainer( .tileCombinedClickable( onClick = onClick, onLongClick = onLongClick, - uiState = uiState, + accessibilityUiState = accessibilityUiState, iconOnly = iconOnly, ) .sysuiResTag(if (iconOnly) TEST_TAG_SMALL else TEST_TAG_LARGE) @@ -278,7 +294,11 @@ fun TileContainer( } @Composable -fun LargeStaticTile(uiState: TileUiState, modifier: Modifier = Modifier) { +fun LargeStaticTile( + uiState: TileUiState, + iconProvider: IconProvider, + modifier: Modifier = Modifier, +) { val colors = TileDefaults.getColorForState(uiState = uiState, iconOnly = false) Box( @@ -291,7 +311,7 @@ fun LargeStaticTile(uiState: TileUiState, modifier: Modifier = Modifier) { LargeTileContent( label = uiState.label, secondaryLabel = "", - iconProvider = { getTileIcon(icon = uiState.icon) }, + iconProvider = { getTileIcon(icon = iconProvider) }, sideDrawable = null, colors = colors, squishiness = { 1f }, @@ -299,8 +319,8 @@ fun LargeStaticTile(uiState: TileUiState, modifier: Modifier = Modifier) { } } -private fun Context.getTileIcon(icon: QSTile.Icon?): Icon { - return icon?.let { +private fun Context.getTileIcon(icon: IconProvider): Icon { + return icon.icon?.let { if (it is QSTileImpl.ResourceIcon) { Icon.Resource(it.resId, null) } else { @@ -321,28 +341,26 @@ fun Modifier.largeTilePadding(): Modifier { fun Modifier.tileCombinedClickable( onClick: () -> Unit, onLongClick: (() -> Unit)?, - uiState: TileUiState, + accessibilityUiState: AccessibilityUiState, iconOnly: Boolean, ): Modifier { val longPressLabel = longPressLabel() return combinedClickable( onClick = onClick, onLongClick = onLongClick, - onClickLabel = uiState.accessibilityUiState.clickLabel, + onClickLabel = accessibilityUiState.clickLabel, onLongClickLabel = longPressLabel, hapticFeedbackEnabled = !Flags.msdlFeedback(), ) .semantics { - role = uiState.accessibilityUiState.accessibilityRole - if (uiState.accessibilityUiState.accessibilityRole == Role.Switch) { - uiState.accessibilityUiState.toggleableState?.let { toggleableState = it } + role = accessibilityUiState.accessibilityRole + if (accessibilityUiState.accessibilityRole == Role.Switch) { + accessibilityUiState.toggleableState?.let { toggleableState = it } } - stateDescription = uiState.accessibilityUiState.stateDescription + stateDescription = accessibilityUiState.stateDescription } .thenIf(iconOnly) { - Modifier.semantics { - contentDescription = uiState.accessibilityUiState.contentDescription - } + Modifier.semantics { contentDescription = accessibilityUiState.contentDescription } } } @@ -474,14 +492,15 @@ private object TileDefaults { label = label, ) - val corner = remember { - object : CornerSize { - override fun toPx(shapeSize: Size, density: Density): Float { - return with(density) { animatedCornerRadius.toPx() } + return remember { + val corner = + object : CornerSize { + override fun toPx(shapeSize: Size, density: Density): Float { + return with(density) { animatedCornerRadius.toPx() } + } } - } + mutableStateOf(RoundedCornerShape(corner)) } - return remember { derivedStateOf { RoundedCornerShape(corner) } } } } @@ -493,5 +512,5 @@ private object TileDefaults { @ReadOnlyComposable private fun resources(): Resources { LocalConfiguration.current - return LocalContext.current.resources + return LocalResources.current } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt index 153238fc91c9..a66b51f6fe50 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt @@ -59,12 +59,14 @@ import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.toSize import androidx.compose.ui.zIndex import com.android.compose.modifiers.size import com.android.compose.modifiers.thenIf import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.InactiveCornerRadius import com.android.systemui.qs.panels.ui.compose.selection.SelectionDefaults.BADGE_ANGLE_RAD +import com.android.systemui.qs.panels.ui.compose.selection.SelectionDefaults.BadgeIconSize import com.android.systemui.qs.panels.ui.compose.selection.SelectionDefaults.BadgeSize import com.android.systemui.qs.panels.ui.compose.selection.SelectionDefaults.BadgeXOffset import com.android.systemui.qs.panels.ui.compose.selection.SelectionDefaults.BadgeYOffset @@ -149,16 +151,14 @@ fun InteractiveTileContainer( onClick = onClick, ) ) { + val size = with(LocalDensity.current) { BadgeIconSize.toDp() } Icon( Icons.Default.Remove, contentDescription = null, modifier = - Modifier.size( - width = { decorationSize.width.roundToInt() }, - height = { decorationSize.height.roundToInt() }, - ) - .align(Alignment.Center) - .graphicsLayer { this.alpha = badgeIconAlpha }, + Modifier.size(size).align(Alignment.Center).graphicsLayer { + this.alpha = badgeIconAlpha + }, ) } } @@ -219,12 +219,13 @@ fun StaticTileBadge( } ) { val secondaryColor = MaterialTheme.colorScheme.secondary + val size = with(LocalDensity.current) { BadgeIconSize.toDp() } Icon( icon, contentDescription = contentDescription, modifier = - Modifier.size(BadgeSize).align(Alignment.Center).drawBehind { - drawCircle(secondaryColor) + Modifier.size(size).align(Alignment.Center).drawBehind { + drawCircle(secondaryColor, radius = BadgeSize.toPx() / 2) }, ) } @@ -338,6 +339,7 @@ private fun offsetForAngle(angle: Float, radius: Float, center: Offset): Offset private object SelectionDefaults { val SelectedBorderWidth = 2.dp val BadgeSize = 24.dp + val BadgeIconSize = 16.sp val BadgeXOffset = -4.dp val BadgeYOffset = 4.dp val ResizingPillWidth = 8.dp diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/toolbar/Toolbar.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/toolbar/Toolbar.kt index 99f52c28a137..3ae90d2f976b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/toolbar/Toolbar.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/toolbar/Toolbar.kt @@ -16,14 +16,20 @@ package com.android.systemui.qs.panels.ui.compose.toolbar +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.shape.CornerSize +import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp import com.android.systemui.compose.modifiers.sysuiResTag +import com.android.systemui.development.ui.compose.BuildNumber import com.android.systemui.qs.footer.ui.compose.IconButton import com.android.systemui.qs.panels.ui.viewmodel.toolbar.ToolbarViewModel +import com.android.systemui.qs.ui.compose.borderOnFocus @Composable fun Toolbar(viewModel: ToolbarViewModel, modifier: Modifier = Modifier) { @@ -44,7 +50,18 @@ fun Toolbar(viewModel: ToolbarViewModel, modifier: Modifier = Modifier) { Modifier.sysuiResTag("settings_button_container"), ) - Spacer(modifier = Modifier.weight(1f)) + Box(modifier = Modifier.weight(1f), contentAlignment = Alignment.Center) { + BuildNumber( + viewModelFactory = viewModel.buildNumberViewModelFactory, + textColor = MaterialTheme.colorScheme.onSurface, + modifier = + Modifier.borderOnFocus( + color = MaterialTheme.colorScheme.secondary, + cornerSize = CornerSize(1.dp), + ) + .wrapContentSize(), + ) + } IconButton( { viewModel.powerButtonViewModel }, diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModel.kt index 03f0297e0d54..3287443f0405 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModel.kt @@ -16,6 +16,7 @@ package com.android.systemui.qs.panels.ui.viewmodel +import androidx.compose.runtime.Stable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import com.android.systemui.dagger.SysUISingleton @@ -25,6 +26,7 @@ import com.android.systemui.qs.pipeline.shared.TileSpec import javax.inject.Inject @SysUISingleton +@Stable class DetailsViewModel @Inject constructor(val currentTilesInteractor: CurrentTilesInteractor) { /** diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt index 19e542e6a21e..15e71c88bea1 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt @@ -22,12 +22,18 @@ import android.service.quicksettings.Tile import android.text.TextUtils import android.widget.Switch import androidx.compose.runtime.Immutable +import androidx.compose.runtime.Stable import androidx.compose.ui.semantics.Role import androidx.compose.ui.state.ToggleableState import com.android.systemui.plugins.qs.QSTile import com.android.systemui.qs.tileimpl.SubtitleArrayMapping import com.android.systemui.res.R +import java.util.function.Supplier +/** + * Ui State for the tiles. It doesn't contain the icon to be able to invalidate the icon part + * separately. For the icon, use [IconProvider]. + */ @Immutable data class TileUiState( val label: String, @@ -35,7 +41,6 @@ data class TileUiState( val state: Int, val handlesLongClick: Boolean, val handlesSecondaryClick: Boolean, - val icon: QSTile.Icon?, val sideDrawable: Drawable?, val accessibilityUiState: AccessibilityUiState, ) @@ -90,7 +95,6 @@ fun QSTile.State.toUiState(resources: Resources): TileUiState { state = if (disabledByPolicy) Tile.STATE_UNAVAILABLE else state, handlesLongClick = handlesLongClick, handlesSecondaryClick = handlesSecondaryClick, - icon = icon ?: iconSupplier?.get(), sideDrawable = sideViewCustomDrawable, AccessibilityUiState( contentDescription?.toString() ?: "", @@ -104,6 +108,14 @@ fun QSTile.State.toUiState(resources: Resources): TileUiState { ) } +fun QSTile.State.toIconProvider(): IconProvider { + return when { + icon != null -> IconProvider.ConstantIcon(icon) + iconSupplier != null -> IconProvider.IconSupplier(iconSupplier) + else -> IconProvider.Empty + } +} + private fun QSTile.State.getStateText(resources: Resources): CharSequence { val arrayResId = SubtitleArrayMapping.getSubtitleId(spec) val array = resources.getStringArray(arrayResId) @@ -114,3 +126,21 @@ private fun getUnavailableText(spec: String?, resources: Resources): String { val arrayResId = SubtitleArrayMapping.getSubtitleId(spec) return resources.getStringArray(arrayResId)[Tile.STATE_UNAVAILABLE] } + +@Stable +sealed interface IconProvider { + + val icon: QSTile.Icon? + + data class ConstantIcon(override val icon: QSTile.Icon) : IconProvider + + data class IconSupplier(val supplier: Supplier<QSTile.Icon?>) : IconProvider { + override val icon: QSTile.Icon? + get() = supplier.get() + } + + data object Empty : IconProvider { + override val icon: QSTile.Icon? + get() = null + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/ToolbarViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/ToolbarViewModel.kt index e54bfa29d2db..10d7871b8ea2 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/ToolbarViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/ToolbarViewModel.kt @@ -24,6 +24,7 @@ import androidx.compose.runtime.setValue import com.android.systemui.animation.Expandable import com.android.systemui.classifier.domain.interactor.FalsingInteractor import com.android.systemui.classifier.domain.interactor.runIfNotFalseTap +import com.android.systemui.development.ui.viewmodel.BuildNumberViewModel import com.android.systemui.globalactions.GlobalActionsDialogLite import com.android.systemui.lifecycle.ExclusiveActivatable import com.android.systemui.lifecycle.Hydrator @@ -46,6 +47,7 @@ class ToolbarViewModel @AssistedInject constructor( editModeButtonViewModelFactory: EditModeButtonViewModel.Factory, + val buildNumberViewModelFactory: BuildNumberViewModel.Factory, private val footerActionsInteractor: FooterActionsInteractor, private val globalActionsDialogLiteProvider: Provider<GlobalActionsDialogLite>, private val falsingInteractor: FalsingInteractor, diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContent.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContent.kt index 7d396c58630e..8ffba1e5f3dd 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContent.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContent.kt @@ -19,26 +19,14 @@ package com.android.systemui.qs.tiles.dialog import android.view.LayoutInflater import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.viewinterop.AndroidView import com.android.systemui.res.R @Composable fun InternetDetailsContent(viewModel: InternetDetailsViewModel) { val coroutineScope = rememberCoroutineScope() - val context = LocalContext.current - - val internetDetailsContentManager = remember { - viewModel.contentManagerFactory.create( - canConfigMobileData = viewModel.getCanConfigMobileData(), - canConfigWifi = viewModel.getCanConfigWifi(), - coroutineScope = coroutineScope, - context = context, - ) - } AndroidView( modifier = Modifier.fillMaxSize(), @@ -46,11 +34,11 @@ fun InternetDetailsContent(viewModel: InternetDetailsViewModel) { // Inflate with the existing dialog xml layout and bind it with the manager val view = LayoutInflater.from(context).inflate(R.layout.internet_connectivity_dialog, null) - internetDetailsContentManager.bind(view) + viewModel.internetDetailsContentManager.bind(view, coroutineScope) view // TODO: b/377388104 - Polish the internet details view UI }, - onRelease = { internetDetailsContentManager.unBind() }, + onRelease = { viewModel.internetDetailsContentManager.unBind() }, ) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentManager.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentManager.kt index 659488bdd0d3..d8e1755e6cca 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentManager.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentManager.kt @@ -43,6 +43,9 @@ import android.widget.Switch import android.widget.TextView import androidx.annotation.MainThread import androidx.annotation.WorkerThread +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleRegistry @@ -79,8 +82,6 @@ constructor( private val internetDetailsContentController: InternetDetailsContentController, @Assisted(CAN_CONFIG_MOBILE_DATA) private val canConfigMobileData: Boolean, @Assisted(CAN_CONFIG_WIFI) private val canConfigWifi: Boolean, - @Assisted private val coroutineScope: CoroutineScope, - @Assisted private var context: Context, private val uiEventLogger: UiEventLogger, @Main private val handler: Handler, @Background private val backgroundExecutor: Executor, @@ -121,26 +122,29 @@ constructor( private lateinit var shareWifiButton: Button private lateinit var airplaneModeButton: Button private var alertDialog: AlertDialog? = null - - private val canChangeWifiState = - WifiEnterpriseRestrictionUtils.isChangeWifiStateAllowed(context) + private var canChangeWifiState = false private var wifiNetworkHeight = 0 private var backgroundOn: Drawable? = null private var backgroundOff: Drawable? = null private var clickJob: Job? = null private var defaultDataSubId = internetDetailsContentController.defaultDataSubscriptionId - @VisibleForTesting - internal var adapter = InternetAdapter(internetDetailsContentController, coroutineScope) + @VisibleForTesting internal lateinit var adapter: InternetAdapter @VisibleForTesting internal var wifiEntriesCount: Int = 0 @VisibleForTesting internal var hasMoreWifiEntries: Boolean = false + private lateinit var context: Context + private lateinit var coroutineScope: CoroutineScope + + var title by mutableStateOf("") + private set + + var subTitle by mutableStateOf("") + private set @AssistedFactory interface Factory { fun create( @Assisted(CAN_CONFIG_MOBILE_DATA) canConfigMobileData: Boolean, @Assisted(CAN_CONFIG_WIFI) canConfigWifi: Boolean, - coroutineScope: CoroutineScope, - context: Context, ): InternetDetailsContentManager } @@ -152,12 +156,16 @@ constructor( * * @param contentView The view to which the content manager should be bound. */ - fun bind(contentView: View) { + fun bind(contentView: View, coroutineScope: CoroutineScope) { if (DEBUG) { Log.d(TAG, "Bind InternetDetailsContentManager") } this.contentView = contentView + context = contentView.context + this.coroutineScope = coroutineScope + adapter = InternetAdapter(internetDetailsContentController, coroutineScope) + canChangeWifiState = WifiEnterpriseRestrictionUtils.isChangeWifiStateAllowed(context) initializeLifecycle() initializeViews() @@ -323,11 +331,11 @@ constructor( } } - fun getTitleText(): String { + private fun getTitleText(): String { return internetDetailsContentController.getDialogTitleText().toString() } - fun getSubtitleText(): String { + private fun getSubtitleText(): String { return internetDetailsContentController.getSubtitleText(isProgressBarVisible).toString() } @@ -336,6 +344,13 @@ constructor( Log.d(TAG, "updateDetailsUI ") } + if (!::context.isInitialized) { + return + } + + title = getTitleText() + subTitle = getSubtitleText() + airplaneModeButton.visibility = if (internetContent.isAirplaneModeEnabled) View.VISIBLE else View.GONE diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsViewModel.kt index 6709fd2bb508..fb63bea4fb9f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsViewModel.kt @@ -28,38 +28,27 @@ class InternetDetailsViewModel @AssistedInject constructor( private val accessPointController: AccessPointController, - val contentManagerFactory: InternetDetailsContentManager.Factory, + private val contentManagerFactory: InternetDetailsContentManager.Factory, private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler, -) : TileDetailsViewModel() { - override fun clickOnSettingsButton() { - qsTileIntentUserActionHandler.handle( - /* expandable= */ null, - Intent(Settings.ACTION_WIFI_SETTINGS), +) : TileDetailsViewModel { + val internetDetailsContentManager by lazy { + contentManagerFactory.create( + canConfigMobileData = accessPointController.canConfigMobileData(), + canConfigWifi = accessPointController.canConfigWifi(), ) } - override fun getTitle(): String { - // TODO: b/377388104 make title and sub title mutable states of string - // by internetDetailsContentManager.getTitleText() - // TODO: test title change between airplane mode and not airplane mode - // TODO: b/377388104 Update the placeholder text - return "Internet" - } + override val title: String + get() = internetDetailsContentManager.title - override fun getSubTitle(): String { - // TODO: b/377388104 make title and sub title mutable states of string - // by internetDetailsContentManager.getSubtitleText() - // TODO: test subtitle change between airplane mode and not airplane mode - // TODO: b/377388104 Update the placeholder text - return "Tab a network to connect" - } + override val subTitle: String + get() = internetDetailsContentManager.subTitle - fun getCanConfigMobileData(): Boolean { - return accessPointController.canConfigMobileData() - } - - fun getCanConfigWifi(): Boolean { - return accessPointController.canConfigWifi() + override fun clickOnSettingsButton() { + qsTileIntentUserActionHandler.handle( + /* expandable= */ null, + Intent(Settings.ACTION_WIFI_SETTINGS), + ) } @AssistedFactory diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/ModesDetailsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/ModesDetailsViewModel.kt index 9a39c3c095ef..4f7e03bd3bc3 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/ModesDetailsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/ModesDetailsViewModel.kt @@ -23,18 +23,14 @@ import com.android.systemui.statusbar.policy.ui.dialog.viewmodel.ModesDialogView class ModesDetailsViewModel( private val onSettingsClick: () -> Unit, val viewModel: ModesDialogViewModel, -) : TileDetailsViewModel() { +) : TileDetailsViewModel { override fun clickOnSettingsButton() { onSettingsClick() } - override fun getTitle(): String { - // TODO(b/388321032): Replace this string with a string in a translatable xml file. - return "Modes" - } + // TODO(b/388321032): Replace this string with a string in a translatable xml file. + override val title = "Modes" - override fun getSubTitle(): String { - // TODO(b/388321032): Replace this string with a string in a translatable xml file. - return "Silences interruptions from people and apps in different circumstances" - } + // TODO(b/388321032): Replace this string with a string in a translatable xml file. + override val subTitle = "Silences interruptions from people and apps in different circumstances" } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/ScreenRecordDetailsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/ScreenRecordDetailsViewModel.kt index c84ddb6fdb36..59f209edb546 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/ScreenRecordDetailsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/ScreenRecordDetailsViewModel.kt @@ -23,19 +23,15 @@ import com.android.systemui.screenrecord.RecordingController class ScreenRecordDetailsViewModel( val recordingController: RecordingController, val onStartRecordingClicked: Runnable, -) : TileDetailsViewModel() { +) : TileDetailsViewModel { override fun clickOnSettingsButton() { // No settings button in this tile. } - override fun getTitle(): String { - // TODO(b/388321032): Replace this string with a string in a translatable xml file, - return "Screen recording" - } + // TODO(b/388321032): Replace this string with a string in a translatable xml file, + override val title = "Screen recording" - override fun getSubTitle(): String { - // No sub-title in this tile. - return "" - } + // No sub-title in this tile. + override val subTitle = "" } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/interactor/HearingDevicesTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/interactor/HearingDevicesTileUserActionInteractor.kt index 5e7172ee3ba7..268efcef9062 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/interactor/HearingDevicesTileUserActionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/interactor/HearingDevicesTileUserActionInteractor.kt @@ -21,6 +21,7 @@ import android.provider.Settings import com.android.systemui.accessibility.hearingaid.HearingDevicesDialogManager import com.android.systemui.accessibility.hearingaid.HearingDevicesUiEventLogger.Companion.LAUNCH_SOURCE_QS_TILE import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.qs.shared.QSSettingsPackageRepository import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler import com.android.systemui.qs.tiles.base.interactor.QSTileInput import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor @@ -37,6 +38,7 @@ constructor( @Main private val mainContext: CoroutineContext, private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler, private val hearingDevicesDialogManager: HearingDevicesDialogManager, + private val settingsPackageRepository: QSSettingsPackageRepository, ) : QSTileUserActionInteractor<HearingDevicesTileModel> { override suspend fun handleInput(input: QSTileInput<HearingDevicesTileModel>) = @@ -53,7 +55,8 @@ constructor( is QSTileUserAction.LongClick -> { qsTileIntentUserActionHandler.handle( action.expandable, - Intent(Settings.ACTION_HEARING_DEVICES_SETTINGS), + Intent(Settings.ACTION_HEARING_DEVICES_SETTINGS) + .setPackage(settingsPackageRepository.getSettingsPackageName()), ) } is QSTileUserAction.ToggleClick -> {} diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt index 4753b9ac0457..7bb831baec20 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt @@ -687,7 +687,7 @@ constructor( if (!isDeviceEntered) { coroutineScope { launch { - deviceEntryHapticsInteractor.playSuccessHaptic + deviceEntryHapticsInteractor.playSuccessHapticOnDeviceEntry .sample(sceneInteractor.currentScene) .collect { currentScene -> if (Flags.msdlFeedback()) { diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java index eae0ba66925d..ade62a9b63d3 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java @@ -164,16 +164,17 @@ public class BrightnessDialog extends Activity { container.setVisibility(View.VISIBLE); ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) container.getLayoutParams(); - int horizontalMargin = - getResources().getDimensionPixelSize(R.dimen.notification_side_paddings); - lp.leftMargin = horizontalMargin; - lp.rightMargin = horizontalMargin; - - int verticalMargin = - getResources().getDimensionPixelSize( - R.dimen.notification_guts_option_vertical_padding); - - lp.topMargin = verticalMargin; + // Remove the margin. Have the container take all the space. Instead, insert padding. + // This allows for the background to be visible around the slider. + int margin = 0; + lp.topMargin = margin; + lp.bottomMargin = margin; + lp.leftMargin = margin; + lp.rightMargin = margin; + int padding = getResources().getDimensionPixelSize( + R.dimen.rounded_slider_background_padding + ); + container.setPadding(padding, padding, padding, padding); // If in multi-window or freeform, increase the top margin so the brightness dialog // doesn't get cut off. final int windowingMode = configuration.windowConfiguration.getWindowingMode(); @@ -182,17 +183,15 @@ public class BrightnessDialog extends Activity { lp.topMargin += 50; } - lp.bottomMargin = verticalMargin; - int orientation = configuration.orientation; int windowWidth = getWindowAvailableWidth(); if (orientation == Configuration.ORIENTATION_LANDSCAPE) { boolean shouldBeFullWidth = getIntent() .getBooleanExtra(EXTRA_BRIGHTNESS_DIALOG_IS_FULL_WIDTH, false); - lp.width = (shouldBeFullWidth ? windowWidth : windowWidth / 2) - horizontalMargin * 2; + lp.width = (shouldBeFullWidth ? windowWidth : windowWidth / 2) - margin * 2; } else if (orientation == Configuration.ORIENTATION_PORTRAIT) { - lp.width = windowWidth - horizontalMargin * 2; + lp.width = windowWidth - margin * 2; } container.setLayoutParams(lp); @@ -202,7 +201,7 @@ public class BrightnessDialog extends Activity { // Exclude this view (and its horizontal margins) from triggering gestures. // This prevents back gesture from being triggered by dragging close to the // edge of the slider (0% or 100%). - bounds.set(-horizontalMargin, 0, right - left + horizontalMargin, bottom - top); + bounds.set(-margin, 0, right - left + margin, bottom - top); v.setSystemGestureExclusionRects(List.of(bounds)); }); } diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/ComposeDialogComposableProvider.kt b/packages/SystemUI/src/com/android/systemui/settings/brightness/ComposeDialogComposableProvider.kt index 9e20055de856..962a3bd2376b 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/ComposeDialogComposableProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/ComposeDialogComposableProvider.kt @@ -17,12 +17,15 @@ package com.android.systemui.settings.brightness import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.ViewCompositionStrategy +import androidx.compose.ui.unit.dp import com.android.compose.theme.PlatformTheme import com.android.systemui.brightness.ui.compose.BrightnessSliderContainer +import com.android.systemui.brightness.ui.compose.ContainerColors import com.android.systemui.brightness.ui.viewmodel.BrightnessSliderViewModel import com.android.systemui.lifecycle.rememberViewModel @@ -44,7 +47,11 @@ private fun BrightnessSliderForDialog( rememberViewModel(traceName = "BrightnessDialog.viewModel") { brightnessSliderViewModelFactory.create(false) } - BrightnessSliderContainer(viewModel = viewModel, Modifier.fillMaxWidth()) + BrightnessSliderContainer( + viewModel = viewModel, + containerColors = ContainerColors.singleColor(ContainerColors.defaultContainerColor), + modifier = Modifier.fillMaxWidth().padding(8.dp), + ) } class ComposableProvider( diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt index 3be2f1b7b957..362b5db012e1 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt @@ -17,6 +17,7 @@ package com.android.systemui.shade import android.content.Context +import android.content.res.Configuration import android.graphics.Rect import android.os.PowerManager import android.os.SystemClock @@ -25,11 +26,13 @@ import android.view.GestureDetector import android.view.MotionEvent import android.view.View import android.view.ViewGroup +import android.view.WindowInsets import android.widget.FrameLayout import androidx.activity.OnBackPressedDispatcher import androidx.activity.OnBackPressedDispatcherOwner import androidx.activity.setViewTreeOnBackPressedDispatcherOwner import androidx.compose.ui.platform.ComposeView +import androidx.core.view.updateMargins import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.LifecycleObserver @@ -101,7 +104,10 @@ constructor( ) : LifecycleOwner { private val logger = Logger(logBuffer, TAG) - private class CommunalWrapper(context: Context) : FrameLayout(context) { + private class CommunalWrapper( + context: Context, + private val communalSettingsInteractor: CommunalSettingsInteractor, + ) : FrameLayout(context) { private val consumers: MutableSet<Consumer<Boolean>> = ArraySet() override fun requestDisallowInterceptTouchEvent(disallowIntercept: Boolean) { @@ -121,6 +127,24 @@ constructor( consumers.clear() } } + + override fun onApplyWindowInsets(windowInsets: WindowInsets): WindowInsets { + if ( + !communalSettingsInteractor.isV2FlagEnabled() || + resources.configuration.orientation != Configuration.ORIENTATION_LANDSCAPE + ) { + return super.onApplyWindowInsets(windowInsets) + } + val type = WindowInsets.Type.displayCutout() + val insets = windowInsets.getInsets(type) + + // Reset horizontal margins added by window insets, so hub can be edge to edge. + if (insets.left > 0 || insets.right > 0) { + val lp = layoutParams as LayoutParams + lp.updateMargins(0, lp.topMargin, 0, lp.bottomMargin) + } + return WindowInsets.CONSUMED + } } /** The container view for the hub. This will not be initialized until [initView] is called. */ @@ -443,7 +467,8 @@ constructor( collectFlow(containerView, keyguardInteractor.isDreaming, { isDreaming = it }) collectFlow(containerView, communalViewModel.swipeToHubEnabled, { swipeToHubEnabled = it }) - communalContainerWrapper = CommunalWrapper(containerView.context) + communalContainerWrapper = + CommunalWrapper(containerView.context, communalSettingsInteractor) communalContainerWrapper?.addView(communalContainerView) logger.d("Hub container initialized") return communalContainerWrapper!! diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java index 305444f7ab5e..dafb1a559d59 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java @@ -30,8 +30,6 @@ import android.graphics.Region; import android.os.IBinder; import android.os.RemoteException; import android.os.Trace; -import android.os.UserHandle; -import android.provider.Settings; import android.util.Log; import android.view.Display; import android.view.IWindow; @@ -75,7 +73,6 @@ import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.user.domain.interactor.SelectedUserInteractor; -import com.android.systemui.util.settings.SecureSettings; import dagger.Lazy; @@ -134,7 +131,6 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW private final SysuiColorExtractor mColorExtractor; private final NotificationShadeWindowModel mNotificationShadeWindowModel; - private final SecureSettings mSecureSettings; /** * Layout params would be aggregated and dispatched all at once if this is > 0. * @@ -168,7 +164,6 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW Lazy<SelectedUserInteractor> userInteractor, UserTracker userTracker, NotificationShadeWindowModel notificationShadeWindowModel, - SecureSettings secureSettings, Lazy<CommunalInteractor> communalInteractor, @ShadeDisplayAware LayoutParams shadeWindowLayoutParams) { mContext = context; @@ -186,7 +181,6 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW mBackgroundExecutor = backgroundExecutor; mColorExtractor = colorExtractor; mNotificationShadeWindowModel = notificationShadeWindowModel; - mSecureSettings = secureSettings; // prefix with {slow} to make sure this dumps at the END of the critical section. dumpManager.registerCriticalDumpable("{slow}NotificationShadeWindowControllerImpl", this); mAuthController = authController; @@ -424,7 +418,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW (long) mLpChanged.preferredMaxDisplayRefreshRate); } - if (state.bouncerShowing && !isSecureWindowsDisabled()) { + if (state.bouncerShowing) { mLpChanged.flags |= LayoutParams.FLAG_SECURE; } else { mLpChanged.flags &= ~LayoutParams.FLAG_SECURE; @@ -437,13 +431,6 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW } } - private boolean isSecureWindowsDisabled() { - return mSecureSettings.getIntForUser( - Settings.Secure.DISABLE_SECURE_WINDOWS, - 0, - UserHandle.USER_CURRENT) == 1; - } - private void adjustScreenOrientation(NotificationShadeWindowState state) { if (state.bouncerShowing || state.isKeyguardShowingAndNotOccluded() || state.dozing) { if (mKeyguardStateController.isKeyguardScreenRotationAllowed()) { @@ -511,17 +498,18 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW } private boolean isExpanded(NotificationShadeWindowState state) { + boolean visForBlur = !Flags.disableShadeVisibleWithBlur() && state.backgroundBlurRadius > 0; boolean isExpanded = !state.forceWindowCollapsed && (state.isKeyguardShowingAndNotOccluded() || state.panelVisible || state.keyguardFadingAway || state.bouncerShowing || state.headsUpNotificationShowing || state.scrimsVisibility != ScrimController.TRANSPARENT) - || state.backgroundBlurRadius > 0 + || visForBlur || state.launchingActivityFromNotification; mLogger.logIsExpanded(isExpanded, state.forceWindowCollapsed, state.isKeyguardShowingAndNotOccluded(), state.panelVisible, state.keyguardFadingAway, state.bouncerShowing, state.headsUpNotificationShowing, state.scrimsVisibility != ScrimController.TRANSPARENT, - state.backgroundBlurRadius > 0, state.launchingActivityFromNotification); + visForBlur, state.launchingActivityFromNotification); return isExpanded; } diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt index 20b44d73e097..5609326362fc 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt @@ -26,6 +26,7 @@ import androidx.compose.material3.ColorScheme import androidx.compose.runtime.getValue import androidx.compose.ui.graphics.Color import com.android.app.tracing.coroutines.launchTraced as launch +import com.android.compose.animation.scene.OverlayKey import com.android.systemui.battery.BatteryMeterViewController import com.android.systemui.lifecycle.ExclusiveActivatable import com.android.systemui.lifecycle.Hydrator @@ -86,6 +87,22 @@ constructor( (ViewGroup, StatusBarLocation) -> BatteryMeterViewController = batteryMeterViewControllerFactory::create + val showClock: Boolean by + hydrator.hydratedStateOf( + traceName = "showClock", + initialValue = + shouldShowClock( + isShadeLayoutWide = shadeInteractor.isShadeLayoutWide.value, + overlays = sceneInteractor.currentOverlays.value, + ), + source = + combine( + shadeInteractor.isShadeLayoutWide, + sceneInteractor.currentOverlays, + ::shouldShowClock, + ), + ) + val notificationsChipHighlight: HeaderChipHighlight by hydrator.hydratedStateOf( traceName = "notificationsChipHighlight", @@ -114,13 +131,6 @@ constructor( }, ) - val isShadeLayoutWide: Boolean by - hydrator.hydratedStateOf( - traceName = "isShadeLayoutWide", - initialValue = shadeInteractor.isShadeLayoutWide.value, - source = shadeInteractor.isShadeLayoutWide, - ) - /** True if there is exactly one mobile connection. */ val isSingleCarrier: StateFlow<Boolean> = mobileIconsInteractor.isSingleCarrier @@ -271,6 +281,11 @@ constructor( } } + private fun shouldShowClock(isShadeLayoutWide: Boolean, overlays: Set<OverlayKey>): Boolean { + // Notifications shade on narrow layout renders its own clock. Hide the header clock. + return isShadeLayoutWide || Overlays.NotificationsShade !in overlays + } + private fun getFormatFromPattern(pattern: String?): DateFormat { val format = DateFormat.getInstanceForSkeleton(pattern, Locale.getDefault()) format.setContext(DisplayContext.CAPITALIZATION_FOR_STANDALONE) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java index c2e355d07e9c..03c191e40ccf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java @@ -22,6 +22,7 @@ import android.app.Flags; import android.app.Notification; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; +import android.service.notification.StatusBarNotification; import android.text.TextUtils; import android.util.DisplayMetrics; import android.util.TypedValue; @@ -31,6 +32,8 @@ import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; +import androidx.annotation.VisibleForTesting; + import com.android.internal.R; import com.android.internal.widget.CachingIconView; import com.android.internal.widget.ConversationLayout; @@ -39,6 +42,7 @@ import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.NotificationContentView; import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation; import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper; +import com.android.systemui.statusbar.notification.shared.NotificationBundleUi; import java.util.ArrayList; import java.util.HashSet; @@ -214,7 +218,7 @@ public class NotificationGroupingUtil { } // in case no view is visible we make sure the time is visible int timeVisibility = !hasVisibleText - || row.getEntry().getSbn().getNotification().showsTime() + || showsTime(row) ? View.VISIBLE : View.GONE; time.setVisibility(timeVisibility); View left = null; @@ -243,6 +247,17 @@ public class NotificationGroupingUtil { } } + @VisibleForTesting + boolean showsTime(ExpandableNotificationRow row) { + StatusBarNotification sbn; + if (NotificationBundleUi.isEnabled()) { + sbn = row.getEntryAdapter() != null ? row.getEntryAdapter().getSbn() : null; + } else { + sbn = row.getEntry().getSbn(); + } + return (sbn != null && sbn.getNotification().showsTime()); + } + /** * Reset the modifications to this row for removing it from the group. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java index 0d34bdc7e477..041ed6504634 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java @@ -59,11 +59,13 @@ import com.android.systemui.shade.domain.interactor.ShadeInteractor; import com.android.systemui.statusbar.dagger.CentralSurfacesDependenciesModule; import com.android.systemui.statusbar.notification.NotifPipelineFlags; import com.android.systemui.statusbar.notification.RemoteInputControllerLogger; +import com.android.systemui.statusbar.notification.collection.EntryAdapter; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntry.EditedSuggestionInfo; import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.shared.NotificationBundleUi; import com.android.systemui.statusbar.phone.ExpandHeadsUpOnInlineReply; import com.android.systemui.statusbar.policy.RemoteInputUriController; import com.android.systemui.statusbar.policy.RemoteInputView; @@ -134,20 +136,21 @@ public class NotificationRemoteInputManager implements CoreStartable { view.getTag(com.android.internal.R.id.notification_action_index_tag); final ExpandableNotificationRow row = getNotificationRowForParent(view.getParent()); - final NotificationEntry entry = getNotificationForParent(view.getParent()); - mLogger.logInitialClick( - row != null ? row.getLoggingKey() : null, actionIndex, pendingIntent); + if (row == null) { + return false; + } + mLogger.logInitialClick(row.getLoggingKey(), actionIndex, pendingIntent); if (handleRemoteInput(view, pendingIntent)) { - mLogger.logRemoteInputWasHandled( - row != null ? row.getLoggingKey() : null, actionIndex); + mLogger.logRemoteInputWasHandled(row.getLoggingKey(), actionIndex); return true; } if (DEBUG) { Log.v(TAG, "Notification click handler invoked for intent: " + pendingIntent); } - logActionClick(view, entry, pendingIntent); + Notification.Action action = getActionFromView(view, row, pendingIntent); + logActionClick(view, row.getKey(), action); // The intent we are sending is for the application, which // won't have permission to immediately start an activity after // the user switches to home. We know it is safe to do at this @@ -156,33 +159,47 @@ public class NotificationRemoteInputManager implements CoreStartable { ActivityManager.getService().resumeAppSwitches(); } catch (RemoteException e) { } - Notification.Action action = getActionFromView(view, entry, pendingIntent); return mCallback.handleRemoteViewClick(view, pendingIntent, action == null ? false : action.isAuthenticationRequired(), actionIndex, () -> { Pair<Intent, ActivityOptions> options = response.getLaunchOptions(view); mLogger.logStartingIntentWithDefaultHandler( - row != null ? row.getLoggingKey() : null, pendingIntent, actionIndex); + row.getLoggingKey(), pendingIntent, actionIndex); boolean started = RemoteViews.startPendingIntent(view, pendingIntent, options); - if (started) releaseNotificationIfKeptForRemoteInputHistory(entry); + if (started) { + if (NotificationBundleUi.isEnabled()) { + releaseNotificationIfKeptForRemoteInputHistory(row.getEntryAdapter()); + } else { + releaseNotificationIfKeptForRemoteInputHistory(row.getEntry()); + } + } return started; }); } private @Nullable Notification.Action getActionFromView(View view, - NotificationEntry entry, PendingIntent actionIntent) { + ExpandableNotificationRow row, PendingIntent actionIntent) { Integer actionIndex = (Integer) view.getTag(com.android.internal.R.id.notification_action_index_tag); if (actionIndex == null) { return null; } - if (entry == null) { + StatusBarNotification statusBarNotification = null; + if (NotificationBundleUi.isEnabled()) { + if (row.getEntryAdapter() != null) { + statusBarNotification = row.getEntryAdapter().getSbn(); + } + } else { + if (row.getEntry() != null) { + statusBarNotification = row.getEntry().getSbn(); + } + } + if (statusBarNotification == null) { Log.w(TAG, "Couldn't determine notification for click."); return null; } // Notification may be updated before this function is executed, and thus play safe // here and verify that the action object is still the one that where the click happens. - StatusBarNotification statusBarNotification = entry.getSbn(); Notification.Action[] actions = statusBarNotification.getNotification().actions; if (actions == null || actionIndex >= actions.length) { Log.w(TAG, "statusBarNotification.getNotification().actions is null or invalid"); @@ -199,14 +216,12 @@ public class NotificationRemoteInputManager implements CoreStartable { private void logActionClick( View view, - NotificationEntry entry, - PendingIntent actionIntent) { - Notification.Action action = getActionFromView(view, entry, actionIntent); + String key, + Notification.Action action) { if (action == null) { return; } ViewParent parent = view.getParent(); - String key = entry.getSbn().getKey(); int buttonIndex = -1; // If this is a default template, determine the index of the button. if (view.getId() == com.android.internal.R.id.action0 && @@ -214,20 +229,10 @@ public class NotificationRemoteInputManager implements CoreStartable { ViewGroup actionGroup = (ViewGroup) parent; buttonIndex = actionGroup.indexOfChild(view); } - final NotificationVisibility nv = mVisibilityProvider.obtain(entry, true); + final NotificationVisibility nv = mVisibilityProvider.obtain(key, true); mClickNotifier.onNotificationActionClick(key, buttonIndex, action, nv, false); } - private NotificationEntry getNotificationForParent(ViewParent parent) { - while (parent != null) { - if (parent instanceof ExpandableNotificationRow) { - return ((ExpandableNotificationRow) parent).getEntry(); - } - parent = parent.getParent(); - } - return null; - } - private @Nullable ExpandableNotificationRow getNotificationRowForParent(ViewParent parent) { while (parent != null) { if (parent instanceof ExpandableNotificationRow) { @@ -394,11 +399,21 @@ public class NotificationRemoteInputManager implements CoreStartable { } } + /** + * Use {@link com.android.systemui.statusbar.notification.row.NotificationActionClickManager} + * instead + */ public void addActionPressListener(Consumer<NotificationEntry> listener) { + NotificationBundleUi.assertInLegacyMode(); mActionPressListeners.addIfAbsent(listener); } + /** + * Use {@link com.android.systemui.statusbar.notification.row.NotificationActionClickManager} + * instead + */ public void removeActionPressListener(Consumer<NotificationEntry> listener) { + NotificationBundleUi.assertInLegacyMode(); mActionPressListeners.remove(listener); } @@ -681,12 +696,30 @@ public class NotificationRemoteInputManager implements CoreStartable { * (after unlock, if applicable), and will then wait a short time to allow the app to update the * notification in response to the action. */ + private void releaseNotificationIfKeptForRemoteInputHistory(EntryAdapter entryAdapter) { + if (entryAdapter == null) { + return; + } + if (mRemoteInputListener != null) { + mRemoteInputListener.releaseNotificationIfKeptForRemoteInputHistory( + entryAdapter.getKey()); + } + entryAdapter.onNotificationActionClicked(); + } + + /** + * Checks if the notification is being kept due to the user sending an inline reply, and if + * so, releases that hold. This is called anytime an action on the notification is dispatched + * (after unlock, if applicable), and will then wait a short time to allow the app to update the + * notification in response to the action. + */ private void releaseNotificationIfKeptForRemoteInputHistory(NotificationEntry entry) { + NotificationBundleUi.assertInLegacyMode(); if (entry == null) { return; } if (mRemoteInputListener != null) { - mRemoteInputListener.releaseNotificationIfKeptForRemoteInputHistory(entry); + mRemoteInputListener.releaseNotificationIfKeptForRemoteInputHistory(entry.getKey()); } for (Consumer<NotificationEntry> listener : mActionPressListeners) { listener.accept(entry); @@ -866,7 +899,7 @@ public class NotificationRemoteInputManager implements CoreStartable { boolean isNotificationKeptForRemoteInputHistory(@NonNull String key); /** Called on user interaction to end lifetime extension for history */ - void releaseNotificationIfKeptForRemoteInputHistory(@NonNull NotificationEntry entry); + void releaseNotificationIfKeptForRemoteInputHistory(@NonNull String entryKey); /** Called when the RemoteInputController is attached to the manager */ void setRemoteInputController(@NonNull RemoteInputController remoteInputController); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/StatusBarChipsReturnAnimations.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/StatusBarChipsReturnAnimations.kt new file mode 100644 index 000000000000..176419454c21 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/StatusBarChipsReturnAnimations.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.chips + +import com.android.systemui.Flags +import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization + +/** Helper for reading or using the status_bar_chips_return_animations flag state. */ +object StatusBarChipsReturnAnimations { + /** The aconfig flag name */ + const val FLAG_NAME = Flags.FLAG_STATUS_BAR_CHIPS_RETURN_ANIMATIONS + + /** Is the feature enabled. */ + @JvmStatic + inline val isEnabled + get() = StatusBarChipsModernization.isEnabled && Flags.statusBarChipsReturnAnimations() +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt index be3afad4e1d1..7e7031200988 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.chips.call.ui.viewmodel +import android.app.PendingIntent import android.content.Context import android.view.View import com.android.internal.jank.Cuj @@ -31,6 +32,7 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.res.R import com.android.systemui.statusbar.chips.StatusBarChipLogTags.pad import com.android.systemui.statusbar.chips.StatusBarChipsLog +import com.android.systemui.statusbar.chips.StatusBarChipsReturnAnimations import com.android.systemui.statusbar.chips.call.domain.interactor.CallChipInteractor import com.android.systemui.statusbar.chips.ui.model.ColorsModel import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel @@ -42,8 +44,10 @@ import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCall import com.android.systemui.util.time.SystemClock import javax.inject.Inject import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn @@ -59,67 +63,110 @@ constructor( private val activityStarter: ActivityStarter, @StatusBarChipsLog private val logger: LogBuffer, ) : OngoingActivityChipViewModel { - override val chip: StateFlow<OngoingActivityChipModel> = - interactor.ongoingCallState - .map { state -> - when (state) { - is OngoingCallModel.NoCall, - is OngoingCallModel.InCallWithVisibleApp -> OngoingActivityChipModel.Inactive() - is OngoingCallModel.InCall -> { - val key = state.notificationKey - val contentDescription = getContentDescription(state.appName) - val icon = - if (state.notificationIconView != null) { - StatusBarConnectedDisplays.assertInLegacyMode() - OngoingActivityChipModel.ChipIcon.StatusBarView( - state.notificationIconView, - contentDescription, - ) - } else if (StatusBarConnectedDisplays.isEnabled) { - OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon( - state.notificationKey, - contentDescription, - ) + private val chipWithReturnAnimation: StateFlow<OngoingActivityChipModel> = + if (StatusBarChipsReturnAnimations.isEnabled) { + interactor.ongoingCallState + .map { state -> + when (state) { + is OngoingCallModel.NoCall -> OngoingActivityChipModel.Inactive() + is OngoingCallModel.InCall -> + prepareChip(state, systemClock, isHidden = state.isAppVisible) + } + } + .stateIn( + scope, + SharingStarted.WhileSubscribed(), + OngoingActivityChipModel.Inactive(), + ) + } else { + MutableStateFlow(OngoingActivityChipModel.Inactive()).asStateFlow() + } + + private val chipLegacy: StateFlow<OngoingActivityChipModel> = + if (!StatusBarChipsReturnAnimations.isEnabled) { + interactor.ongoingCallState + .map { state -> + when (state) { + is OngoingCallModel.NoCall -> OngoingActivityChipModel.Inactive() + is OngoingCallModel.InCall -> + if (state.isAppVisible) { + OngoingActivityChipModel.Inactive() } else { - OngoingActivityChipModel.ChipIcon.SingleColorIcon(phoneIcon) + prepareChip(state, systemClock, isHidden = false) } - - val colors = ColorsModel.AccentThemed - - // This block mimics OngoingCallController#updateChip. - if (state.startTimeMs <= 0L) { - // If the start time is invalid, don't show a timer and show just an - // icon. See b/192379214. - OngoingActivityChipModel.Active.IconOnly( - key = key, - icon = icon, - colors = colors, - onClickListenerLegacy = getOnClickListener(state), - clickBehavior = getClickBehavior(state), - ) - } else { - val startTimeInElapsedRealtime = - state.startTimeMs - systemClock.currentTimeMillis() + - systemClock.elapsedRealtime() - OngoingActivityChipModel.Active.Timer( - key = key, - icon = icon, - colors = colors, - startTimeMs = startTimeInElapsedRealtime, - onClickListenerLegacy = getOnClickListener(state), - clickBehavior = getClickBehavior(state), - ) - } } } + .stateIn( + scope, + SharingStarted.WhileSubscribed(), + OngoingActivityChipModel.Inactive(), + ) + } else { + MutableStateFlow(OngoingActivityChipModel.Inactive()).asStateFlow() + } + + override val chip: StateFlow<OngoingActivityChipModel> = + if (StatusBarChipsReturnAnimations.isEnabled) { + chipWithReturnAnimation + } else { + chipLegacy + } + + /** Builds an [OngoingActivityChipModel.Active] from all the relevant information. */ + private fun prepareChip( + state: OngoingCallModel.InCall, + systemClock: SystemClock, + isHidden: Boolean, + ): OngoingActivityChipModel.Active { + val key = state.notificationKey + val contentDescription = getContentDescription(state.appName) + val icon = + if (state.notificationIconView != null) { + StatusBarConnectedDisplays.assertInLegacyMode() + OngoingActivityChipModel.ChipIcon.StatusBarView( + state.notificationIconView, + contentDescription, + ) + } else if (StatusBarConnectedDisplays.isEnabled) { + OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon( + state.notificationKey, + contentDescription, + ) + } else { + OngoingActivityChipModel.ChipIcon.SingleColorIcon(phoneIcon) } - .stateIn(scope, SharingStarted.WhileSubscribed(), OngoingActivityChipModel.Inactive()) - private fun getOnClickListener(state: OngoingCallModel.InCall): View.OnClickListener? { - if (state.intent == null) { - return null + val colors = ColorsModel.AccentThemed + + // This block mimics OngoingCallController#updateChip. + if (state.startTimeMs <= 0L) { + // If the start time is invalid, don't show a timer and show just an icon. + // See b/192379214. + return OngoingActivityChipModel.Active.IconOnly( + key = key, + icon = icon, + colors = colors, + onClickListenerLegacy = getOnClickListener(state.intent), + clickBehavior = getClickBehavior(state.intent), + isHidden = isHidden, + ) + } else { + val startTimeInElapsedRealtime = + state.startTimeMs - systemClock.currentTimeMillis() + systemClock.elapsedRealtime() + return OngoingActivityChipModel.Active.Timer( + key = key, + icon = icon, + colors = colors, + startTimeMs = startTimeInElapsedRealtime, + onClickListenerLegacy = getOnClickListener(state.intent), + clickBehavior = getClickBehavior(state.intent), + isHidden = isHidden, + ) } + } + private fun getOnClickListener(intent: PendingIntent?): View.OnClickListener? { + if (intent == null) return null return View.OnClickListener { view -> StatusBarChipsModernization.assertInLegacyMode() logger.log(TAG, LogLevel.INFO, {}, { "Chip clicked" }) @@ -127,7 +174,7 @@ constructor( view.requireViewById<ChipBackgroundContainer>(R.id.ongoing_activity_chip_background) // This mimics OngoingCallController#updateChipClickListener. activityStarter.postStartActivityDismissingKeyguard( - state.intent, + intent, ActivityTransitionAnimator.Controller.fromView( backgroundView, Cuj.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP, @@ -136,10 +183,8 @@ constructor( } } - private fun getClickBehavior( - state: OngoingCallModel.InCall - ): OngoingActivityChipModel.ClickBehavior = - if (state.intent == null) { + private fun getClickBehavior(intent: PendingIntent?): OngoingActivityChipModel.ClickBehavior = + if (intent == null) { OngoingActivityChipModel.ClickBehavior.None } else { OngoingActivityChipModel.ClickBehavior.ExpandAction( @@ -149,10 +194,7 @@ constructor( expandable.activityTransitionController( Cuj.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP ) - activityStarter.postStartActivityDismissingKeyguard( - state.intent, - animationController, - ) + activityStarter.postStartActivityDismissingKeyguard(intent, animationController) } ) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt index 02e4ba4d6437..6f8552738d33 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.chips.ui.binder import android.annotation.IdRes import android.content.Context import android.content.res.ColorStateList +import android.graphics.Typeface import android.graphics.drawable.GradientDrawable import android.view.View import android.view.ViewGroup @@ -27,6 +28,7 @@ import android.widget.FrameLayout import android.widget.ImageView import android.widget.TextView import androidx.annotation.UiThread +import com.android.systemui.FontStyles import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.ui.binder.ContentDescriptionViewBinder import com.android.systemui.common.ui.binder.IconViewBinder @@ -170,6 +172,16 @@ object OngoingActivityChipBinder { forceLayout() } + /** Updates the typefaces for any text shown in the chip. */ + fun updateTypefaces(binding: OngoingActivityChipViewBinding) { + binding.timeView.typeface = + Typeface.create(FontStyles.GSF_LABEL_LARGE_EMPHASIZED, Typeface.NORMAL) + binding.textView.typeface = + Typeface.create(FontStyles.GSF_LABEL_LARGE_EMPHASIZED, Typeface.NORMAL) + binding.shortTimeDeltaView.typeface = + Typeface.create(FontStyles.GSF_LABEL_LARGE_EMPHASIZED, Typeface.NORMAL) + } + private fun setChipIcon( chipModel: OngoingActivityChipModel.Active, backgroundView: ChipBackgroundContainer, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChipContent.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChipContent.kt index 5242feac898b..55d753662a65 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChipContent.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChipContent.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.chips.ui.compose +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -27,11 +28,13 @@ import androidx.compose.ui.layout.MeasureScope import androidx.compose.ui.node.LayoutModifierNode import androidx.compose.ui.node.ModifierNodeElement import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.text.TextMeasurer import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.rememberTextMeasurer import androidx.compose.ui.unit.Constraints +import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.constrain import androidx.compose.ui.unit.dp @@ -42,14 +45,16 @@ import com.android.systemui.statusbar.chips.ui.viewmodel.rememberChronometerStat import com.android.systemui.statusbar.chips.ui.viewmodel.rememberTimeRemainingState import kotlin.math.min +@OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable fun ChipContent(viewModel: OngoingActivityChipModel.Active, modifier: Modifier = Modifier) { val context = LocalContext.current + val density = LocalDensity.current val isTextOnly = viewModel.icon == null val hasEmbeddedIcon = viewModel.icon is OngoingActivityChipModel.ChipIcon.StatusBarView || viewModel.icon is OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon - val textStyle = MaterialTheme.typography.labelLarge + val textStyle = MaterialTheme.typography.labelLargeEmphasized val textColor = Color(viewModel.colors.text(context)) val maxTextWidth = dimensionResource(id = R.dimen.ongoing_activity_chip_max_text_width) val startPadding = @@ -86,7 +91,7 @@ fun ChipContent(viewModel: OngoingActivityChipModel.Active, modifier: Modifier = startPadding = startPadding, endPadding = endPadding, ) - .neverDecreaseWidth(), + .neverDecreaseWidth(density), ) } @@ -97,7 +102,7 @@ fun ChipContent(viewModel: OngoingActivityChipModel.Active, modifier: Modifier = style = textStyle, color = textColor, softWrap = false, - modifier = modifier.neverDecreaseWidth(), + modifier = modifier.neverDecreaseWidth(density), ) } @@ -150,23 +155,31 @@ fun ChipContent(viewModel: OngoingActivityChipModel.Active, modifier: Modifier = } /** A modifier that ensures the width of the content only increases and never decreases. */ -private fun Modifier.neverDecreaseWidth(): Modifier { - return this.then(NeverDecreaseWidthElement) +private fun Modifier.neverDecreaseWidth(density: Density): Modifier { + return this.then(NeverDecreaseWidthElement(density)) } -private data object NeverDecreaseWidthElement : ModifierNodeElement<NeverDecreaseWidthNode>() { +private data class NeverDecreaseWidthElement(val density: Density) : + ModifierNodeElement<NeverDecreaseWidthNode>() { override fun create(): NeverDecreaseWidthNode { return NeverDecreaseWidthNode() } override fun update(node: NeverDecreaseWidthNode) { - error("This should never be called") + node.onDensityUpdated() } } private class NeverDecreaseWidthNode : Modifier.Node(), LayoutModifierNode { private var minWidth = 0 + fun onDensityUpdated() { + // When the font or display size changes, we should re-determine what our minWidth is from + // scratch (e.g. if the font size decreased, we may be able to take *less* room). + // See b/395607413. + minWidth = 0 + } + override fun MeasureScope.measure( measurable: Measurable, constraints: Constraints, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt index d81ea07cae2d..4edb23dc9f0e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt @@ -18,24 +18,18 @@ package com.android.systemui.statusbar.chips.ui.compose import android.content.res.ColorStateList import android.view.ViewGroup -import androidx.compose.foundation.background -import androidx.compose.foundation.border -import androidx.compose.foundation.clickable +import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.Shape import androidx.compose.ui.layout.layout import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.dimensionResource @@ -43,7 +37,6 @@ import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics import androidx.compose.ui.viewinterop.AndroidView import com.android.compose.animation.Expandable -import com.android.compose.modifiers.thenIf import com.android.systemui.animation.Expandable import com.android.systemui.common.ui.compose.Icon import com.android.systemui.common.ui.compose.load @@ -60,32 +53,45 @@ fun OngoingActivityChip( iconViewStore: NotificationIconContainerViewBinder.IconViewStore?, modifier: Modifier = Modifier, ) { - when (val clickBehavior = model.clickBehavior) { - is OngoingActivityChipModel.ClickBehavior.ExpandAction -> { - // Wrap the chip in an Expandable so we can animate the expand transition. - ExpandableChip( - color = { Color.Transparent }, - shape = - RoundedCornerShape( - dimensionResource(id = R.dimen.ongoing_activity_chip_corner_radius) - ), - modifier = modifier, - ) { expandable -> - ChipBody(model, iconViewStore, onClick = { clickBehavior.onClick(expandable) }) - } + val contentDescription = + when (val icon = model.icon) { + is OngoingActivityChipModel.ChipIcon.StatusBarView -> icon.contentDescription.load() + is OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon -> + icon.contentDescription.load() + is OngoingActivityChipModel.ChipIcon.SingleColorIcon, + null -> null } - is OngoingActivityChipModel.ClickBehavior.ShowHeadsUpNotification -> { - ChipBody( - model, - iconViewStore, - onClick = { clickBehavior.onClick() }, - modifier = modifier, - ) + + val borderStroke = + model.colors.outline(LocalContext.current)?.let { + BorderStroke(dimensionResource(R.dimen.ongoing_activity_chip_outline_width), Color(it)) } - is OngoingActivityChipModel.ClickBehavior.None -> { - ChipBody(model, iconViewStore, modifier = modifier) + val onClick = + when (val clickBehavior = model.clickBehavior) { + is OngoingActivityChipModel.ClickBehavior.ExpandAction -> { expandable: Expandable -> + clickBehavior.onClick(expandable) + } + is OngoingActivityChipModel.ClickBehavior.ShowHeadsUpNotification -> { _ -> + clickBehavior.onClick() + } + is OngoingActivityChipModel.ClickBehavior.None -> null } + + Expandable( + color = Color(model.colors.background(LocalContext.current).defaultColor), + shape = + RoundedCornerShape(dimensionResource(id = R.dimen.ongoing_activity_chip_corner_radius)), + modifier = + modifier.height(dimensionResource(R.dimen.ongoing_appops_chip_height)).semantics { + if (contentDescription != null) { + this.contentDescription = contentDescription + } + }, + borderStroke = borderStroke, + onClick = onClick, + ) { + ChipBody(model, iconViewStore, isClickable = onClick != null) } } @@ -93,22 +99,13 @@ fun OngoingActivityChip( private fun ChipBody( model: OngoingActivityChipModel.Active, iconViewStore: NotificationIconContainerViewBinder.IconViewStore?, + isClickable: Boolean, modifier: Modifier = Modifier, - onClick: (() -> Unit)? = null, ) { - val context = LocalContext.current - val isClickable = onClick != null val hasEmbeddedIcon = model.icon is OngoingActivityChipModel.ChipIcon.StatusBarView || model.icon is OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon - val contentDescription = - when (val icon = model.icon) { - is OngoingActivityChipModel.ChipIcon.StatusBarView -> icon.contentDescription.load() - is OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon -> - icon.contentDescription.load() - is OngoingActivityChipModel.ChipIcon.SingleColorIcon -> null - null -> null - } + val chipSidePadding = dimensionResource(id = R.dimen.ongoing_activity_chip_side_padding) val minWidth = if (isClickable) { @@ -119,68 +116,38 @@ private fun ChipBody( dimensionResource(id = R.dimen.ongoing_activity_chip_min_text_width) + chipSidePadding } - val outline = model.colors.outline(context) - val outlineWidth = dimensionResource(R.dimen.ongoing_activity_chip_outline_width) - - val shape = - RoundedCornerShape(dimensionResource(id = R.dimen.ongoing_activity_chip_corner_radius)) - - // Use a Box with `fillMaxHeight` to create a larger click surface for the chip. The visible - // height of the chip is determined by the height of the background of the Row below. - Box( - contentAlignment = Alignment.Center, + Row( + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically, modifier = modifier .fillMaxHeight() - .clickable(enabled = isClickable, onClick = onClick ?: {}) - .semantics { - if (contentDescription != null) { - this.contentDescription = contentDescription - } - }, - ) { - Row( - horizontalArrangement = Arrangement.Center, - verticalAlignment = Alignment.CenterVertically, - modifier = - Modifier.height(dimensionResource(R.dimen.ongoing_appops_chip_height)) - .thenIf(isClickable) { Modifier.widthIn(min = minWidth) } - .layout { measurable, constraints -> - val placeable = measurable.measure(constraints) - layout(placeable.width, placeable.height) { - if (constraints.maxWidth >= minWidth.roundToPx()) { - placeable.place(0, 0) - } + .layout { measurable, constraints -> + val placeable = measurable.measure(constraints) + layout(placeable.width, placeable.height) { + if (constraints.maxWidth >= minWidth.roundToPx()) { + placeable.place(0, 0) } } - .background(Color(model.colors.background(context).defaultColor), shape = shape) - .thenIf(outline != null) { - Modifier.border( - width = outlineWidth, - color = Color(outline!!), - shape = shape, - ) - } - .padding( - horizontal = - if (hasEmbeddedIcon) { - dimensionResource( - R.dimen - .ongoing_activity_chip_side_padding_for_embedded_padding_icon - ) - } else { - dimensionResource(id = R.dimen.ongoing_activity_chip_side_padding) - } - ), - ) { - model.icon?.let { - ChipIcon(viewModel = it, iconViewStore = iconViewStore, colors = model.colors) - } + } + .padding( + horizontal = + if (hasEmbeddedIcon) { + dimensionResource( + R.dimen.ongoing_activity_chip_side_padding_for_embedded_padding_icon + ) + } else { + dimensionResource(id = R.dimen.ongoing_activity_chip_side_padding) + } + ), + ) { + model.icon?.let { + ChipIcon(viewModel = it, iconViewStore = iconViewStore, colors = model.colors) + } - val isIconOnly = model is OngoingActivityChipModel.Active.IconOnly - if (!isIconOnly) { - ChipContent(viewModel = model) - } + val isIconOnly = model is OngoingActivityChipModel.Active.IconOnly + if (!isIconOnly) { + ChipContent(viewModel = model) } } } @@ -228,6 +195,7 @@ private fun StatusBarIcon( iconFactory: () -> StatusBarIconView?, ) { val context = LocalContext.current + val colorTintList = ColorStateList.valueOf(colors.text(context)) val iconSizePx = context.resources.getDimensionPixelSize( @@ -238,18 +206,8 @@ private fun StatusBarIcon( factory = { _ -> iconFactory.invoke()?.apply { layoutParams = ViewGroup.LayoutParams(iconSizePx, iconSizePx) - imageTintList = ColorStateList.valueOf(colors.text(context)) } ?: throw IllegalStateException("Missing StatusBarIconView for $notificationKey") }, + update = { iconView -> iconView.imageTintList = colorTintList }, ) } - -@Composable -private fun ExpandableChip( - color: () -> Color, - shape: Shape, - modifier: Modifier = Modifier, - content: @Composable (Expandable) -> Unit, -) { - Expandable(color = color(), shape = shape, modifier = modifier.clip(shape)) { content(it) } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/ColorsModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/ColorsModel.kt index 4954cb0a1b24..b2683762cb2a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/ColorsModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/ColorsModel.kt @@ -19,7 +19,6 @@ package com.android.systemui.statusbar.chips.ui.model import android.content.Context import android.content.res.ColorStateList import androidx.annotation.ColorInt -import com.android.settingslib.Utils import com.android.systemui.res.R /** Model representing how the chip in the status bar should be colored. */ @@ -34,14 +33,14 @@ sealed interface ColorsModel { @ColorInt fun outline(context: Context): Int? /** The chip should match the theme's primary accent color. */ - // TODO(b/347717946): The chip's color isn't getting updated when the user switches theme, it - // only gets updated when a different configuration change happens, like a rotation. data object AccentThemed : ColorsModel { override fun background(context: Context): ColorStateList = - Utils.getColorAttr(context, com.android.internal.R.attr.colorAccent) + ColorStateList.valueOf( + context.getColor(com.android.internal.R.color.materialColorPrimaryFixedDim) + ) override fun text(context: Context) = - Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.colorPrimary) + context.getColor(com.android.internal.R.color.materialColorOnPrimaryFixed) override fun outline(context: Context) = null } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt index 0cfc3216beb5..8e470742f174 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.chips.ui.model +import android.annotation.CurrentTimeMillisLong +import android.annotation.ElapsedRealtimeLong import android.view.View import com.android.systemui.animation.Expandable import com.android.systemui.common.shared.model.ContentDescription @@ -102,7 +104,7 @@ sealed class OngoingActivityChipModel { * [ChipChronometer] is based off of elapsed realtime. See * [android.widget.Chronometer.setBase]. */ - val startTimeMs: Long, + @ElapsedRealtimeLong val startTimeMs: Long, override val onClickListenerLegacy: View.OnClickListener?, override val clickBehavior: ClickBehavior, override val isHidden: Boolean = false, @@ -129,10 +131,15 @@ sealed class OngoingActivityChipModel { override val icon: ChipIcon, override val colors: ColorsModel, /** - * The time of the event that this chip represents, relative to - * [com.android.systemui.util.time.SystemClock.currentTimeMillis]. + * The time of the event that this chip represents. Relative to + * [com.android.systemui.util.time.SystemClock.currentTimeMillis] because that's what's + * required by [android.widget.DateTimeView]. + * + * TODO(b/372657935): When the Compose chips are launched, we should convert this to be + * relative to [com.android.systemui.util.time.SystemClock.elapsedRealtime] so that + * this model and the [Timer] model use the same units. */ - val time: Long, + @CurrentTimeMillisLong val time: Long, override val onClickListenerLegacy: View.OnClickListener?, override val clickBehavior: ClickBehavior, override val isHidden: Boolean = false, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt index eae2c25d77d8..8228b5533fca 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt @@ -555,7 +555,6 @@ constructor( secondary = DEFAULT_INTERNAL_INACTIVE_MODEL, ) - // TODO(b/392886257): Support 3 chips if there's space available. - private const val MAX_VISIBLE_CHIPS = 2 + private const val MAX_VISIBLE_CHIPS = 3 } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/TimeRemainingState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/TimeRemainingState.kt index eb6ebcaa5796..803d422c0f0f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/TimeRemainingState.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/TimeRemainingState.kt @@ -16,7 +16,6 @@ package com.android.systemui.statusbar.chips.ui.viewmodel -import android.os.SystemClock import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf @@ -39,7 +38,14 @@ import kotlinx.coroutines.delay * Manages state and updates for the duration remaining between now and a given time in the future. */ class TimeRemainingState(private val timeSource: TimeSource, private val futureTimeMillis: Long) { - private var durationRemaining by mutableStateOf(Duration.ZERO) + // Start with the right duration from the outset so we don't use "now" as an initial value. + private var durationRemaining by + mutableStateOf( + calculateDurationRemaining( + currentTimeMillis = timeSource.getCurrentTime(), + futureTimeMillis = futureTimeMillis, + ) + ) private var startTimeMillis: Long = 0 /** @@ -56,7 +62,11 @@ class TimeRemainingState(private val timeSource: TimeSource, private val futureT while (true) { val currentTime = timeSource.getCurrentTime() durationRemaining = - (futureTimeMillis - currentTime).toDuration(DurationUnit.MILLISECONDS) + calculateDurationRemaining( + currentTimeMillis = currentTime, + futureTimeMillis = futureTimeMillis, + ) + // No need to update if duration is more than 1 minute in the past. Because, we will // stop displaying anything. if (durationRemaining.inWholeMilliseconds < -1.minutes.inWholeMilliseconds) { @@ -67,6 +77,13 @@ class TimeRemainingState(private val timeSource: TimeSource, private val futureT } } + private fun calculateDurationRemaining( + currentTimeMillis: Long, + futureTimeMillis: Long, + ): Duration { + return (futureTimeMillis - currentTimeMillis).toDuration(DurationUnit.MILLISECONDS) + } + private fun calculateNextUpdateDelay(duration: Duration): Long { val durationAbsolute = duration.absoluteValue return when { @@ -85,7 +102,7 @@ class TimeRemainingState(private val timeSource: TimeSource, private val futureT @Composable fun rememberTimeRemainingState( futureTimeMillis: Long, - timeSource: TimeSource = remember { TimeSource { SystemClock.elapsedRealtime() } }, + timeSource: TimeSource = remember { TimeSource { System.currentTimeMillis() } }, ): TimeRemainingState { val state = diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java index 434eb7d3d410..a7929ecbd801 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java @@ -33,6 +33,7 @@ import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpHandler; import com.android.systemui.dump.DumpManager; +import com.android.systemui.media.NotificationMediaManager; import com.android.systemui.media.controls.domain.pipeline.MediaDataManager; import com.android.systemui.power.domain.interactor.PowerInteractor; import com.android.systemui.scene.shared.flag.SceneContainerFlag; @@ -44,7 +45,6 @@ import com.android.systemui.shade.ShadeSurfaceImpl; import com.android.systemui.shade.carrier.ShadeCarrierGroupController; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NotificationClickNotifier; -import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.SmartReplyController; import com.android.systemui.statusbar.StatusBarStateControllerImpl; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt index 7fa9f0e9dcc2..a658e1d55f7a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt @@ -267,7 +267,8 @@ constructor( if (StatusBarChipsModernization.isEnabled) { ongoingProcessRequiresStatusBarVisible } else { - ongoingCallStateLegacy is OngoingCallModel.InCall + ongoingCallStateLegacy is OngoingCallModel.InCall && + !ongoingCallStateLegacy.isAppVisible } val statusBarMode = toBarMode( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.java index 41353b9921bd..4d68f2e6ef1b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.java @@ -63,17 +63,6 @@ public class BundleEntry extends PipelineEntry { @Nullable @Override - public NotifSection getSection() { - return null; - } - - @Override - public int getSectionIndex() { - return 0; - } - - @Nullable - @Override public PipelineEntry getParent() { return null; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapter.kt index 64db9df8270c..26c302bf6409 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapter.kt @@ -20,6 +20,7 @@ import android.app.Notification import android.content.Context import android.os.Build import android.service.notification.StatusBarNotification +import android.util.Log import com.android.systemui.statusbar.notification.icon.IconPack import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import kotlinx.coroutines.flow.StateFlow @@ -118,5 +119,13 @@ class BundleEntryAdapter(val entry: BundleEntry) : EntryAdapter { override fun onNotificationBubbleIconClicked() { // do nothing. these cannot be a bubble + Log.wtf(TAG, "onNotificationBubbleIconClicked() called") + } + + override fun onNotificationActionClicked() { + // do nothing. these have no actions + Log.wtf(TAG, "onNotificationActionClicked() called") } } + +private const val TAG = "BundleEntryAdapter" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java index 0e75b6050678..3118ce56ac69 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java @@ -140,5 +140,10 @@ public interface EntryAdapter { * Process a click on a notification bubble icon */ void onNotificationBubbleIconClicked(); + + /** + * Processes a click on a notification action + */ + void onNotificationActionClicked(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapterFactoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapterFactoryImpl.kt index 779c25a3b402..a5169865c3c9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapterFactoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapterFactoryImpl.kt @@ -16,11 +16,11 @@ package com.android.systemui.statusbar.notification.collection -import androidx.annotation.VisibleForTesting import com.android.internal.logging.MetricsLogger import com.android.systemui.statusbar.notification.NotificationActivityStarter import com.android.systemui.statusbar.notification.collection.coordinator.VisualStabilityCoordinator import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier +import com.android.systemui.statusbar.notification.row.NotificationActionClickManager import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProvider import javax.inject.Inject @@ -33,6 +33,7 @@ constructor( private val peopleNotificationIdentifier: PeopleNotificationIdentifier, private val iconStyleProvider: NotificationIconStyleProvider, private val visualStabilityCoordinator: VisualStabilityCoordinator, + private val notificationActionClickManager: NotificationActionClickManager, ) : EntryAdapterFactory { override fun create(entry: PipelineEntry): EntryAdapter { return if (entry is NotificationEntry) { @@ -42,15 +43,11 @@ constructor( peopleNotificationIdentifier, iconStyleProvider, visualStabilityCoordinator, + notificationActionClickManager, entry, ) } else { BundleEntryAdapter((entry as BundleEntry)) } } - - @VisibleForTesting - fun getNotificationActivityStarter() : NotificationActivityStarter { - return notificationActivityStarter - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt index 4a1b9568c714..04dc7d5ed3ff 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt @@ -31,8 +31,8 @@ data class ListAttachState private constructor( var parent: PipelineEntry?, /** - * The section that this ListEntry was sorted into. If the child of the group, this will be the - * parent's section. Null if not attached to the list. + * The section that this PipelineEntry was sorted into. If the child of the group, this will be + * the parent's section. Null if not attached to the list. */ var section: NotifSection?, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java index 697d0a06cf9d..caa7abb1aa7a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java @@ -66,10 +66,6 @@ public abstract class ListEntry extends PipelineEntry { return mPreviousAttachState.getParent(); } - public int getSectionIndex() { - return mAttachState.getSection() != null ? mAttachState.getSection().getIndex() : -1; - } - /** * Stores the current attach state into {@link #getPreviousAttachState()}} and then starts a * fresh attach state (all entries will be null/default-initialized). diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.kt index f662a040fae6..0fc0e9c5eab8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.kt @@ -24,6 +24,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.OnBefo import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeSortListener import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeTransformGroupsListener import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifBundler import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter @@ -169,6 +170,14 @@ class NotifPipeline @Inject constructor( } /** + * NotifBundler that is used to determine whether a notification should be bundled according to + * classification. + */ + fun setNotifBundler(bundler: NotifBundler) { + mShadeListBuilder.setBundler(bundler) + } + + /** * StabilityManager that is used to determine whether to suppress group and section changes. * This should only be set once. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapter.kt index 0ff2dd7c7f43..1168c647c26a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapter.kt @@ -24,6 +24,7 @@ import com.android.systemui.statusbar.notification.collection.coordinator.Visual import com.android.systemui.statusbar.notification.icon.IconPack import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow +import com.android.systemui.statusbar.notification.row.NotificationActionClickManager import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProvider import kotlinx.coroutines.flow.StateFlow @@ -33,6 +34,7 @@ class NotificationEntryAdapter( private val peopleNotificationIdentifier: PeopleNotificationIdentifier, private val iconStyleProvider: NotificationIconStyleProvider, private val visualStabilityCoordinator: VisualStabilityCoordinator, + private val notificationActionClickManager: NotificationActionClickManager, private val entry: NotificationEntry, ) : EntryAdapter { @@ -142,4 +144,8 @@ class NotificationEntryAdapter( override fun onNotificationBubbleIconClicked() { notificationActivityStarter.onNotificationBubbleIconClicked(entry) } + + override fun onNotificationActionClicked() { + notificationActionClickManager.onNotificationActionClicked(entry) + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/PipelineEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/PipelineEntry.java index 84de77bac352..872cd68e1b21 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/PipelineEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/PipelineEntry.java @@ -70,7 +70,9 @@ public abstract class PipelineEntry { /** * @return Index of section assigned to this entry. */ - public abstract int getSectionIndex(); + public int getSectionIndex() { + return mAttachState.getSection() != null ? mAttachState.getSection().getIndex() : -1; + } /** * @return Parent PipelineEntry diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java index bb84ab8f421a..238ba8d9f490 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java @@ -48,6 +48,7 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dump.DumpManager; import com.android.systemui.statusbar.NotificationInteractionTracker; import com.android.systemui.statusbar.notification.NotifPipelineFlags; +import com.android.systemui.statusbar.notification.collection.coordinator.BundleCoordinator; import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection; import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeFinalizeFilterListener; import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener; @@ -58,8 +59,10 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.SemiSt import com.android.systemui.statusbar.notification.collection.listbuilder.SemiStableSort.StableOrder; import com.android.systemui.statusbar.notification.collection.listbuilder.ShadeListBuilderHelper; import com.android.systemui.statusbar.notification.collection.listbuilder.ShadeListBuilderLogger; +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.DefaultNotifBundler; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.DefaultNotifStabilityManager; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator; +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifBundler; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter; @@ -78,6 +81,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; +import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -122,7 +126,8 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { private final List<NotifComparator> mNotifComparators = new ArrayList<>(); private final List<NotifSection> mNotifSections = new ArrayList<>(); private NotifStabilityManager mNotifStabilityManager; - + private NotifBundler mNotifBundler; + private Map<String, BundleEntry> mIdToBundleEntry = new HashMap<>(); private final NamedListenerSet<OnBeforeTransformGroupsListener> mOnBeforeTransformGroupsListeners = new NamedListenerSet<>(); private final NamedListenerSet<OnBeforeSortListener> @@ -273,6 +278,21 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { } } + void setBundler(NotifBundler bundler) { + Assert.isMainThread(); + mPipelineState.requireState(STATE_IDLE); + + mNotifBundler = bundler; + if (mNotifBundler == null) { + throw new IllegalStateException(TAG + ".setBundler: null"); + } + + mIdToBundleEntry.clear(); + for (String id: mNotifBundler.getBundleIds()) { + mIdToBundleEntry.put(id, new BundleEntry(id)); + } + } + void setNotifStabilityManager(@NonNull NotifStabilityManager notifStabilityManager) { Assert.isMainThread(); mPipelineState.requireState(STATE_IDLE); @@ -297,6 +317,14 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { return mNotifStabilityManager; } + @NonNull + private NotifBundler getNotifBundler() { + if (mNotifBundler == null) { + return DefaultNotifBundler.INSTANCE; + } + return mNotifBundler; + } + void setComparators(List<NotifComparator> comparators) { Assert.isMainThread(); mPipelineState.requireState(STATE_IDLE); @@ -651,7 +679,7 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { j--; } } - } else { + } else if (tle instanceof NotificationEntry) { // maybe put top-level-entries back into their previous groups if (maybeSuppressGroupChange(tle.getRepresentativeEntry(), topLevelList)) { // entry was put back into its previous group, so we remove it from the list of @@ -659,7 +687,7 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { topLevelList.remove(i); i--; } - } + } // Promoters ignore bundles so we don't have to demote any here. } Trace.endSection(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BundleCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BundleCoordinator.kt index e6d5f4120a20..8833ff1ce20c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BundleCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BundleCoordinator.kt @@ -20,15 +20,19 @@ import android.app.NotificationChannel.NEWS_ID import android.app.NotificationChannel.PROMOTIONS_ID import android.app.NotificationChannel.RECS_ID import android.app.NotificationChannel.SOCIAL_MEDIA_ID -import com.android.systemui.statusbar.notification.collection.PipelineEntry +import android.app.NotificationChannel.SYSTEM_RESERVED_IDS import com.android.systemui.statusbar.notification.collection.NotifPipeline +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.PipelineEntry import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifBundler import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner import com.android.systemui.statusbar.notification.collection.render.NodeController import com.android.systemui.statusbar.notification.dagger.NewsHeader import com.android.systemui.statusbar.notification.dagger.PromoHeader import com.android.systemui.statusbar.notification.dagger.RecsHeader import com.android.systemui.statusbar.notification.dagger.SocialHeader +import com.android.systemui.statusbar.notification.shared.NotificationBundleUi import com.android.systemui.statusbar.notification.stack.BUCKET_NEWS import com.android.systemui.statusbar.notification.stack.BUCKET_PROMO import com.android.systemui.statusbar.notification.stack.BUCKET_RECS @@ -90,6 +94,20 @@ class BundleCoordinator @Inject constructor( } } + val bundler = + object : NotifBundler("NotifBundler") { + + // Use list instead of set to keep fixed order + override val bundleIds: List<String> = SYSTEM_RESERVED_IDS + + override fun getBundleIdOrNull(entry: NotificationEntry?): String? { + return entry?.representativeEntry?.channel?.id?.takeIf { it in this.bundleIds } + } + } + override fun attach(pipeline: NotifPipeline) { + if (NotificationBundleUi.isEnabled) { + pipeline.setNotifBundler(bundler) + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt index fdb8cd871dd9..a0eab43f854b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt @@ -26,6 +26,7 @@ import com.android.systemui.statusbar.NotificationRemoteInputManager import com.android.systemui.statusbar.chips.notification.domain.interactor.StatusBarNotificationChipsInteractor import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips import com.android.systemui.statusbar.notification.NotifPipelineFlags +import com.android.systemui.statusbar.notification.collection.BundleEntry import com.android.systemui.statusbar.notification.collection.GroupEntry import com.android.systemui.statusbar.notification.collection.NotifCollection import com.android.systemui.statusbar.notification.collection.NotifPipeline @@ -47,7 +48,9 @@ import com.android.systemui.statusbar.notification.headsup.PinnedStatus import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider import com.android.systemui.statusbar.notification.logKey +import com.android.systemui.statusbar.notification.row.NotificationActionClickManager import com.android.systemui.statusbar.notification.shared.GroupHunAnimationFix +import com.android.systemui.statusbar.notification.shared.NotificationBundleUi import com.android.systemui.statusbar.notification.stack.BUCKET_HEADS_UP import com.android.systemui.util.concurrency.DelayableExecutor import com.android.systemui.util.time.SystemClock @@ -82,6 +85,7 @@ constructor( private val mHeadsUpViewBinder: HeadsUpViewBinder, private val mVisualInterruptionDecisionProvider: VisualInterruptionDecisionProvider, private val mRemoteInputManager: NotificationRemoteInputManager, + private val notificationActionClickManager: NotificationActionClickManager, private val mLaunchFullScreenIntentProvider: LaunchFullScreenIntentProvider, private val mFlags: NotifPipelineFlags, private val statusBarNotificationChipsInteractor: StatusBarNotificationChipsInteractor, @@ -107,7 +111,11 @@ constructor( pipeline.addOnBeforeFinalizeFilterListener(::onBeforeFinalizeFilter) pipeline.addPromoter(mNotifPromoter) pipeline.addNotificationLifetimeExtender(mLifetimeExtender) - mRemoteInputManager.addActionPressListener(mActionPressListener) + if (NotificationBundleUi.isEnabled) { + notificationActionClickManager.addActionClickListener(mActionPressListener) + } else { + mRemoteInputManager.addActionPressListener(mActionPressListener) + } if (StatusBarNotifChips.isEnabled) { applicationScope.launch { @@ -423,6 +431,7 @@ constructor( map[child.key] = GroupLocation.Child } } + is BundleEntry -> map[topLevelEntry.key] = GroupLocation.Bundle else -> error("unhandled type $topLevelEntry") } } @@ -781,7 +790,7 @@ constructor( */ private val mActionPressListener = Consumer<NotificationEntry> { entry -> - mHeadsUpManager.setUserActionMayIndirectlyRemove(entry) + mHeadsUpManager.setUserActionMayIndirectlyRemove(entry.key) mExecutor.execute { endNotifLifetimeExtensionIfExtended(entry) } } @@ -950,6 +959,7 @@ private enum class GroupLocation { Isolated, Summary, Child, + Bundle, } private fun Map<String, GroupLocation>.getLocation(key: String): GroupLocation = diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinator.kt index 56deb18df9ab..d542e67e665a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinator.kt @@ -26,6 +26,7 @@ import com.android.systemui.dump.DumpManager import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.StatusBarState +import com.android.systemui.statusbar.notification.collection.BundleEntry import com.android.systemui.statusbar.notification.collection.GroupEntry import com.android.systemui.statusbar.notification.collection.PipelineEntry import com.android.systemui.statusbar.notification.collection.NotifPipeline @@ -193,6 +194,7 @@ constructor( when (it) { is NotificationEntry -> listOfNotNull(it) is GroupEntry -> it.children + is BundleEntry -> emptyList() else -> error("unhandled type of $it") } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt index df0cde51e8ba..818ef6b335c7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt @@ -24,6 +24,7 @@ import com.android.systemui.statusbar.notification.collection.coordinator.dagger import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider import com.android.systemui.statusbar.notification.promoted.AutomaticPromotionCoordinator +import com.android.systemui.statusbar.notification.shared.NotificationBundleUi import com.android.systemui.statusbar.notification.shared.NotificationMinimalism import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor import com.android.systemui.statusbar.notification.shared.PriorityPeopleSection @@ -113,11 +114,12 @@ constructor( mCoordinators.add(remoteInputCoordinator) mCoordinators.add(dismissibilityCoordinator) mCoordinators.add(automaticPromotionCoordinator) - + if (NotificationBundleUi.isEnabled) { + mCoordinators.add(bundleCoordinator) + } if (NotificationsLiveDataStoreRefactor.isEnabled) { mCoordinators.add(statsLoggerCoordinator) } - // Manually add Ordered Sections if (NotificationMinimalism.isEnabled) { mOrderedSections.add(lockScreenMinimalismCoordinator.topOngoingSectioner) // Top Ongoing @@ -135,7 +137,7 @@ constructor( mOrderedSections.add(conversationCoordinator.peopleSilentSectioner) // People Silent } mOrderedSections.add(rankingCoordinator.alertingSectioner) // Alerting - if (NotificationClassificationFlag.isEnabled) { + if (NotificationClassificationFlag.isEnabled && !NotificationBundleUi.isEnabled) { mOrderedSections.add(bundleCoordinator.newsSectioner) mOrderedSections.add(bundleCoordinator.socialSectioner) mOrderedSections.add(bundleCoordinator.recsSectioner) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java index 20c6736b74c8..b54f21b23bba 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java @@ -34,6 +34,7 @@ import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.statusbar.IStatusBarService; +import com.android.systemui.statusbar.notification.collection.BundleEntry; import com.android.systemui.statusbar.notification.collection.GroupEntry; import com.android.systemui.statusbar.notification.collection.PipelineEntry; import com.android.systemui.statusbar.notification.collection.NotifPipeline; @@ -54,6 +55,7 @@ import com.android.systemui.statusbar.notification.row.icon.AppIconProvider; import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProvider; import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation; import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation; +import com.android.systemui.statusbar.notification.shared.NotificationBundleUi; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -306,7 +308,9 @@ public class PreparationCoordinator implements Coordinator { private void inflateAllRequiredViews(List<PipelineEntry> entries) { for (int i = 0, size = entries.size(); i < size; i++) { PipelineEntry entry = entries.get(i); - if (entry instanceof GroupEntry) { + if (NotificationBundleUi.isEnabled() && entry instanceof BundleEntry) { + // TODO(b/399738511) Inflate bundle views. + } else if (entry instanceof GroupEntry) { GroupEntry groupEntry = (GroupEntry) entry; inflateRequiredGroupViews(groupEntry); } else { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java index d1063d95a305..3fad8f0510a1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java @@ -132,7 +132,12 @@ public class RankingCoordinator implements Coordinator { public void onEntriesUpdated(@NonNull List<PipelineEntry> entries) { mHasSilentEntries = false; for (int i = 0; i < entries.size(); i++) { - if (entries.get(i).getRepresentativeEntry().getSbn().isClearable()) { + NotificationEntry notifEntry = entries.get(i).getRepresentativeEntry(); + if (notifEntry == null) { + // TODO(b/395698521) Handle BundleEntry + continue; + } + if (notifEntry.getSbn().isClearable()) { mHasSilentEntries = true; break; } @@ -147,6 +152,7 @@ public class RankingCoordinator implements Coordinator { @Override public boolean isInSection(PipelineEntry entry) { return !mHighPriorityProvider.isHighPriority(entry) + && entry.getRepresentativeEntry() != null && entry.getRepresentativeEntry().isAmbient(); } @@ -161,7 +167,12 @@ public class RankingCoordinator implements Coordinator { public void onEntriesUpdated(@NonNull List<PipelineEntry> entries) { mHasMinimizedEntries = false; for (int i = 0; i < entries.size(); i++) { - if (entries.get(i).getRepresentativeEntry().getSbn().isClearable()) { + NotificationEntry notifEntry = entries.get(i).getRepresentativeEntry(); + if (notifEntry == null) { + // TODO(b/395698521) Handle BundleEntry + continue; + } + if (notifEntry.getSbn().isClearable()) { mHasMinimizedEntries = true; break; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinator.kt index e7c767f42c12..27c0dcccfe43 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinator.kt @@ -221,20 +221,20 @@ constructor( mSmartReplyHistoryExtender.isExtending(key) } else false - override fun releaseNotificationIfKeptForRemoteInputHistory(entry: NotificationEntry) { - if (DEBUG) Log.d(TAG, "releaseNotificationIfKeptForRemoteInputHistory(entry=${entry.key})") + override fun releaseNotificationIfKeptForRemoteInputHistory(entryKey: String) { + if (DEBUG) Log.d(TAG, "releaseNotificationIfKeptForRemoteInputHistory(entry=${entryKey})") if (!lifetimeExtensionRefactor()) { mRemoteInputHistoryExtender.endLifetimeExtensionAfterDelay( - entry.key, + entryKey, REMOTE_INPUT_EXTENDER_RELEASE_DELAY, ) mSmartReplyHistoryExtender.endLifetimeExtensionAfterDelay( - entry.key, + entryKey, REMOTE_INPUT_EXTENDER_RELEASE_DELAY, ) } mRemoteInputActiveExtender.endLifetimeExtensionAfterDelay( - entry.key, + entryKey, REMOTE_INPUT_EXTENDER_RELEASE_DELAY, ) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java index c2f0806a9cd6..6b32c6a18ec0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java @@ -17,7 +17,6 @@ package com.android.systemui.statusbar.notification.collection.inflation; import static com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_NONE; -import static com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_SENSITIVE_CONTENT; import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED; import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED; import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC; @@ -276,7 +275,7 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { if (LockscreenOtpRedaction.isSingleLineViewEnabled()) { if (inflaterParams.isChildInGroup() - && redactionType == REDACTION_TYPE_SENSITIVE_CONTENT) { + && redactionType != REDACTION_TYPE_NONE) { params.requireContentViews(FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE); } else { params.markContentViewsFreeable(FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifBundler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifBundler.kt new file mode 100644 index 000000000000..14a9113f7eb4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifBundler.kt @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.statusbar.notification.collection.listbuilder.pluggable + +import com.android.systemui.statusbar.notification.collection.NotificationEntry + +/** Pluggable for bundling notifications according to classification. */ +abstract class NotifBundler protected constructor(name: String?) : Pluggable<NotifBundler?>(name) { + abstract val bundleIds: List<String> + + abstract fun getBundleIdOrNull(entry: NotificationEntry?): String? +} + +/** The default, no-op instance of NotifBundler which does not bundle anything. */ +object DefaultNotifBundler : NotifBundler("DefaultNotifBundler") { + override val bundleIds: List<String> + get() = listOf() + + override fun getBundleIdOrNull(entry: NotificationEntry?): String? { + return null + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java index ef3da9498f70..1e5aa01714be 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java @@ -83,6 +83,7 @@ import com.android.systemui.statusbar.notification.logging.dagger.NotificationsL import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractor; import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractorImpl; import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel; +import com.android.systemui.statusbar.notification.row.NotificationActionClickManager; import com.android.systemui.statusbar.notification.row.NotificationEntryProcessorFactory; import com.android.systemui.statusbar.notification.row.NotificationEntryProcessorFactoryLooperImpl; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; @@ -346,4 +347,5 @@ public interface NotificationsModule { /** Provides an instance of {@link EntryAdapterFactory} */ @Binds EntryAdapterFactory provideEntryAdapterFactory(EntryAdapterFactoryImpl impl); + } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManager.kt index 9728fcfcd6ba..25ae50c34659 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManager.kt @@ -19,7 +19,6 @@ package com.android.systemui.statusbar.notification.headsup import android.graphics.Region import com.android.systemui.Dumpable import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.statusbar.notification.collection.EntryAdapter import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import dagger.Binds @@ -155,9 +154,9 @@ interface HeadsUpManager : Dumpable { fun setAnimationStateHandler(handler: AnimationStateHandler) /** - * Set an entry to be expanded and therefore stick in the heads up area if it's pinned until - * it's collapsed again. - */ + * Set an entry to be expanded and therefore stick in the heads up area if it's pinned until + * it's collapsed again. + */ fun setExpanded(key: String, row: ExpandableNotificationRow, expanded: Boolean) /** @@ -199,12 +198,12 @@ interface HeadsUpManager : Dumpable { * Notes that the user took an action on an entry that might indirectly cause the system or the * app to remove the notification. * - * @param entry the entry that might be indirectly removed by the user's action + * @param entry the key of the entry that might be indirectly removed by the user's action * @see * com.android.systemui.statusbar.notification.collection.coordinator.HeadsUpCoordinator.mActionPressListener * @see .canRemoveImmediately */ - fun setUserActionMayIndirectlyRemove(entry: NotificationEntry) + fun setUserActionMayIndirectlyRemove(entryKey: String) /** * Decides whether a click is invalid for a notification, i.e. it has not been shown long enough @@ -332,7 +331,7 @@ class HeadsUpManagerEmptyImpl @Inject constructor() : HeadsUpManager { override fun setUser(user: Int) {} - override fun setUserActionMayIndirectlyRemove(entry: NotificationEntry) {} + override fun setUserActionMayIndirectlyRemove(entryKey: String) {} override fun shouldSwallowClick(key: String): Boolean = false diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java index ca94655318b9..ca83666079ec 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java @@ -1126,8 +1126,8 @@ public class HeadsUpManagerImpl * @see HeadsUpCoordinator.mActionPressListener * @see #canRemoveImmediately(String) */ - public void setUserActionMayIndirectlyRemove(@NonNull NotificationEntry entry) { - HeadsUpEntry headsUpEntry = getHeadsUpEntry(entry.getKey()); + public void setUserActionMayIndirectlyRemove(@NonNull String entryKey) { + HeadsUpEntry headsUpEntry = getHeadsUpEntry(entryKey); if (headsUpEntry != null) { headsUpEntry.mUserActionMayIndirectlyRemove = true; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt index c3266fc57c2e..92c87e6403ec 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt @@ -18,10 +18,10 @@ package com.android.systemui.statusbar.notification.init import android.service.notification.StatusBarNotification import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.media.NotificationMediaManager import com.android.systemui.people.widget.PeopleSpaceWidgetManager import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption import com.android.systemui.statusbar.NotificationListener -import com.android.systemui.statusbar.NotificationMediaManager import com.android.systemui.statusbar.NotificationPresenter import com.android.systemui.statusbar.notification.AnimatedImageNotificationManager import com.android.systemui.statusbar.notification.NotificationActivityStarter diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractor.kt index 1abd48c0c50b..a99ca072b6c8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractor.kt @@ -70,9 +70,6 @@ constructor( private fun OngoingCallModel.getNotifData(): NotifAndPromotedContent? = when (this) { is OngoingCallModel.InCall -> NotifAndPromotedContent(notificationKey, promotedContent) - is OngoingCallModel.InCallWithVisibleApp -> - // TODO(b/395989259): support InCallWithVisibleApp when it has notif data - null is OngoingCallModel.NoCall -> null } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java index 6837cb2a6292..689222608abe 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java @@ -128,9 +128,14 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView updateColors(); } - private void updateColors() { - if (usesTransparentBackground()) { - mNormalColor = SurfaceEffectColors.surfaceEffect1(getContext()); + protected void updateColors() { + if (notificationRowTransparency()) { + if (mIsBlurSupported) { + mNormalColor = SurfaceEffectColors.surfaceEffect1(getContext()); + } else { + mNormalColor = mContext.getColor( + com.android.internal.R.color.materialColorSurfaceContainer); + } } else { mNormalColor = mContext.getColor( com.android.internal.R.color.materialColorSurfaceContainerHigh); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorListView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorListView.kt index 640d364895ae..dccc28f5fe49 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorListView.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorListView.kt @@ -34,11 +34,11 @@ import android.view.LayoutInflater import android.view.View import android.widget.ImageView import android.widget.LinearLayout -import android.widget.Switch import android.widget.TextView import com.android.settingslib.Utils import com.android.systemui.res.R import com.android.systemui.util.Assert +import com.google.android.material.materialswitch.MaterialSwitch /** Half-shelf for notification channel controls */ class ChannelEditorListView(c: Context, attrs: AttributeSet) : LinearLayout(c, attrs) { @@ -139,12 +139,12 @@ class ChannelEditorListView(c: Context, attrs: AttributeSet) : LinearLayout(c, a class AppControlView(c: Context, attrs: AttributeSet) : LinearLayout(c, attrs) { lateinit var iconView: ImageView lateinit var channelName: TextView - lateinit var switch: Switch + lateinit var switch: MaterialSwitch override fun onFinishInflate() { iconView = requireViewById(R.id.icon) channelName = requireViewById(R.id.app_name) - switch = requireViewById(R.id.toggle) + switch = requireViewById(R.id.material_toggle) setOnClickListener { switch.toggle() } } @@ -155,7 +155,7 @@ class ChannelRow(c: Context, attrs: AttributeSet) : LinearLayout(c, attrs) { lateinit var controller: ChannelEditorDialogController private lateinit var channelName: TextView private lateinit var channelDescription: TextView - private lateinit var switch: Switch + private lateinit var switch: MaterialSwitch private val highlightColor: Int var gentle = false @@ -175,7 +175,7 @@ class ChannelRow(c: Context, attrs: AttributeSet) : LinearLayout(c, attrs) { super.onFinishInflate() channelName = requireViewById(R.id.channel_name) channelDescription = requireViewById(R.id.channel_description) - switch = requireViewById(R.id.toggle) + switch = requireViewById(R.id.material_toggle) switch.setOnCheckedChangeListener { _, b -> channel?.let { controller.proposeEditForChannel( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 68ad4fad31c1..8da2f768bf71 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -19,9 +19,12 @@ package com.android.systemui.statusbar.notification.row; import static android.app.Flags.notificationsRedesignTemplates; import static android.app.Notification.Action.SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY; import static android.service.notification.NotificationListenerService.REASON_CANCEL; +import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_EXPANDED; +import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED; import static com.android.systemui.Flags.notificationRowTransparency; import static com.android.systemui.Flags.notificationsPinnedHunInShade; +import static com.android.systemui.Flags.notificationRowAccessibilityExpanded; import static com.android.systemui.flags.Flags.ENABLE_NOTIFICATIONS_SIMULATE_SLOW_MEASURE; import static com.android.systemui.statusbar.notification.NotificationUtils.logKey; import static com.android.systemui.statusbar.notification.collection.NotificationEntry.DismissState.PARENT_DISMISSED; @@ -65,6 +68,7 @@ import android.view.ViewGroup; import android.view.ViewParent; import android.view.ViewStub; import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; import android.widget.Chronometer; @@ -111,7 +115,6 @@ import com.android.systemui.statusbar.notification.NotificationUtils; import com.android.systemui.statusbar.notification.SourceType; import com.android.systemui.statusbar.notification.collection.EntryAdapter; import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.collection.NotificationEntryAdapter; import com.android.systemui.statusbar.notification.collection.PipelineEntry; import com.android.systemui.statusbar.notification.collection.provider.NotificationDismissibilityProvider; import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager; @@ -120,6 +123,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.PromotedNotificationUi; import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiForceExpanded; import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation; import com.android.systemui.statusbar.notification.row.shared.LockscreenOtpRedaction; @@ -182,7 +186,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private boolean mShowSnooze = false; private boolean mIsFaded; - private boolean mIsPromotedOngoing = false; private boolean mHasStatusBarChipDuringHeadsUpAnimation = false; @Nullable @@ -406,10 +409,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } private void toggleExpansionState(View v, boolean shouldLogExpandClickMetric) { - boolean isGroupRoot = NotificationBundleUi.isEnabled() - ? mGroupMembershipManager.isGroupRoot(mEntryAdapter) - : mGroupMembershipManager.isGroupSummary(mEntry); - if (!shouldShowPublic() && (!mIsMinimized || isExpanded()) && isGroupRoot) { + if (!shouldShowPublic() && (!mIsMinimized || isExpanded()) && isGroupRoot()) { mGroupExpansionChanging = true; if (NotificationBundleUi.isEnabled()) { final boolean wasExpanded = mGroupExpansionManager.isGroupExpanded(mEntryAdapter); @@ -871,7 +871,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private void updateLimitsForView(NotificationContentView layout) { final int maxExpandedHeight; - if (isPromotedOngoing()) { + if (PromotedNotificationUiForceExpanded.isEnabled() && isPromotedOngoing()) { maxExpandedHeight = mMaxExpandedHeightForPromotedOngoing; } else { maxExpandedHeight = mMaxExpandedHeight; @@ -979,7 +979,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } else if (isAboveShelf() != wasAboveShelf) { mAboveShelfChangedListener.onAboveShelfStateChanged(!wasAboveShelf); } - updateBackgroundOpacity(); + updateColors(); } /** @@ -1350,7 +1350,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView if (mIsSummaryWithChildren) { return mChildrenContainer.getIntrinsicHeight(); } - if (isPromotedOngoing()) { + if (PromotedNotificationUiForceExpanded.isEnabled() && isPromotedOngoing()) { return getMaxExpandHeight(); } if (mExpandedWhenPinned) { @@ -2775,7 +2775,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return false; } - public void applyLaunchAnimationParams(LaunchAnimationParameters params) { if (params == null) { // `null` params indicates the animation is over, which means we can't access @@ -2940,7 +2939,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView if (mIsSummaryWithChildren && !shouldShowPublic()) { return !mChildrenExpanded; } - if (isPromotedOngoing()) { + if (PromotedNotificationUiForceExpanded.isEnabled() && isPromotedOngoing()) { return false; } return mEnableNonGroupedNotificationExpand && mExpandable; @@ -2951,17 +2950,6 @@ 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); - } /** * Sets whether the status bar is showing a chip corresponding to this notification. @@ -3062,10 +3050,13 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } public void setUserLocked(boolean userLocked) { - if (isPromotedOngoing()) return; + if (PromotedNotificationUiForceExpanded.isEnabled() && isPromotedOngoing()) return; mUserLocked = userLocked; mPrivateLayout.setUserExpanding(userLocked); + if (android.app.Flags.expandingPublicView()) { + mPublicLayout.setUserExpanding(userLocked); + } // This is intentionally not guarded with mIsSummaryWithChildren since we might have had // children but not anymore. if (mChildrenContainer != null) { @@ -3122,7 +3113,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mChildrenContainer.setOnKeyguard(onKeyguard); } } - updateBackgroundOpacity(); + updateColors(); } } @@ -3193,6 +3184,12 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return mGroupExpansionManager.isGroupExpanded(mEntry); } + private boolean isGroupRoot() { + return NotificationBundleUi.isEnabled() + ? mGroupMembershipManager.isGroupRoot(mEntryAdapter) + : mGroupMembershipManager.isGroupSummary(mEntry); + } + private void onAttachedChildrenCountChanged() { final boolean wasSummary = mIsSummaryWithChildren; mIsSummaryWithChildren = mChildrenContainer != null @@ -3242,7 +3239,16 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } public boolean isPromotedOngoing() { - return PromotedNotificationUiForceExpanded.isEnabled() && mIsPromotedOngoing; + if (!PromotedNotificationUi.isEnabled()) { + return false; + } + + final NotificationEntry entry = mEntry; + if (entry == null) { + return false; + } + + return entry.isPromotedOngoing(); } private boolean isPromotedNotificationExpanded(boolean allowOnKeyguard) { @@ -3271,6 +3277,27 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } /** + * Is this row currently showing an expanded state? This method is different from + * {@link #isExpanded()}, because it also handles groups, and pinned notifications. + */ + private boolean isShowingExpanded() { + if (!shouldShowPublic() && (!mIsMinimized || isExpanded()) && isGroupRoot()) { + // is group and expanded? + return isGroupExpanded(); + } else if (mEnableNonGroupedNotificationExpand) { + if (isPinned()) { + // is pinned and expanded? + return mExpandedWhenPinned; + } else { + // is regular notification and expanded? + return isExpanded(); + } + } else { + return false; + } + } + + /** * Check whether the view state is currently expanded. This is given by the system in {@link * #setSystemExpanded(boolean)} and can be overridden by user expansion or * collapsing in {@link #setUserExpanded(boolean)}. Note that the visual appearance of this @@ -3283,7 +3310,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } public boolean isExpanded(boolean allowOnKeyguard) { - if (isPromotedOngoing()) { + if (PromotedNotificationUiForceExpanded.isEnabled() && isPromotedOngoing()) { return isPromotedNotificationExpanded(allowOnKeyguard); } @@ -3952,9 +3979,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView public void onExpandedByGesture(boolean userExpanded) { int event = MetricsEvent.ACTION_NOTIFICATION_GESTURE_EXPANDER; - if (NotificationBundleUi.isEnabled() - ? mGroupMembershipManager.isGroupRoot(mEntryAdapter) - : mGroupMembershipManager.isGroupSummary(mEntry)) { + if (isGroupRoot()) { event = MetricsEvent.ACTION_NOTIFICATION_GROUP_GESTURE_EXPANDER; } mMetricsLogger.action(event, userExpanded); @@ -4007,6 +4032,18 @@ public class ExpandableNotificationRow extends ActivatableNotificationView if (mExpansionChangedListener != null) { mExpansionChangedListener.onExpansionChanged(nowExpanded); } + if (notificationRowAccessibilityExpanded()) { + notifyAccessibilityContentExpansionChanged(); + } + } + } + + private void notifyAccessibilityContentExpansionChanged() { + if (AccessibilityManager.getInstance(mContext).isEnabled()) { + AccessibilityEvent event = AccessibilityEvent.obtain(); + event.setEventType(TYPE_WINDOW_CONTENT_CHANGED); + event.setContentChangeTypes(CONTENT_CHANGE_TYPE_EXPANDED); + sendAccessibilityEventUnchecked(event); } } @@ -4037,33 +4074,50 @@ public class ExpandableNotificationRow extends ActivatableNotificationView super.onInitializeAccessibilityNodeInfoInternal(info); final boolean isLongClickable = isNotificationRowLongClickable(); if (isLongClickable) { - info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK); + info.addAction(AccessibilityAction.ACTION_LONG_CLICK); } info.setLongClickable(isLongClickable); if (canViewBeDismissed() && !mIsSnoozed) { - info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_DISMISS); + info.addAction(AccessibilityAction.ACTION_DISMISS); } - boolean expandable = shouldShowPublic(); - boolean isExpanded = false; - if (!expandable) { - if (mIsSummaryWithChildren) { - expandable = true; - if (!mIsMinimized || isExpanded()) { - isExpanded = isGroupExpanded(); + + if (notificationRowAccessibilityExpanded()) { + if (isAccessibilityExpandable()) { + if (isShowingExpanded()) { + info.addAction(AccessibilityAction.ACTION_COLLAPSE); + info.setExpandedState(AccessibilityNodeInfo.EXPANDED_STATE_FULL); + } else { + info.addAction(AccessibilityAction.ACTION_EXPAND); + info.setExpandedState(AccessibilityNodeInfo.EXPANDED_STATE_COLLAPSED); } } else { - expandable = mPrivateLayout.isContentExpandable(); - isExpanded = isExpanded(); + info.setExpandedState(AccessibilityNodeInfo.EXPANDED_STATE_UNDEFINED); } - } - if (expandable && !mIsSnoozed) { - if (isExpanded) { - info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_COLLAPSE); - } else { - info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_EXPAND); + } else { + boolean expandable = shouldShowPublic(); + boolean isExpanded = false; + if (!expandable) { + if (mIsSummaryWithChildren) { + expandable = true; + if (!mIsMinimized || isExpanded()) { + isExpanded = isGroupExpanded(); + } + } else { + expandable = mPrivateLayout.isContentExpandable(); + isExpanded = isExpanded(); + } + } + + if (expandable) { + if (isExpanded) { + info.addAction(AccessibilityAction.ACTION_COLLAPSE); + } else { + info.addAction(AccessibilityAction.ACTION_EXPAND); + } } } + NotificationMenuRowPlugin provider = getProvider(); if (provider != null) { MenuItem snoozeMenu = provider.getSnoozeMenuItem(getContext()); @@ -4076,6 +4130,12 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } } + /** @return whether this row's expansion state can be toggled by an accessibility action. */ + private boolean isAccessibilityExpandable() { + // don't add expand accessibility actions to snoozed notifications + return !mIsSnoozed && isContentExpandable(); + } + @Override public boolean performAccessibilityActionInternal(int action, Bundle arguments) { if (super.performAccessibilityActionInternal(action, arguments)) { @@ -4339,8 +4399,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView + (!shouldShowPublic() && mIsSummaryWithChildren)); pw.print(", mShowNoBackground: " + mShowNoBackground); pw.print(", clipBounds: " + getClipBounds()); - if (PromotedNotificationUiForceExpanded.isEnabled()) { - pw.print(", isPromotedOngoing: " + isPromotedOngoing()); + pw.print(", isPromotedOngoing: " + isPromotedOngoing()); + if (notificationRowAccessibilityExpanded()) { + pw.print(", isShowingExpanded: " + isShowingExpanded()); + pw.print(", isAccessibilityExpandable: " + isAccessibilityExpandable()); } pw.print(", isExpandable: " + isExpandable()); pw.print(", mExpandable: " + mExpandable); @@ -4569,11 +4631,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } } - private void updateBackgroundOpacity() { - if (mBackgroundNormal != null) { - // Row background should be opaque when it's displayed as a heads-up notification or - // displayed on keyguard. - mBackgroundNormal.setForceOpaque(mIsHeadsUp || mOnKeyguard); - } + @Override + protected boolean usesTransparentBackground() { + return super.usesTransparentBackground() && !mIsHeadsUp && !mOnKeyguard; } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationActionClickManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationActionClickManager.kt new file mode 100644 index 000000000000..2b451406eaad --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationActionClickManager.kt @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.util.ListenerSet +import java.util.function.Consumer +import javax.inject.Inject + +/** + * Pipeline components can register consumers here to be informed when a notification action is + * clicked + */ +@SysUISingleton +class NotificationActionClickManager @Inject constructor() { + private val actionClickListeners = ListenerSet<Consumer<NotificationEntry>>() + + fun addActionClickListener(listener: Consumer<NotificationEntry>) { + actionClickListeners.addIfAbsent(listener) + } + + fun removeActionClickListener(listener: Consumer<NotificationEntry>) { + actionClickListeners.remove(listener) + } + + fun onNotificationActionClicked(entry: NotificationEntry) { + for (listener in actionClickListeners) { + listener.accept(entry) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java index e1219e88a405..4914e1073059 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java @@ -39,7 +39,6 @@ import androidx.annotation.Nullable; import com.android.internal.graphics.ColorUtils; import com.android.internal.util.ContrastColorUtil; import com.android.systemui.Dumpable; -import com.android.systemui.common.shared.colors.SurfaceEffectColors; import com.android.systemui.res.R; import com.android.systemui.statusbar.notification.shared.NotificationAddXOnHoverToDismiss; import com.android.systemui.util.DrawableDumpKt; @@ -156,14 +155,6 @@ public class NotificationBackgroundView extends View implements Dumpable, mBgIsColorized = b; } - /** Sets if the background should be opaque. */ - public void setForceOpaque(boolean forceOpaque) { - mForceOpaque = forceOpaque; - if (notificationRowTransparency()) { - updateBaseLayerColor(); - } - } - private Path calculateDismissButtonCutoutPath(Rect backgroundBounds) { // TODO(b/365585705): Adapt to RTL after the UX design is finalized. @@ -327,10 +318,7 @@ public class NotificationBackgroundView extends View implements Dumpable, // For colorized notifications, this uses a color that matches the tint color at 90% alpha. int color = isColorized() ? ColorUtils.setAlphaComponent(mTintColor, (int) (MAX_ALPHA * 0.9f)) - : SurfaceEffectColors.surfaceEffect1(getContext()); - if (mForceOpaque) { - color = ColorUtils.setAlphaComponent(color, MAX_ALPHA); - } + : mNormalColor; getBaseBackgroundLayer().setColorFilter( new PorterDuffColorFilter( color, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java index 51569c1596ef..3ffc052b5acc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java @@ -59,7 +59,6 @@ import com.android.systemui.statusbar.notification.NmSummarizationUiFlag; import com.android.systemui.statusbar.notification.NotificationUtils; 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; @@ -1006,10 +1005,6 @@ public class NotificationContentInflater implements NotificationRowContentBinder entry.setPromotedNotificationContentModel(result.mPromotedContent); } - if (PromotedNotificationUiForceExpanded.isEnabled()) { - row.setPromotedOngoing(entry.isOngoingPromoted()); - } - boolean setRepliesAndActions = true; if ((reInflateFlags & FLAG_CONTENT_VIEW_CONTRACTED) != 0) { if (result.inflatedContentView != null) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt index 482b315aa14d..b1c145e08777 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt @@ -53,7 +53,6 @@ import com.android.systemui.statusbar.notification.NmSummarizationUiFlag import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.logKey import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractor -import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiForceExpanded import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel import com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_CONTRACTED import com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_EXPANDED @@ -1293,6 +1292,7 @@ constructor( runningInflations, e, row, + entry, callback, logger, "applying view synchronously", @@ -1318,6 +1318,7 @@ constructor( runningInflations, InflationException(invalidReason), row, + entry, callback, logger, "applied invalid view", @@ -1377,6 +1378,7 @@ constructor( runningInflations, e, row, + entry, callback, logger, "applying view", @@ -1480,6 +1482,7 @@ constructor( runningInflations: HashMap<Int, CancellationSignal>, e: Exception, notification: ExpandableNotificationRow?, + entry: NotificationEntry, callback: InflationCallback?, logger: NotificationRowContentBinderLogger, logContext: String, @@ -1487,7 +1490,7 @@ constructor( Assert.isMainThread() logger.logAsyncTaskException(notification?.loggingKey, logContext, e) runningInflations.values.forEach(Consumer { obj: CancellationSignal -> obj.cancel() }) - callback?.handleInflationException(notification?.entry, e) + callback?.handleInflationException(entry, e) } /** @@ -1520,10 +1523,6 @@ constructor( entry.promotedNotificationContentModel = result.promotedContent } - if (PromotedNotificationUiForceExpanded.isEnabled) { - row.setPromotedOngoing(entry.isOngoingPromoted()) - } - result.inflatedSmartReplyState?.let { row.privateLayout.setInflatedSmartReplyState(it) } setContentViewsFromRemoteViews( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt index f00c3ae20e30..53728c7da62d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt @@ -89,7 +89,8 @@ data class ActiveNotificationModel( init { if (!PromotedNotificationContentModel.featureFlagEnabled()) { if (promotedContent != null) { - Log.wtf(TAG, "passing non-null promoted content without feature flag enabled") + // TODO(b/401018545): convert to Log.wtf and fix tests (see: ag/32114199) + Log.e(TAG, "passing non-null promoted content without feature flag enabled") } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java index 9aa4c54c4292..e4e56c5de65b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java @@ -58,10 +58,10 @@ import com.android.systemui.keyguard.shared.model.KeyguardState; import com.android.systemui.keyguard.shared.model.TransitionState; import com.android.systemui.keyguard.shared.model.TransitionStep; import com.android.systemui.log.SessionTracker; +import com.android.systemui.media.NotificationMediaManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.res.R; import com.android.systemui.scene.shared.model.Scenes; -import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.policy.KeyguardStateController; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index fc721bfae369..a4ee4ad6f6ec 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -140,6 +140,7 @@ import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.WakefulnessLifecycle; +import com.android.systemui.media.NotificationMediaManager; import com.android.systemui.navigationbar.NavigationBarController; import com.android.systemui.navigationbar.views.NavigationBarView; import com.android.systemui.notetask.NoteTaskController; @@ -191,7 +192,6 @@ import com.android.systemui.statusbar.LiftReveal; import com.android.systemui.statusbar.LightRevealScrim; import com.android.systemui.statusbar.LockscreenShadeTransitionController; import com.android.systemui.statusbar.NotificationLockscreenUserManager; -import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationPresenter; import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.NotificationShadeDepthController; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java index 74b1c3bbfd77..2c8866fef030 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java @@ -38,6 +38,7 @@ import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.InitController; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor; +import com.android.systemui.media.NotificationMediaManager; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.ActivityStarter.OnDismissAction; import com.android.systemui.power.domain.interactor.PowerInteractor; @@ -50,7 +51,6 @@ import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.LockscreenShadeTransitionController; import com.android.systemui.statusbar.NotificationLockscreenUserManager; -import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationPresenter; import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.NotificationShadeWindowController; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt index b6628926dc4b..61b7d80a8c85 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt @@ -25,7 +25,6 @@ import android.view.View import androidx.annotation.VisibleForTesting import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.CoreStartable -import com.android.systemui.Dumpable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main @@ -162,6 +161,10 @@ constructor( notificationKey = currentInfo.key, appName = currentInfo.appName, promotedContent = currentInfo.promotedContent, + // [hasOngoingCall()] filters out the case in which the call is ongoing but the app + // is visible (we issue [OngoingCallModel.NoCall] below in that case), so this can + // be safely made false. + isAppVisible = false, ) } else { return OngoingCallModel.NoCall diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractor.kt index d6ca656356e3..bed9e7cf4646 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractor.kt @@ -51,9 +51,7 @@ import kotlinx.coroutines.flow.stateIn * This class monitors call notifications and the visibility of call apps to determine the * appropriate chip state. It emits: * * - [OngoingCallModel.NoCall] when there is no call notification - * * - [OngoingCallModel.InCallWithVisibleApp] when there is a call notification but the call app is - * visible - * * - [OngoingCallModel.InCall] when there is a call notification and the call app is not visible + * * - [OngoingCallModel.InCall] when there is a call notification */ @SysUISingleton class OngoingCallInteractor @@ -85,12 +83,14 @@ constructor( initialValue = OngoingCallModel.NoCall, ) + // TODO(b/400720280): maybe put this inside [OngoingCallModel]. @VisibleForTesting val isStatusBarRequiredForOngoingCall = combine(ongoingCallState, isChipSwipedAway) { callState, chipSwipedAway -> - callState is OngoingCallModel.InCall && !chipSwipedAway + callState.willCallChipBeVisible() && !chipSwipedAway } + // TODO(b/400720280): maybe put this inside [OngoingCallModel]. @VisibleForTesting val isGestureListeningEnabled = combine( @@ -98,9 +98,12 @@ constructor( statusBarModeRepositoryStore.defaultDisplay.isInFullscreenMode, isChipSwipedAway, ) { callState, isFullscreen, chipSwipedAway -> - callState is OngoingCallModel.InCall && !chipSwipedAway && isFullscreen + callState.willCallChipBeVisible() && !chipSwipedAway && isFullscreen } + private fun OngoingCallModel.willCallChipBeVisible() = + this is OngoingCallModel.InCall && !isAppVisible + private fun createOngoingCallStateFlow( notification: ActiveNotificationModel? ): Flow<OngoingCallModel> { @@ -147,34 +150,23 @@ constructor( model: ActiveNotificationModel, isVisible: Boolean, ): OngoingCallModel { - return when { - isVisible -> { - logger.d({ "Call app is visible: uid=$int1" }) { int1 = model.uid } - OngoingCallModel.InCallWithVisibleApp( - startTimeMs = model.whenTime, - notificationIconView = model.statusBarChipIconView, - intent = model.contentIntent, - notificationKey = model.key, - appName = model.appName, - promotedContent = model.promotedContent, - ) - } - - else -> { - logger.d({ "Active call detected: startTime=$long1 hasIcon=$bool1" }) { - long1 = model.whenTime - bool1 = model.statusBarChipIconView != null - } - OngoingCallModel.InCall( - startTimeMs = model.whenTime, - notificationIconView = model.statusBarChipIconView, - intent = model.contentIntent, - notificationKey = model.key, - appName = model.appName, - promotedContent = model.promotedContent, - ) - } + logger.d({ + "Active call detected: uid=$int1 startTime=$long1 hasIcon=$bool1 isAppVisible=$bool2" + }) { + int1 = model.uid + long1 = model.whenTime + bool1 = model.statusBarChipIconView != null + bool2 = isVisible } + return OngoingCallModel.InCall( + startTimeMs = model.whenTime, + notificationIconView = model.statusBarChipIconView, + intent = model.contentIntent, + notificationKey = model.key, + appName = model.appName, + promotedContent = model.promotedContent, + isAppVisible = isVisible, + ) } private fun setStatusBarRequiredForOngoingCall(statusBarRequired: Boolean) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt index 62f0ba032f36..322dfff8f144 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt @@ -26,25 +26,6 @@ sealed interface OngoingCallModel { data object NoCall : OngoingCallModel /** - * There is an ongoing call but the call app is currently visible, so we don't need to show the - * chip. - * - * @property startTimeMs see [InCall.startTimeMs]. - * @property notificationIconView see [InCall.notificationIconView]. - * @property intent see [InCall.intent]. - * @property appName see [InCall.appName]. - * @property promotedContent see [InCall.promotedContent]. - */ - data class InCallWithVisibleApp( - val startTimeMs: Long, - val notificationIconView: StatusBarIconView?, - val intent: PendingIntent?, - val notificationKey: String, - val appName: String, - val promotedContent: PromotedNotificationContentModel?, - ) : OngoingCallModel - - /** * There *is* an ongoing call. * * @property startTimeMs the time that the phone call started, based on the notification's @@ -58,6 +39,7 @@ sealed interface OngoingCallModel { * @property appName the user-readable name of the app that posted the call notification. * @property promotedContent if the call notification also meets promoted notification criteria, * this field is filled in with the content related to promotion. Otherwise null. + * @property isAppVisible whether the app to which the call belongs is currently visible. */ data class InCall( val startTimeMs: Long, @@ -66,5 +48,6 @@ sealed interface OngoingCallModel { val notificationKey: String, val appName: String, val promotedContent: PromotedNotificationContentModel?, + val isAppVisible: Boolean, ) : OngoingCallModel } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt index 29528502aa03..db1977b3ff45 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt @@ -45,6 +45,7 @@ import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.Mobil import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileConnectionsRepositoryKairosImpl import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractorImpl +import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractorKairosAdapter import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractorKairosImpl import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel @@ -92,8 +93,10 @@ import kotlinx.coroutines.flow.Flow DemoModeMobileConnectionDataSourceKairosImpl.Module::class, MobileRepositorySwitcherKairos.Module::class, MobileConnectionsRepositoryKairosImpl.Module::class, + MobileIconsInteractorKairosImpl.Module::class, MobileConnectionRepositoryKairosFactoryImpl.Module::class, MobileConnectionsRepositoryKairosAdapter.Module::class, + MobileIconsInteractorKairosAdapter.Module::class, ] ) abstract class StatusBarPipelineModule { @@ -171,7 +174,7 @@ abstract class StatusBarPipelineModule { @Provides fun mobileIconsInteractor( impl: Provider<MobileIconsInteractorImpl>, - kairosImpl: Provider<MobileIconsInteractorKairosImpl>, + kairosImpl: Provider<MobileIconsInteractorKairosAdapter>, ): MobileIconsInteractor { return if (Flags.statusBarMobileIconKairos()) { kairosImpl.get() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorKairos.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorKairos.kt index 4580ad974b29..a9399593973b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorKairos.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorKairos.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,38 +22,37 @@ import com.android.settingslib.SignalIcon.MobileIconGroup import com.android.settingslib.graph.SignalDrawable import com.android.settingslib.mobile.MobileIconCarrierIdOverrides import com.android.settingslib.mobile.MobileIconCarrierIdOverridesImpl -import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.KairosBuilder +import com.android.systemui.kairos.ExperimentalKairosApi +import com.android.systemui.kairos.State +import com.android.systemui.kairos.combine +import com.android.systemui.kairos.flatMap +import com.android.systemui.kairos.map +import com.android.systemui.kairosBuilder import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.log.table.logDiffsForTable import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState.Connected import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository +import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepositoryKairos import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel.DefaultIcon import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel.OverriddenIcon import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel import com.android.systemui.statusbar.pipeline.satellite.ui.model.SatelliteIconModel import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.flatMapLatest -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.stateIn +@ExperimentalKairosApi interface MobileIconInteractorKairos { /** The table log created for this connection */ val tableLogBuffer: TableLogBuffer /** The current mobile data activity */ - val activity: Flow<DataActivityModel> + val activity: State<DataActivityModel> /** See [MobileConnectionsRepository.mobileIsDefault]. */ - val mobileIsDefault: Flow<Boolean> + val mobileIsDefault: State<Boolean> /** * True when telephony tells us that the data state is CONNECTED. See @@ -61,31 +60,31 @@ interface MobileIconInteractorKairos { * consider this connection to be serving data, and thus want to show a network type icon, when * data is connected. Other data connection states would typically cause us not to show the icon */ - val isDataConnected: StateFlow<Boolean> + val isDataConnected: State<Boolean> /** True if we consider this connection to be in service, i.e. can make calls */ - val isInService: StateFlow<Boolean> + val isInService: State<Boolean> /** True if this connection is emergency only */ - val isEmergencyOnly: StateFlow<Boolean> + val isEmergencyOnly: State<Boolean> /** Observable for the data enabled state of this connection */ - val isDataEnabled: StateFlow<Boolean> + val isDataEnabled: State<Boolean> /** True if the RAT icon should always be displayed and false otherwise. */ - val alwaysShowDataRatIcon: StateFlow<Boolean> + val alwaysShowDataRatIcon: State<Boolean> /** Canonical representation of the current mobile signal strength as a triangle. */ - val signalLevelIcon: StateFlow<SignalIconModel> + val signalLevelIcon: State<SignalIconModel> /** Observable for RAT type (network type) indicator */ - val networkTypeIconGroup: StateFlow<NetworkTypeIconModel> + val networkTypeIconGroup: State<NetworkTypeIconModel> /** Whether or not to show the slice attribution */ - val showSliceAttribution: StateFlow<Boolean> + val showSliceAttribution: State<Boolean> /** True if this connection is satellite-based */ - val isNonTerrestrial: StateFlow<Boolean> + val isNonTerrestrial: State<Boolean> /** * Provider name for this network connection. The name can be one of 3 values: @@ -95,7 +94,7 @@ interface MobileIconInteractorKairos { * override in [connectionInfo.operatorAlphaShort], a value that is derived from * [ServiceState] */ - val networkName: StateFlow<NetworkNameModel> + val networkName: State<NetworkNameModel> /** * Provider name for this network connection. The name can be one of 3 values: @@ -108,119 +107,110 @@ interface MobileIconInteractorKairos { * TODO(b/296600321): De-duplicate this field with [networkName] after determining the data * provided is identical */ - val carrierName: StateFlow<String> + val carrierName: State<String> /** True if there is only one active subscription. */ - val isSingleCarrier: StateFlow<Boolean> + val isSingleCarrier: State<Boolean> /** * True if this connection is considered roaming. The roaming bit can come from [ServiceState], * or directly from the telephony manager's CDMA ERI number value. Note that we don't consider a * connection to be roaming while carrier network change is active */ - val isRoaming: StateFlow<Boolean> + val isRoaming: State<Boolean> /** See [MobileIconsInteractor.isForceHidden]. */ - val isForceHidden: Flow<Boolean> + val isForceHidden: State<Boolean> /** See [MobileConnectionRepository.isAllowedDuringAirplaneMode]. */ - val isAllowedDuringAirplaneMode: StateFlow<Boolean> + val isAllowedDuringAirplaneMode: State<Boolean> /** True when in carrier network change mode */ - val carrierNetworkChangeActive: StateFlow<Boolean> + val carrierNetworkChangeActive: State<Boolean> } /** Interactor for a single mobile connection. This connection _should_ have one subscription ID */ -@Suppress("EXPERIMENTAL_IS_NOT_ENABLED") +@ExperimentalKairosApi class MobileIconInteractorKairosImpl( - @Background scope: CoroutineScope, - defaultSubscriptionHasDataEnabled: StateFlow<Boolean>, - override val alwaysShowDataRatIcon: StateFlow<Boolean>, - alwaysUseCdmaLevel: StateFlow<Boolean>, - override val isSingleCarrier: StateFlow<Boolean>, - override val mobileIsDefault: StateFlow<Boolean>, - defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>>, - defaultMobileIconGroup: StateFlow<MobileIconGroup>, - isDefaultConnectionFailed: StateFlow<Boolean>, - override val isForceHidden: Flow<Boolean>, - connectionRepository: MobileConnectionRepository, + defaultSubscriptionHasDataEnabled: State<Boolean>, + override val alwaysShowDataRatIcon: State<Boolean>, + alwaysUseCdmaLevel: State<Boolean>, + override val isSingleCarrier: State<Boolean>, + override val mobileIsDefault: State<Boolean>, + defaultMobileIconMapping: State<Map<String, MobileIconGroup>>, + defaultMobileIconGroup: State<MobileIconGroup>, + isDefaultConnectionFailed: State<Boolean>, + override val isForceHidden: State<Boolean>, + private val connectionRepository: MobileConnectionRepositoryKairos, private val context: Context, - val carrierIdOverrides: MobileIconCarrierIdOverrides = MobileIconCarrierIdOverridesImpl(), -) : MobileIconInteractor, MobileIconInteractorKairos { - override val tableLogBuffer: TableLogBuffer = connectionRepository.tableLogBuffer + private val carrierIdOverrides: MobileIconCarrierIdOverrides = + MobileIconCarrierIdOverridesImpl(), +) : MobileIconInteractorKairos, KairosBuilder by kairosBuilder() { + override val tableLogBuffer: TableLogBuffer + get() = connectionRepository.tableLogBuffer - override val activity = connectionRepository.dataActivityDirection + override val activity: State<DataActivityModel> + get() = connectionRepository.dataActivityDirection - override val isDataEnabled: StateFlow<Boolean> = connectionRepository.dataEnabled + override val isDataEnabled: State<Boolean> = connectionRepository.dataEnabled - override val carrierNetworkChangeActive: StateFlow<Boolean> = - connectionRepository.carrierNetworkChangeActive + override val carrierNetworkChangeActive: State<Boolean> + get() = connectionRepository.carrierNetworkChangeActive // True if there exists _any_ icon override for this carrierId. Note that overrides can include // any or none of the icon groups defined in MobileMappings, so we still need to check on a // per-network-type basis whether or not the given icon group is overridden - private val carrierIdIconOverrideExists = - connectionRepository.carrierId - .map { carrierIdOverrides.carrierIdEntryExists(it) } - .distinctUntilChanged() - .stateIn(scope, SharingStarted.WhileSubscribed(), false) + private val carrierIdIconOverrideExists: State<Boolean> = + connectionRepository.carrierId.map { carrierIdOverrides.carrierIdEntryExists(it) } - override val networkName = + override val networkName: State<NetworkNameModel> = combine(connectionRepository.operatorAlphaShort, connectionRepository.networkName) { - operatorAlphaShort, - networkName -> - if (networkName is NetworkNameModel.Default && operatorAlphaShort != null) { - NetworkNameModel.IntentDerived(operatorAlphaShort) - } else { - networkName - } + operatorAlphaShort, + networkName -> + if (networkName is NetworkNameModel.Default && operatorAlphaShort != null) { + NetworkNameModel.IntentDerived(operatorAlphaShort) + } else { + networkName } - .stateIn( - scope, - SharingStarted.WhileSubscribed(), - connectionRepository.networkName.value, - ) + } - override val carrierName = + override val carrierName: State<String> = combine(connectionRepository.operatorAlphaShort, connectionRepository.carrierName) { - operatorAlphaShort, - networkName -> - if (networkName is NetworkNameModel.Default && operatorAlphaShort != null) { - operatorAlphaShort - } else { - networkName.name - } + operatorAlphaShort, + networkName -> + if (networkName is NetworkNameModel.Default && operatorAlphaShort != null) { + operatorAlphaShort + } else { + networkName.name } - .stateIn( - scope, - SharingStarted.WhileSubscribed(), - connectionRepository.carrierName.value.name, - ) + } /** What the mobile icon would be before carrierId overrides */ - private val defaultNetworkType: StateFlow<MobileIconGroup> = + private val defaultNetworkType: State<MobileIconGroup> = combine( - connectionRepository.resolvedNetworkType, - defaultMobileIconMapping, - defaultMobileIconGroup, - ) { resolvedNetworkType, mapping, defaultGroup -> - when (resolvedNetworkType) { - is ResolvedNetworkType.CarrierMergedNetworkType -> - resolvedNetworkType.iconGroupOverride - else -> { - mapping[resolvedNetworkType.lookupKey] ?: defaultGroup - } + connectionRepository.resolvedNetworkType, + defaultMobileIconMapping, + defaultMobileIconGroup, + ) { resolvedNetworkType, mapping, defaultGroup -> + when (resolvedNetworkType) { + is ResolvedNetworkType.CarrierMergedNetworkType -> + resolvedNetworkType.iconGroupOverride + + else -> { + mapping[resolvedNetworkType.lookupKey] ?: defaultGroup } } - .stateIn(scope, SharingStarted.WhileSubscribed(), defaultMobileIconGroup.value) + } - override val networkTypeIconGroup = - combine(defaultNetworkType, carrierIdIconOverrideExists) { networkType, overrideExists -> + override val networkTypeIconGroup: State<NetworkTypeIconModel> = buildState { + combineTransactionally(defaultNetworkType, carrierIdIconOverrideExists) { + networkType, + overrideExists -> // DefaultIcon comes out of the icongroup lookup, we check for overrides here if (overrideExists) { val iconOverride = carrierIdOverrides.getOverrideFor( - connectionRepository.carrierId.value, + connectionRepository.carrierId.sample(), networkType.name, context.resources, ) @@ -233,106 +223,101 @@ class MobileIconInteractorKairosImpl( DefaultIcon(networkType) } } - .distinctUntilChanged() - .logDiffsForTable( - tableLogBuffer = tableLogBuffer, - initialValue = DefaultIcon(defaultMobileIconGroup.value), - ) - .stateIn( - scope, - SharingStarted.WhileSubscribed(), - DefaultIcon(defaultMobileIconGroup.value), - ) + .also { logDiffsForTable(it, tableLogBuffer = tableLogBuffer) } + } - override val showSliceAttribution: StateFlow<Boolean> = + override val showSliceAttribution: State<Boolean> = combine( - connectionRepository.allowNetworkSliceIndicator, - connectionRepository.hasPrioritizedNetworkCapabilities, - ) { allowed, hasPrioritizedNetworkCapabilities -> - allowed && hasPrioritizedNetworkCapabilities - } - .stateIn(scope, SharingStarted.WhileSubscribed(), false) + connectionRepository.allowNetworkSliceIndicator, + connectionRepository.hasPrioritizedNetworkCapabilities, + ) { allowed, hasPrioritizedNetworkCapabilities -> + allowed && hasPrioritizedNetworkCapabilities + } - override val isNonTerrestrial: StateFlow<Boolean> = connectionRepository.isNonTerrestrial + override val isNonTerrestrial: State<Boolean> + get() = connectionRepository.isNonTerrestrial - override val isRoaming: StateFlow<Boolean> = + override val isRoaming: State<Boolean> = combine( - connectionRepository.carrierNetworkChangeActive, - connectionRepository.isGsm, - connectionRepository.isRoaming, - connectionRepository.cdmaRoaming, - ) { carrierNetworkChangeActive, isGsm, isRoaming, cdmaRoaming -> - if (carrierNetworkChangeActive) { - false - } else if (isGsm) { - isRoaming - } else { - cdmaRoaming - } + connectionRepository.carrierNetworkChangeActive, + connectionRepository.isGsm, + connectionRepository.isRoaming, + connectionRepository.cdmaRoaming, + ) { carrierNetworkChangeActive, isGsm, isRoaming, cdmaRoaming -> + if (carrierNetworkChangeActive) { + false + } else if (isGsm) { + isRoaming + } else { + cdmaRoaming } - .stateIn(scope, SharingStarted.WhileSubscribed(), false) + } - private val level: StateFlow<Int> = + private val level: State<Int> = combine( - connectionRepository.isGsm, - connectionRepository.primaryLevel, - connectionRepository.cdmaLevel, - alwaysUseCdmaLevel, - ) { isGsm, primaryLevel, cdmaLevel, alwaysUseCdmaLevel -> - when { - // GSM connections should never use the CDMA level - isGsm -> primaryLevel - alwaysUseCdmaLevel -> cdmaLevel - else -> primaryLevel - } + connectionRepository.isGsm, + connectionRepository.primaryLevel, + connectionRepository.cdmaLevel, + alwaysUseCdmaLevel, + ) { isGsm, primaryLevel, cdmaLevel, alwaysUseCdmaLevel -> + when { + // GSM connections should never use the CDMA level + isGsm -> primaryLevel + alwaysUseCdmaLevel -> cdmaLevel + else -> primaryLevel } - .stateIn(scope, SharingStarted.WhileSubscribed(), 0) + } - private val numberOfLevels: StateFlow<Int> = connectionRepository.numberOfLevels + private val numberOfLevels: State<Int> + get() = connectionRepository.numberOfLevels - override val isDataConnected: StateFlow<Boolean> = + override val isDataConnected: State<Boolean> = connectionRepository.dataConnectionState .map { it == Connected } - .stateIn(scope, SharingStarted.WhileSubscribed(), false) + .also { + onActivated { logDiffsForTable(it, tableLogBuffer, "icon", "isDataConnected") } + } - override val isInService = connectionRepository.isInService + override val isInService + get() = connectionRepository.isInService - override val isEmergencyOnly: StateFlow<Boolean> = connectionRepository.isEmergencyOnly + override val isEmergencyOnly: State<Boolean> + get() = connectionRepository.isEmergencyOnly - override val isAllowedDuringAirplaneMode = connectionRepository.isAllowedDuringAirplaneMode + override val isAllowedDuringAirplaneMode: State<Boolean> + get() = connectionRepository.isAllowedDuringAirplaneMode /** Whether or not to show the error state of [SignalDrawable] */ - private val showExclamationMark: StateFlow<Boolean> = + private val showExclamationMark: State<Boolean> = combine(defaultSubscriptionHasDataEnabled, isDefaultConnectionFailed, isInService) { - isDefaultDataEnabled, - isDefaultConnectionFailed, - isInService -> - !isDefaultDataEnabled || isDefaultConnectionFailed || !isInService - } - .stateIn(scope, SharingStarted.WhileSubscribed(), true) + isDefaultDataEnabled, + isDefaultConnectionFailed, + isInService -> + !isDefaultDataEnabled || isDefaultConnectionFailed || !isInService + } - private val cellularShownLevel: StateFlow<Int> = + private val cellularShownLevel: State<Int> = combine(level, isInService, connectionRepository.inflateSignalStrength) { - level, - isInService, - inflate -> - if (isInService) { - if (inflate) level + 1 else level - } else 0 + level, + isInService, + inflate -> + when { + !isInService -> 0 + inflate -> level + 1 + else -> level } - .stateIn(scope, SharingStarted.WhileSubscribed(), 0) + } // Satellite level is unaffected by the inflateSignalStrength property // See b/346904529 for details - private val satelliteShownLevel: StateFlow<Int> = + private val satelliteShownLevel: State<Int> = if (Flags.carrierRoamingNbIotNtn()) { - connectionRepository.satelliteLevel - } else { - combine(level, isInService) { level, isInService -> if (isInService) level else 0 } - } - .stateIn(scope, SharingStarted.WhileSubscribed(), 0) + connectionRepository.satelliteLevel + } else { + combine(level, isInService) { level, isInService -> if (isInService) level else 0 } + } - private val cellularIcon: Flow<SignalIconModel.Cellular> = + private val cellularIcon: State<SignalIconModel.Cellular> = combine( cellularShownLevel, numberOfLevels, @@ -347,7 +332,7 @@ class MobileIconInteractorKairosImpl( ) } - private val satelliteIcon: Flow<SignalIconModel.Satellite> = + private val satelliteIcon: State<SignalIconModel.Satellite> = satelliteShownLevel.map { SignalIconModel.Satellite( level = it, @@ -357,24 +342,14 @@ class MobileIconInteractorKairosImpl( ) } - override val signalLevelIcon: StateFlow<SignalIconModel> = run { - val initial = - SignalIconModel.Cellular( - cellularShownLevel.value, - numberOfLevels.value, - showExclamationMark.value, - carrierNetworkChangeActive.value, - ) + override val signalLevelIcon: State<SignalIconModel> = isNonTerrestrial - .flatMapLatest { ntn -> + .flatMap { ntn -> if (ntn) { satelliteIcon } else { cellularIcon } } - .distinctUntilChanged() - .logDiffsForTable(tableLogBuffer, columnPrefix = "icon", initialValue = initial) - .stateIn(scope, SharingStarted.WhileSubscribed(), initial) - } + .also { onActivated { logDiffsForTable(it, tableLogBuffer, columnPrefix = "icon") } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorKairosAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorKairosAdapter.kt new file mode 100644 index 000000000000..a3b9b17257cf --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorKairosAdapter.kt @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.mobile.domain.interactor + +import com.android.systemui.kairos.BuildScope +import com.android.systemui.kairos.ExperimentalKairosApi +import com.android.systemui.kairos.toColdConflatedFlow +import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel +import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel +import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel +import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.StateFlow + +@ExperimentalKairosApi +fun BuildScope.MobileIconInteractorKairosAdapter( + kairosImpl: MobileIconInteractorKairos +): MobileIconInteractor = + with(kairosImpl) { + MobileIconInteractorKairosAdapter( + tableLogBuffer = tableLogBuffer, + activity = activity.toColdConflatedFlow(kairosNetwork), + mobileIsDefault = mobileIsDefault.toColdConflatedFlow(kairosNetwork), + isDataConnected = isDataConnected.toStateFlow(), + isInService = isInService.toStateFlow(), + isEmergencyOnly = isEmergencyOnly.toStateFlow(), + isDataEnabled = isDataEnabled.toStateFlow(), + alwaysShowDataRatIcon = alwaysShowDataRatIcon.toStateFlow(), + signalLevelIcon = signalLevelIcon.toStateFlow(), + networkTypeIconGroup = networkTypeIconGroup.toStateFlow(), + showSliceAttribution = showSliceAttribution.toStateFlow(), + isNonTerrestrial = isNonTerrestrial.toStateFlow(), + networkName = networkName.toStateFlow(), + carrierName = carrierName.toStateFlow(), + isSingleCarrier = isSingleCarrier.toStateFlow(), + isRoaming = isRoaming.toStateFlow(), + isForceHidden = isForceHidden.toColdConflatedFlow(kairosNetwork), + isAllowedDuringAirplaneMode = isAllowedDuringAirplaneMode.toStateFlow(), + carrierNetworkChangeActive = carrierNetworkChangeActive.toStateFlow(), + ) + } + +private class MobileIconInteractorKairosAdapter( + override val tableLogBuffer: TableLogBuffer, + override val activity: Flow<DataActivityModel>, + override val mobileIsDefault: Flow<Boolean>, + override val isDataConnected: StateFlow<Boolean>, + override val isInService: StateFlow<Boolean>, + override val isEmergencyOnly: StateFlow<Boolean>, + override val isDataEnabled: StateFlow<Boolean>, + override val alwaysShowDataRatIcon: StateFlow<Boolean>, + override val signalLevelIcon: StateFlow<SignalIconModel>, + override val networkTypeIconGroup: StateFlow<NetworkTypeIconModel>, + override val showSliceAttribution: StateFlow<Boolean>, + override val isNonTerrestrial: StateFlow<Boolean>, + override val networkName: StateFlow<NetworkNameModel>, + override val carrierName: StateFlow<String>, + override val isSingleCarrier: StateFlow<Boolean>, + override val isRoaming: StateFlow<Boolean>, + override val isForceHidden: Flow<Boolean>, + override val isAllowedDuringAirplaneMode: StateFlow<Boolean>, + override val carrierNetworkChangeActive: StateFlow<Boolean>, +) : MobileIconInteractor diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKairos.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKairos.kt index e8e0a833af2a..14a276b60933 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKairos.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKairos.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,41 +21,47 @@ import android.telephony.CarrierConfigManager import android.telephony.SubscriptionManager import android.telephony.SubscriptionManager.PROFILE_CLASS_PROVISIONING import com.android.settingslib.SignalIcon.MobileIconGroup -import com.android.settingslib.mobile.TelephonyIcons +import com.android.systemui.Flags +import com.android.systemui.KairosActivatable +import com.android.systemui.KairosBuilder +import com.android.systemui.activated import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.flags.FeatureFlagsClassic import com.android.systemui.flags.Flags.FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS +import com.android.systemui.kairos.BuildScope +import com.android.systemui.kairos.ExperimentalKairosApi +import com.android.systemui.kairos.Incremental +import com.android.systemui.kairos.State +import com.android.systemui.kairos.asyncEvent +import com.android.systemui.kairos.buildSpec +import com.android.systemui.kairos.combine +import com.android.systemui.kairos.filter +import com.android.systemui.kairos.flatMap +import com.android.systemui.kairos.flatten +import com.android.systemui.kairos.map +import com.android.systemui.kairos.mapValues +import com.android.systemui.kairos.stateOf +import com.android.systemui.kairosBuilder import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.log.table.logDiffsForTable import com.android.systemui.statusbar.core.NewStatusBarIcons import com.android.systemui.statusbar.core.StatusBarRootModernization import com.android.systemui.statusbar.pipeline.dagger.MobileSummaryLog import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel -import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository -import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository +import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepositoryKairos +import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepositoryKairos import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository import com.android.systemui.statusbar.policy.data.repository.UserSetupRepository import com.android.systemui.util.CarrierConfigTracker -import java.lang.ref.WeakReference +import dagger.Binds +import dagger.Provides +import dagger.multibindings.ElementsIntoSet import javax.inject.Inject -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.ExperimentalCoroutinesApi +import javax.inject.Provider +import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.flatMapLatest -import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.mapLatest -import kotlinx.coroutines.flow.stateIn -import kotlinx.coroutines.flow.transformLatest /** * Business layer logic for the set of mobile subscription icons. @@ -67,98 +73,79 @@ import kotlinx.coroutines.flow.transformLatest * represents each RAT (LTE, 3G, etc.), as well as can produce an interactor for each individual * icon */ +@ExperimentalKairosApi interface MobileIconsInteractorKairos { /** See [MobileConnectionsRepository.mobileIsDefault]. */ - val mobileIsDefault: StateFlow<Boolean> + val mobileIsDefault: State<Boolean> /** List of subscriptions, potentially filtered for CBRS */ - val filteredSubscriptions: Flow<List<SubscriptionModel>> - - /** Subscription ID of the current default data subscription */ - val defaultDataSubId: Flow<Int?> + val filteredSubscriptions: State<List<SubscriptionModel>> /** * The current list of [MobileIconInteractor]s associated with the current list of * [filteredSubscriptions] */ - val icons: StateFlow<List<MobileIconInteractor>> + val icons: Incremental<Int, MobileIconInteractorKairos> /** Whether the mobile icons can be stacked vertically. */ - val isStackable: StateFlow<Boolean> - - /** - * Observable for the subscriptionId of the current mobile data connection. Null if we don't - * have a valid subscription id - */ - val activeMobileDataSubscriptionId: StateFlow<Int?> + val isStackable: State<Boolean> /** True if the active mobile data subscription has data enabled */ - val activeDataConnectionHasDataEnabled: StateFlow<Boolean> + val activeDataConnectionHasDataEnabled: State<Boolean> /** * Flow providing a reference to the Interactor for the active data subId. This represents the - * [MobileIconInteractor] responsible for the active data connection, if any. + * [MobileIconInteractorKairos] responsible for the active data connection, if any. */ - val activeDataIconInteractor: StateFlow<MobileIconInteractor?> + val activeDataIconInteractor: State<MobileIconInteractorKairos?> /** True if the RAT icon should always be displayed and false otherwise. */ - val alwaysShowDataRatIcon: StateFlow<Boolean> + val alwaysShowDataRatIcon: State<Boolean> /** True if the CDMA level should be preferred over the primary level. */ - val alwaysUseCdmaLevel: StateFlow<Boolean> + val alwaysUseCdmaLevel: State<Boolean> /** True if there is only one active subscription. */ - val isSingleCarrier: StateFlow<Boolean> + val isSingleCarrier: State<Boolean> /** The icon mapping from network type to [MobileIconGroup] for the default subscription */ - val defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>> + val defaultMobileIconMapping: State<Map<String, MobileIconGroup>> /** Fallback [MobileIconGroup] in the case where there is no icon in the mapping */ - val defaultMobileIconGroup: StateFlow<MobileIconGroup> + val defaultMobileIconGroup: State<MobileIconGroup> /** True only if the default network is mobile, and validation also failed */ - val isDefaultConnectionFailed: StateFlow<Boolean> + val isDefaultConnectionFailed: State<Boolean> /** True once the user has been set up */ - val isUserSetUp: StateFlow<Boolean> + val isUserSetUp: State<Boolean> /** True if we're configured to force-hide the mobile icons and false otherwise. */ - val isForceHidden: Flow<Boolean> + val isForceHidden: State<Boolean> /** * True if the device-level service state (with -1 subscription id) reports emergency calls * only. This value is only useful when there are no other subscriptions OR all existing * subscriptions report that they are not in service. */ - val isDeviceInEmergencyCallsOnlyMode: Flow<Boolean> - - /** - * Vends out a [MobileIconInteractor] tracking the [MobileConnectionRepository] for the given - * subId. - */ - fun getMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor + val isDeviceInEmergencyCallsOnlyMode: State<Boolean> } -@OptIn(ExperimentalCoroutinesApi::class) -@Suppress("EXPERIMENTAL_IS_NOT_ENABLED") +@ExperimentalKairosApi @SysUISingleton class MobileIconsInteractorKairosImpl @Inject constructor( - private val mobileConnectionsRepo: MobileConnectionsRepository, + private val mobileConnectionsRepo: MobileConnectionsRepositoryKairos, private val carrierConfigTracker: CarrierConfigTracker, @MobileSummaryLog private val tableLogger: TableLogBuffer, connectivityRepository: ConnectivityRepository, userSetupRepo: UserSetupRepository, - @Background private val scope: CoroutineScope, private val context: Context, private val featureFlagsClassic: FeatureFlagsClassic, -) : MobileIconsInteractor, MobileIconsInteractorKairos { - - // Weak reference lookup for created interactors - private val reuseCache = mutableMapOf<Int, WeakReference<MobileIconInteractor>>() +) : MobileIconsInteractorKairos, KairosBuilder by kairosBuilder() { - override val mobileIsDefault = + override val mobileIsDefault: State<Boolean> = combine( mobileConnectionsRepo.mobileIsDefault, mobileConnectionsRepo.hasCarrierMergedConnection, @@ -167,47 +154,36 @@ constructor( // the `isDefault` calculation. See b/272586234. mobileIsDefault || hasCarrierMergedConnection } - .logDiffsForTable( - tableLogger, - LOGGING_PREFIX, - columnName = "mobileIsDefault", - initialValue = false, - ) - .stateIn(scope, SharingStarted.WhileSubscribed(), false) - - override val activeMobileDataSubscriptionId: StateFlow<Int?> = - mobileConnectionsRepo.activeMobileDataSubscriptionId - - override val activeDataConnectionHasDataEnabled: StateFlow<Boolean> = - mobileConnectionsRepo.activeMobileDataRepository - .flatMapLatest { it?.dataEnabled ?: flowOf(false) } - .stateIn(scope, SharingStarted.WhileSubscribed(), false) - - override val activeDataIconInteractor: StateFlow<MobileIconInteractor?> = - mobileConnectionsRepo.activeMobileDataSubscriptionId - .mapLatest { - if (it != null) { - getMobileConnectionInteractorForSubId(it) - } else { - null + .also { + onActivated { + logDiffsForTable( + it, + tableLogger, + LOGGING_PREFIX, + columnName = "mobileIsDefault", + ) } } - .stateIn(scope, SharingStarted.WhileSubscribed(), null) - private val unfilteredSubscriptions: Flow<List<SubscriptionModel>> = - mobileConnectionsRepo.subscriptions + override val activeDataConnectionHasDataEnabled: State<Boolean> = + mobileConnectionsRepo.activeMobileDataRepository.flatMap { + it?.dataEnabled ?: stateOf(false) + } + + private val unfilteredSubscriptions: State<Collection<SubscriptionModel>> + get() = mobileConnectionsRepo.subscriptions /** Any filtering that we can do based purely on the info of each subscription individually. */ - private val subscriptionsBasedFilteredSubs = - unfilteredSubscriptions - .map { it.filterBasedOnProvisioning().filterBasedOnNtn() } - .distinctUntilChanged() + private val subscriptionsBasedFilteredSubs: State<List<SubscriptionModel>> = + unfilteredSubscriptions.map { + it.asSequence().filterBasedOnProvisioning().filterBasedOnNtn().toList() + } - private fun List<SubscriptionModel>.filterBasedOnProvisioning(): List<SubscriptionModel> = + private fun Sequence<SubscriptionModel>.filterBasedOnProvisioning() = if (!featureFlagsClassic.isEnabled(FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS)) { this } else { - this.filter { it.profileClass != PROFILE_CLASS_PROVISIONING } + filter { it.profileClass != PROFILE_CLASS_PROVISIONING } } /** @@ -219,9 +195,10 @@ constructor( * need to filter out those subscriptions here so we guarantee the subscription never turns into * an icon. See b/336881301. */ - private fun List<SubscriptionModel>.filterBasedOnNtn(): List<SubscriptionModel> { - return this.filter { !it.isExclusivelyNonTerrestrial } - } + private fun Sequence<SubscriptionModel>.filterBasedOnNtn(): Sequence<SubscriptionModel> = + filter { + !it.isExclusivelyNonTerrestrial + } /** * Generally, SystemUI wants to show iconography for each subscription that is listed by @@ -236,22 +213,23 @@ constructor( * [CarrierConfigManager.KEY_ALWAYS_SHOW_PRIMARY_SIGNAL_BAR_IN_OPPORTUNISTIC_NETWORK_BOOLEAN], * and by checking which subscription is opportunistic, or which one is active. */ - override val filteredSubscriptions: Flow<List<SubscriptionModel>> = + override val filteredSubscriptions: State<List<SubscriptionModel>> = buildState { combine( subscriptionsBasedFilteredSubs, mobileConnectionsRepo.activeMobileDataSubscriptionId, - connectivityRepository.vcnSubId, + connectivityRepository.vcnSubId.toState(), ) { preFilteredSubs, activeId, vcnSubId -> filterSubsBasedOnOpportunistic(preFilteredSubs, activeId, vcnSubId) } - .distinctUntilChanged() - .logDiffsForTable( - tableLogger, - LOGGING_PREFIX, - columnName = "filteredSubscriptions", - initialValue = listOf(), - ) - .stateIn(scope, SharingStarted.WhileSubscribed(), listOf()) + .also { + logDiffsForTable( + it, + tableLogger, + LOGGING_PREFIX, + columnName = "filteredSubscriptions", + ) + } + } private fun filterSubsBasedOnOpportunistic( subList: List<SubscriptionModel>, @@ -298,19 +276,25 @@ constructor( } } - override val defaultDataSubId = mobileConnectionsRepo.defaultDataSubId - - override val icons = - filteredSubscriptions - .mapLatest { subs -> - subs.map { getMobileConnectionInteractorForSubId(it.subscriptionId) } + override val icons: Incremental<Int, MobileIconInteractorKairos> = buildIncremental { + val filteredSubIds = + filteredSubscriptions.map { it.asSequence().map { sub -> sub.subscriptionId }.toSet() } + mobileConnectionsRepo.mobileConnectionsBySubId + .filterIncrementally { (subId, _) -> + // Filter out repo if subId is not present in the filtered set + filteredSubIds.map { subId in it } } - .stateIn(scope, SharingStarted.WhileSubscribed(), emptyList()) + // Just map the repos to interactors + .mapValues { (subId, repo) -> buildSpec { mobileConnection(repo) } } + .applyLatestSpecForKey() + } - override val isStackable = + override val isStackable: State<Boolean> = if (NewStatusBarIcons.isEnabled && StatusBarRootModernization.isEnabled) { - icons.flatMapLatest { icons -> - combine(icons.map { it.signalLevelIcon }) { signalLevelIcons -> + icons.flatMap { iconsBySubId: Map<Int, MobileIconInteractorKairos> -> + iconsBySubId.values + .map { it.signalLevelIcon } + .combine { signalLevelIcons -> // These are only stackable if: // - They are cellular // - There's exactly two @@ -319,11 +303,15 @@ constructor( it.size == 2 && it[0].numberOfLevels == it[1].numberOfLevels } } - } - } else { - flowOf(false) } - .stateIn(scope, SharingStarted.WhileSubscribed(), false) + } else { + stateOf(false) + } + + override val activeDataIconInteractor: State<MobileIconInteractorKairos?> = + combine(mobileConnectionsRepo.activeMobileDataSubscriptionId, icons) { activeSubId, icons -> + activeSubId?.let { icons[activeSubId] } + } /** * Copied from the old pipeline. We maintain a 2s period of time where we will keep the @@ -335,67 +323,59 @@ constructor( * * The goal of this is to minimize the flickering in the UI of the cellular indicator */ - private val forcingCellularValidation = + private val forcingCellularValidation: State<Boolean> = buildState { mobileConnectionsRepo.activeSubChangedInGroupEvent - .filter { mobileConnectionsRepo.defaultConnectionIsValidated.value } - .transformLatest { - emit(true) - delay(2000) - emit(false) + .filter(mobileConnectionsRepo.defaultConnectionIsValidated) + .mapLatestBuild { + asyncEvent { + delay(2.seconds) + false + } + .holdState(true) + } + .holdState(stateOf(false)) + .flatten() + .also { + logDiffsForTable(it, tableLogger, LOGGING_PREFIX, columnName = "forcingValidation") } - .logDiffsForTable( - tableLogger, - LOGGING_PREFIX, - columnName = "forcingValidation", - initialValue = false, - ) - .stateIn(scope, SharingStarted.WhileSubscribed(), false) + } /** * Mapping from network type to [MobileIconGroup] using the config generated for the default * subscription Id. This mapping is the same for every subscription. */ - override val defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>> = - mobileConnectionsRepo.defaultMobileIconMapping.stateIn( - scope, - SharingStarted.WhileSubscribed(), - initialValue = mapOf(), - ) + override val defaultMobileIconMapping: State<Map<String, MobileIconGroup>> + get() = mobileConnectionsRepo.defaultMobileIconMapping - override val alwaysShowDataRatIcon: StateFlow<Boolean> = - mobileConnectionsRepo.defaultDataSubRatConfig - .mapLatest { it.alwaysShowDataRatIcon } - .stateIn(scope, SharingStarted.WhileSubscribed(), false) + override val alwaysShowDataRatIcon: State<Boolean> = + mobileConnectionsRepo.defaultDataSubRatConfig.map { it.alwaysShowDataRatIcon } - override val alwaysUseCdmaLevel: StateFlow<Boolean> = - mobileConnectionsRepo.defaultDataSubRatConfig - .mapLatest { it.alwaysShowCdmaRssi } - .stateIn(scope, SharingStarted.WhileSubscribed(), false) + override val alwaysUseCdmaLevel: State<Boolean> = + mobileConnectionsRepo.defaultDataSubRatConfig.map { it.alwaysShowCdmaRssi } - override val isSingleCarrier: StateFlow<Boolean> = + override val isSingleCarrier: State<Boolean> = mobileConnectionsRepo.subscriptions .map { it.size == 1 } - .logDiffsForTable( - tableLogger, - columnPrefix = LOGGING_PREFIX, - columnName = "isSingleCarrier", - initialValue = false, - ) - .stateIn(scope, SharingStarted.WhileSubscribed(), false) + .also { + onActivated { + logDiffsForTable( + it, + tableLogger, + columnPrefix = LOGGING_PREFIX, + columnName = "isSingleCarrier", + ) + } + } /** If there is no mapping in [defaultMobileIconMapping], then use this default icon group */ - override val defaultMobileIconGroup: StateFlow<MobileIconGroup> = - mobileConnectionsRepo.defaultMobileIconGroup.stateIn( - scope, - SharingStarted.WhileSubscribed(), - initialValue = TelephonyIcons.G, - ) + override val defaultMobileIconGroup: State<MobileIconGroup> + get() = mobileConnectionsRepo.defaultMobileIconGroup /** * We want to show an error state when cellular has actually failed to validate, but not if some * other transport type is active, because then we expect there not to be validation. */ - override val isDefaultConnectionFailed: StateFlow<Boolean> = + override val isDefaultConnectionFailed: State<Boolean> = combine( mobileIsDefault, mobileConnectionsRepo.defaultConnectionIsValidated, @@ -407,46 +387,63 @@ constructor( else -> !defaultConnectionIsValidated } } - .logDiffsForTable( - tableLogger, - LOGGING_PREFIX, - columnName = "isDefaultConnectionFailed", - initialValue = false, - ) - .stateIn(scope, SharingStarted.WhileSubscribed(), false) - - override val isUserSetUp: StateFlow<Boolean> = userSetupRepo.isUserSetUp - - override val isForceHidden: Flow<Boolean> = - connectivityRepository.forceHiddenSlots - .map { it.contains(ConnectivitySlot.MOBILE) } - .stateIn(scope, SharingStarted.WhileSubscribed(), false) - - override val isDeviceInEmergencyCallsOnlyMode: Flow<Boolean> = - mobileConnectionsRepo.isDeviceEmergencyCallCapable - - /** Vends out new [MobileIconInteractor] for a particular subId */ - override fun getMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor = - reuseCache[subId]?.get() ?: createMobileConnectionInteractorForSubId(subId) - - private fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor = - MobileIconInteractorImpl( - scope, - activeDataConnectionHasDataEnabled, - alwaysShowDataRatIcon, - alwaysUseCdmaLevel, - isSingleCarrier, - mobileIsDefault, - defaultMobileIconMapping, - defaultMobileIconGroup, - isDefaultConnectionFailed, - isForceHidden, - mobileConnectionsRepo.getRepoForSubId(subId), - context, - ) - .also { reuseCache[subId] = WeakReference(it) } + .also { + onActivated { + logDiffsForTable( + it, + tableLogger, + LOGGING_PREFIX, + columnName = "isDefaultConnectionFailed", + ) + } + } + + override val isUserSetUp: State<Boolean> = buildState { userSetupRepo.isUserSetUp.toState() } + + override val isForceHidden: State<Boolean> = buildState { + connectivityRepository.forceHiddenSlots.toState().map { + it.contains(ConnectivitySlot.MOBILE) + } + } + + override val isDeviceInEmergencyCallsOnlyMode: State<Boolean> + get() = mobileConnectionsRepo.isDeviceEmergencyCallCapable + + /** Vends out a new [MobileIconInteractorKairos] for a particular subId */ + private fun BuildScope.mobileConnection( + repo: MobileConnectionRepositoryKairos + ): MobileIconInteractorKairos = activated { + MobileIconInteractorKairosImpl( + activeDataConnectionHasDataEnabled, + alwaysShowDataRatIcon, + alwaysUseCdmaLevel, + isSingleCarrier, + mobileIsDefault, + defaultMobileIconMapping, + defaultMobileIconGroup, + isDefaultConnectionFailed, + isForceHidden, + repo, + context, + ) + } companion object { private const val LOGGING_PREFIX = "Intr" } + + @dagger.Module + interface Module { + + @Binds fun bindImpl(impl: MobileIconsInteractorKairosImpl): MobileIconsInteractorKairos + + companion object { + @Provides + @ElementsIntoSet + fun kairosActivatable( + impl: Provider<MobileIconsInteractorKairosImpl> + ): Set<@JvmSuppressWildcards KairosActivatable> = + if (Flags.statusBarMobileIconKairos()) setOf(impl.get()) else emptySet() + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKairosAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKairosAdapter.kt new file mode 100644 index 000000000000..87877b3e9f43 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKairosAdapter.kt @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.mobile.domain.interactor + +import android.content.Context +import com.android.settingslib.SignalIcon +import com.android.settingslib.mobile.MobileMappings +import com.android.systemui.Flags +import com.android.systemui.KairosActivatable +import com.android.systemui.KairosBuilder +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.kairos.ExperimentalKairosApi +import com.android.systemui.kairos.KairosNetwork +import com.android.systemui.kairos.buildSpec +import com.android.systemui.kairos.combine +import com.android.systemui.kairos.map +import com.android.systemui.kairos.mapValues +import com.android.systemui.kairos.toColdConflatedFlow +import com.android.systemui.kairosBuilder +import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel +import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository +import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepositoryKairos +import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy +import com.android.systemui.statusbar.policy.data.repository.UserSetupRepository +import dagger.Provides +import dagger.multibindings.ElementsIntoSet +import javax.inject.Inject +import javax.inject.Provider +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.stateIn + +@ExperimentalKairosApi +@SysUISingleton +class MobileIconsInteractorKairosAdapter +@Inject +constructor( + private val kairosInteractor: MobileIconsInteractorKairos, + private val repo: MobileConnectionsRepository, + repoK: MobileConnectionsRepositoryKairos, + kairosNetwork: KairosNetwork, + @Application scope: CoroutineScope, + context: Context, + mobileMappingsProxy: MobileMappingsProxy, + private val userSetupRepo: UserSetupRepository, +) : MobileIconsInteractor, KairosBuilder by kairosBuilder() { + + private val interactorsBySubIdK = buildIncremental { + kairosInteractor.icons + .mapValues { (subId, interactor) -> + buildSpec { MobileIconInteractorKairosAdapter(interactor) } + } + .applyLatestSpecForKey() + } + + private val interactorsBySubId = + interactorsBySubIdK + .toColdConflatedFlow(kairosNetwork) + .stateIn(scope, SharingStarted.Eagerly, emptyMap()) + + override val defaultDataSubId: Flow<Int?> + get() = repo.defaultDataSubId + + override val mobileIsDefault: StateFlow<Boolean> = + kairosInteractor.mobileIsDefault + .toColdConflatedFlow(kairosNetwork) + .stateIn(scope, SharingStarted.WhileSubscribed(), repo.mobileIsDefault.value) + + override val filteredSubscriptions: Flow<List<SubscriptionModel>> = + kairosInteractor.filteredSubscriptions.toColdConflatedFlow(kairosNetwork) + + override val icons: StateFlow<List<MobileIconInteractor>> = + interactorsBySubIdK + .map { it.values.toList() } + .toColdConflatedFlow(kairosNetwork) + .stateIn(scope, SharingStarted.WhileSubscribed(), emptyList()) + + override val isStackable: StateFlow<Boolean> = + kairosInteractor.isStackable + .toColdConflatedFlow(kairosNetwork) + .stateIn(scope, SharingStarted.WhileSubscribed(), false) + + override val activeMobileDataSubscriptionId: StateFlow<Int?> + get() = repo.activeMobileDataSubscriptionId + + override val activeDataConnectionHasDataEnabled: StateFlow<Boolean> = + kairosInteractor.activeDataConnectionHasDataEnabled + .toColdConflatedFlow(kairosNetwork) + .stateIn(scope, SharingStarted.WhileSubscribed(), false) + + override val activeDataIconInteractor: StateFlow<MobileIconInteractor?> = + combine(repoK.activeMobileDataSubscriptionId, interactorsBySubIdK) { subId, interactors -> + interactors[subId] + } + .toColdConflatedFlow(kairosNetwork) + .stateIn(scope, SharingStarted.WhileSubscribed(), null) + + override val alwaysShowDataRatIcon: StateFlow<Boolean> = + kairosInteractor.alwaysShowDataRatIcon + .toColdConflatedFlow(kairosNetwork) + .stateIn(scope, SharingStarted.WhileSubscribed(), false) + + override val alwaysUseCdmaLevel: StateFlow<Boolean> = + kairosInteractor.alwaysUseCdmaLevel + .toColdConflatedFlow(kairosNetwork) + .stateIn(scope, SharingStarted.WhileSubscribed(), false) + + override val isSingleCarrier: StateFlow<Boolean> = + kairosInteractor.isSingleCarrier + .toColdConflatedFlow(kairosNetwork) + .stateIn(scope, SharingStarted.WhileSubscribed(), false) + + override val defaultMobileIconMapping: StateFlow<Map<String, SignalIcon.MobileIconGroup>> = + kairosInteractor.defaultMobileIconMapping + .toColdConflatedFlow(kairosNetwork) + .stateIn(scope, SharingStarted.WhileSubscribed(), emptyMap()) + + override val defaultMobileIconGroup: StateFlow<SignalIcon.MobileIconGroup> = + kairosInteractor.defaultMobileIconGroup + .toColdConflatedFlow(kairosNetwork) + .stateIn( + scope, + SharingStarted.WhileSubscribed(), + mobileMappingsProxy.getDefaultIcons(MobileMappings.Config.readConfig(context)), + ) + + override val isDefaultConnectionFailed: StateFlow<Boolean> = + kairosInteractor.isDefaultConnectionFailed + .toColdConflatedFlow(kairosNetwork) + .stateIn(scope, SharingStarted.WhileSubscribed(), false) + + override val isUserSetUp: StateFlow<Boolean> + get() = userSetupRepo.isUserSetUp + + override val isForceHidden: Flow<Boolean> = + kairosInteractor.isForceHidden + .toColdConflatedFlow(kairosNetwork) + .stateIn(scope, SharingStarted.WhileSubscribed(), false) + + override val isDeviceInEmergencyCallsOnlyMode: Flow<Boolean> + get() = repo.isDeviceEmergencyCallCapable + + override fun getMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor = + interactorsBySubId.value[subId] ?: error("Unknown subscription id: $subId") + + @dagger.Module + object Module { + @Provides + @ElementsIntoSet + fun kairosActivatable( + impl: Provider<MobileIconsInteractorKairosAdapter> + ): Set<@JvmSuppressWildcards KairosActivatable> = + if (Flags.statusBarMobileIconKairos()) setOf(impl.get()) else emptySet() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModelKairos.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModelKairos.kt new file mode 100644 index 000000000000..fce8c85338f3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModelKairos.kt @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel + +import android.graphics.Color +import com.android.systemui.statusbar.phone.StatusBarLocation +import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractor +import com.android.systemui.statusbar.pipeline.mobile.ui.VerboseMobileViewLogger +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.stateIn + +/** + * A view model for an individual mobile icon that embeds the notion of a [StatusBarLocation]. This + * allows the mobile icon to change some view parameters at different locations + * + * @param commonImpl for convenience, this class wraps a base interface that can provides all of the + * common implementations between locations. See [MobileIconViewModel] + * @property location the [StatusBarLocation] of this VM. + * @property verboseLogger an optional logger to log extremely verbose view updates. + */ +abstract class LocationBasedMobileViewModelKairos( + val commonImpl: MobileIconViewModelCommonKairos, + val location: StatusBarLocation, + val verboseLogger: VerboseMobileViewLogger?, +) : MobileIconViewModelCommonKairos by commonImpl { + val defaultColor: Int = Color.WHITE + + companion object { + fun viewModelForLocation( + commonImpl: MobileIconViewModelCommon, + interactor: MobileIconInteractor, + verboseMobileViewLogger: VerboseMobileViewLogger, + location: StatusBarLocation, + scope: CoroutineScope, + ): LocationBasedMobileViewModel = + when (location) { + StatusBarLocation.HOME -> + HomeMobileIconViewModel(commonImpl, verboseMobileViewLogger) + StatusBarLocation.KEYGUARD -> KeyguardMobileIconViewModel(commonImpl) + StatusBarLocation.QS -> QsMobileIconViewModel(commonImpl) + StatusBarLocation.SHADE_CARRIER_GROUP -> + ShadeCarrierGroupMobileIconViewModel(commonImpl, interactor, scope) + } + } +} + +class HomeMobileIconViewModelKairos( + commonImpl: MobileIconViewModelCommonKairos, + verboseMobileViewLogger: VerboseMobileViewLogger, +) : + MobileIconViewModelCommonKairos, + LocationBasedMobileViewModelKairos( + commonImpl, + location = StatusBarLocation.HOME, + verboseMobileViewLogger, + ) + +class QsMobileIconViewModelKairos(commonImpl: MobileIconViewModelCommonKairos) : + MobileIconViewModelCommonKairos, + LocationBasedMobileViewModelKairos( + commonImpl, + location = StatusBarLocation.QS, + // Only do verbose logging for the Home location. + verboseLogger = null, + ) + +class ShadeCarrierGroupMobileIconViewModelKairos( + commonImpl: MobileIconViewModelCommonKairos, + interactor: MobileIconInteractor, + scope: CoroutineScope, +) : + MobileIconViewModelCommonKairos, + LocationBasedMobileViewModelKairos( + commonImpl, + location = StatusBarLocation.SHADE_CARRIER_GROUP, + // Only do verbose logging for the Home location. + verboseLogger = null, + ) { + private val isSingleCarrier = interactor.isSingleCarrier + val carrierName = interactor.carrierName + + override val isVisible: StateFlow<Boolean> = + combine(super.isVisible, isSingleCarrier) { isVisible, isSingleCarrier -> + if (isSingleCarrier) false else isVisible + } + .stateIn(scope, SharingStarted.WhileSubscribed(), super.isVisible.value) +} + +class KeyguardMobileIconViewModelKairos(commonImpl: MobileIconViewModelCommonKairos) : + MobileIconViewModelCommonKairos, + LocationBasedMobileViewModelKairos( + commonImpl, + location = StatusBarLocation.KEYGUARD, + // Only do verbose logging for the Home location. + verboseLogger = null, + ) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelKairos.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelKairos.kt new file mode 100644 index 000000000000..cc7fc0964dae --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelKairos.kt @@ -0,0 +1,328 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel + +import com.android.systemui.Flags.statusBarStaticInoutIndicators +import com.android.systemui.common.shared.model.ContentDescription +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.log.table.logDiffsForTable +import com.android.systemui.res.R +import com.android.systemui.statusbar.core.NewStatusBarIcons +import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor +import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractor +import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor +import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel +import com.android.systemui.statusbar.pipeline.mobile.ui.model.MobileContentDescription +import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants +import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.mapLatest +import kotlinx.coroutines.flow.stateIn + +/** Common interface for all of the location-based mobile icon view models. */ +interface MobileIconViewModelCommonKairos : MobileIconViewModelCommon { + override val subscriptionId: Int + /** True if this view should be visible at all. */ + override val isVisible: StateFlow<Boolean> + override val icon: Flow<SignalIconModel> + override val contentDescription: Flow<MobileContentDescription?> + override val roaming: Flow<Boolean> + /** The RAT icon (LTE, 3G, 5G, etc) to be displayed. Null if we shouldn't show anything */ + override val networkTypeIcon: Flow<Icon.Resource?> + /** The slice attribution. Drawn as a background layer */ + override val networkTypeBackground: StateFlow<Icon.Resource?> + override val activityInVisible: Flow<Boolean> + override val activityOutVisible: Flow<Boolean> + override val activityContainerVisible: Flow<Boolean> +} + +/** + * View model for the state of a single mobile icon. Each [MobileIconViewModel] will keep watch over + * a single line of service via [MobileIconInteractor] and update the UI based on that + * subscription's information. + * + * There will be exactly one [MobileIconViewModel] per filtered subscription offered from + * [MobileIconsInteractor.filteredSubscriptions]. + * + * For the sake of keeping log spam in check, every flow funding the [MobileIconViewModelCommon] + * interface is implemented as a [StateFlow]. This ensures that each location-based mobile icon view + * model gets the exact same information, as well as allows us to log that unified state only once + * per icon. + */ +class MobileIconViewModelKairos( + override val subscriptionId: Int, + iconInteractor: MobileIconInteractor, + airplaneModeInteractor: AirplaneModeInteractor, + constants: ConnectivityConstants, + scope: CoroutineScope, +) : MobileIconViewModelCommonKairos { + private val cellProvider by lazy { + CellularIconViewModelKairos( + subscriptionId, + iconInteractor, + airplaneModeInteractor, + constants, + scope, + ) + } + + private val satelliteProvider by lazy { + CarrierBasedSatelliteViewModelKairosImpl( + subscriptionId, + airplaneModeInteractor, + iconInteractor, + scope, + ) + } + + /** + * Similar to repository switching, this allows us to split up the logic of satellite/cellular + * states, since they are different by nature + */ + private val vmProvider: Flow<MobileIconViewModelCommon> = + iconInteractor.isNonTerrestrial + .mapLatest { nonTerrestrial -> + if (nonTerrestrial) { + satelliteProvider + } else { + cellProvider + } + } + .stateIn(scope, SharingStarted.WhileSubscribed(), cellProvider) + + override val isVisible: StateFlow<Boolean> = + vmProvider + .flatMapLatest { it.isVisible } + .stateIn(scope, SharingStarted.WhileSubscribed(), false) + + override val icon: Flow<SignalIconModel> = vmProvider.flatMapLatest { it.icon } + + override val contentDescription: Flow<MobileContentDescription?> = + vmProvider.flatMapLatest { it.contentDescription } + + override val roaming: Flow<Boolean> = vmProvider.flatMapLatest { it.roaming } + + override val networkTypeIcon: Flow<Icon.Resource?> = + vmProvider.flatMapLatest { it.networkTypeIcon } + + override val networkTypeBackground: StateFlow<Icon.Resource?> = + vmProvider + .flatMapLatest { it.networkTypeBackground } + .stateIn(scope, SharingStarted.WhileSubscribed(), null) + + override val activityInVisible: Flow<Boolean> = + vmProvider.flatMapLatest { it.activityInVisible } + + override val activityOutVisible: Flow<Boolean> = + vmProvider.flatMapLatest { it.activityOutVisible } + + override val activityContainerVisible: Flow<Boolean> = + vmProvider.flatMapLatest { it.activityContainerVisible } +} + +/** Representation of this network when it is non-terrestrial (e.g., satellite) */ +private class CarrierBasedSatelliteViewModelKairosImpl( + override val subscriptionId: Int, + airplaneModeInteractor: AirplaneModeInteractor, + interactor: MobileIconInteractor, + scope: CoroutineScope, +) : MobileIconViewModelCommon, MobileIconViewModelCommonKairos { + override val isVisible: StateFlow<Boolean> = + airplaneModeInteractor.isAirplaneMode + .map { !it } + .stateIn(scope, SharingStarted.WhileSubscribed(), false) + + override val icon: Flow<SignalIconModel> = interactor.signalLevelIcon + + override val contentDescription: Flow<MobileContentDescription?> = MutableStateFlow(null) + + /** These fields are not used for satellite icons currently */ + override val roaming: Flow<Boolean> = flowOf(false) + override val networkTypeIcon: Flow<Icon.Resource?> = flowOf(null) + override val networkTypeBackground: StateFlow<Icon.Resource?> = MutableStateFlow(null) + override val activityInVisible: Flow<Boolean> = flowOf(false) + override val activityOutVisible: Flow<Boolean> = flowOf(false) + override val activityContainerVisible: Flow<Boolean> = flowOf(false) +} + +/** Terrestrial (cellular) icon. */ +@Suppress("EXPERIMENTAL_IS_NOT_ENABLED") +private class CellularIconViewModelKairos( + override val subscriptionId: Int, + iconInteractor: MobileIconInteractor, + airplaneModeInteractor: AirplaneModeInteractor, + constants: ConnectivityConstants, + scope: CoroutineScope, +) : MobileIconViewModelCommon, MobileIconViewModelCommonKairos { + override val isVisible: StateFlow<Boolean> = + if (!constants.hasDataCapabilities) { + flowOf(false) + } else { + combine( + airplaneModeInteractor.isAirplaneMode, + iconInteractor.isAllowedDuringAirplaneMode, + iconInteractor.isForceHidden, + ) { isAirplaneMode, isAllowedDuringAirplaneMode, isForceHidden -> + if (isForceHidden) { + false + } else if (isAirplaneMode) { + isAllowedDuringAirplaneMode + } else { + true + } + } + } + .distinctUntilChanged() + .logDiffsForTable( + iconInteractor.tableLogBuffer, + columnName = "visible", + initialValue = false, + ) + .stateIn(scope, SharingStarted.WhileSubscribed(), false) + + override val icon: Flow<SignalIconModel> = iconInteractor.signalLevelIcon + + override val contentDescription: Flow<MobileContentDescription?> = + combine(iconInteractor.signalLevelIcon, iconInteractor.networkName) { icon, nameModel -> + when (icon) { + is SignalIconModel.Cellular -> + MobileContentDescription.Cellular( + nameModel.name, + icon.levelDescriptionRes(), + ) + else -> null + } + } + .stateIn(scope, SharingStarted.WhileSubscribed(), null) + + private fun SignalIconModel.Cellular.levelDescriptionRes() = + when (level) { + 0 -> R.string.accessibility_no_signal + 1 -> R.string.accessibility_one_bar + 2 -> R.string.accessibility_two_bars + 3 -> R.string.accessibility_three_bars + 4 -> { + if (numberOfLevels == 6) { + R.string.accessibility_four_bars + } else { + R.string.accessibility_signal_full + } + } + 5 -> { + if (numberOfLevels == 6) { + R.string.accessibility_signal_full + } else { + R.string.accessibility_no_signal + } + } + else -> R.string.accessibility_no_signal + } + + private val showNetworkTypeIcon: Flow<Boolean> = + combine( + iconInteractor.isDataConnected, + iconInteractor.isDataEnabled, + iconInteractor.alwaysShowDataRatIcon, + iconInteractor.mobileIsDefault, + iconInteractor.carrierNetworkChangeActive, + ) { dataConnected, dataEnabled, alwaysShow, mobileIsDefault, carrierNetworkChange -> + alwaysShow || + (!carrierNetworkChange && (dataEnabled && dataConnected && mobileIsDefault)) + } + .distinctUntilChanged() + .logDiffsForTable( + iconInteractor.tableLogBuffer, + columnName = "showNetworkTypeIcon", + initialValue = false, + ) + .stateIn(scope, SharingStarted.WhileSubscribed(), false) + + override val networkTypeIcon: Flow<Icon.Resource?> = + combine(iconInteractor.networkTypeIconGroup, showNetworkTypeIcon) { + networkTypeIconGroup, + shouldShow -> + val desc = + if (networkTypeIconGroup.contentDescription != 0) + ContentDescription.Resource(networkTypeIconGroup.contentDescription) + else null + val icon = + if (networkTypeIconGroup.iconId != 0) + Icon.Resource(networkTypeIconGroup.iconId, desc) + else null + return@combine when { + !shouldShow -> null + else -> icon + } + } + .distinctUntilChanged() + .stateIn(scope, SharingStarted.WhileSubscribed(), null) + + override val networkTypeBackground = + iconInteractor.showSliceAttribution + .map { + when { + it && NewStatusBarIcons.isEnabled -> + Icon.Resource(R.drawable.mobile_network_type_background_updated, null) + it -> Icon.Resource(R.drawable.mobile_network_type_background, null) + else -> null + } + } + .stateIn(scope, SharingStarted.WhileSubscribed(), null) + + override val roaming: StateFlow<Boolean> = + iconInteractor.isRoaming + .logDiffsForTable( + iconInteractor.tableLogBuffer, + columnName = "roaming", + initialValue = false, + ) + .stateIn(scope, SharingStarted.WhileSubscribed(), false) + + private val activity: Flow<DataActivityModel?> = + if (!constants.shouldShowActivityConfig) { + flowOf(null) + } else { + iconInteractor.activity + } + + override val activityInVisible: Flow<Boolean> = + activity + .map { it?.hasActivityIn ?: false } + .stateIn(scope, SharingStarted.WhileSubscribed(), false) + + override val activityOutVisible: Flow<Boolean> = + activity + .map { it?.hasActivityOut ?: false } + .stateIn(scope, SharingStarted.WhileSubscribed(), false) + + override val activityContainerVisible: Flow<Boolean> = + if (statusBarStaticInoutIndicators()) { + flowOf(constants.shouldShowActivityConfig) + } else { + activity.map { it != null && (it.hasActivityIn || it.hasActivityOut) } + } + .stateIn(scope, SharingStarted.WhileSubscribed(), false) +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelKairos.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelKairos.kt new file mode 100644 index 000000000000..a65540738828 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelKairos.kt @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel + +import androidx.annotation.VisibleForTesting +import com.android.app.tracing.coroutines.launchTraced as launch +import com.android.systemui.coroutines.newTracingContext +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.statusbar.phone.StatusBarLocation +import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor +import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor +import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger +import com.android.systemui.statusbar.pipeline.mobile.ui.VerboseMobileViewLogger +import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView +import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants +import java.util.concurrent.ConcurrentHashMap +import javax.inject.Inject +import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.cancel +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.mapLatest +import kotlinx.coroutines.flow.stateIn + +/** + * View model for describing the system's current mobile cellular connections. The result is a list + * of [MobileIconViewModel]s which describe the individual icons and can be bound to + * [ModernStatusBarMobileView]. + */ +@SysUISingleton +class MobileIconsViewModelKairos +@Inject +constructor( + val logger: MobileViewLogger, + private val verboseLogger: VerboseMobileViewLogger, + private val interactor: MobileIconsInteractor, + private val airplaneModeInteractor: AirplaneModeInteractor, + private val constants: ConnectivityConstants, + @Background private val scope: CoroutineScope, +) { + @VisibleForTesting + val reuseCache = ConcurrentHashMap<Int, Pair<MobileIconViewModel, CoroutineScope>>() + + val activeMobileDataSubscriptionId: StateFlow<Int?> = interactor.activeMobileDataSubscriptionId + + val subscriptionIdsFlow: StateFlow<List<Int>> = + interactor.filteredSubscriptions + .mapLatest { subscriptions -> + subscriptions.map { subscriptionModel -> subscriptionModel.subscriptionId } + } + .stateIn(scope, SharingStarted.WhileSubscribed(), listOf()) + + val mobileSubViewModels: StateFlow<List<MobileIconViewModelCommon>> = + subscriptionIdsFlow + .map { ids -> ids.map { commonViewModelForSub(it) } } + .stateIn(scope, SharingStarted.WhileSubscribed(), emptyList()) + + private val firstMobileSubViewModel: StateFlow<MobileIconViewModelCommon?> = + mobileSubViewModels + .map { + if (it.isEmpty()) { + null + } else { + // Mobile icons get reversed by [StatusBarIconController], so the last element + // in this list will show up visually first. + it.last() + } + } + .stateIn(scope, SharingStarted.WhileSubscribed(), null) + + /** + * A flow that emits `true` if the mobile sub that's displayed first visually is showing its + * network type icon and `false` otherwise. + */ + val firstMobileSubShowingNetworkTypeIcon: StateFlow<Boolean> = + firstMobileSubViewModel + .flatMapLatest { firstMobileSubViewModel -> + firstMobileSubViewModel?.networkTypeIcon?.map { it != null } ?: flowOf(false) + } + .stateIn(scope, SharingStarted.WhileSubscribed(), false) + + val isStackable: StateFlow<Boolean> = interactor.isStackable + + init { + scope.launch { subscriptionIdsFlow.collect { invalidateCaches(it) } } + } + + fun viewModelForSub(subId: Int, location: StatusBarLocation): LocationBasedMobileViewModel { + val common = commonViewModelForSub(subId) + return LocationBasedMobileViewModel.viewModelForLocation( + common, + interactor.getMobileConnectionInteractorForSubId(subId), + verboseLogger, + location, + scope, + ) + } + + private fun commonViewModelForSub(subId: Int): MobileIconViewModelCommon { + return reuseCache.getOrPut(subId) { createViewModel(subId) }.first + } + + private fun createViewModel(subId: Int): Pair<MobileIconViewModel, CoroutineScope> { + // Create a child scope so we can cancel it + val vmScope = scope.createChildScope(newTracingContext("MobileIconViewModel")) + val vm = + MobileIconViewModel( + subId, + interactor.getMobileConnectionInteractorForSubId(subId), + airplaneModeInteractor, + constants, + vmScope, + ) + + return Pair(vm, vmScope) + } + + private fun CoroutineScope.createChildScope(extraContext: CoroutineContext) = + CoroutineScope(coroutineContext + Job(coroutineContext[Job]) + extraContext) + + private fun invalidateCaches(subIds: List<Int>) { + reuseCache.keys + .filter { !subIds.contains(it) } + .forEach { id -> + reuseCache + .remove(id) + // Cancel the view model's scope after removing it + ?.second + ?.cancel() + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelKairos.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelKairos.kt new file mode 100644 index 000000000000..2dbb02c8f095 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelKairos.kt @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel + +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.lifecycle.ExclusiveActivatable +import com.android.systemui.lifecycle.Hydrator +import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf + +@OptIn(ExperimentalCoroutinesApi::class) +class StackedMobileIconViewModelKairos +@AssistedInject +constructor(mobileIconsViewModel: MobileIconsViewModel) : ExclusiveActivatable() { + private val hydrator = Hydrator("StackedMobileIconViewModel") + + private val isStackable: Boolean by + hydrator.hydratedStateOf( + traceName = "isStackable", + source = mobileIconsViewModel.isStackable, + initialValue = false, + ) + + private val iconViewModelFlow: Flow<List<MobileIconViewModelCommon>> = + combine( + mobileIconsViewModel.mobileSubViewModels, + mobileIconsViewModel.activeMobileDataSubscriptionId, + ) { viewModels, activeSubId -> + // Sort to get the active subscription first, if it's set + viewModels.sortedByDescending { it.subscriptionId == activeSubId } + } + + val dualSim: DualSim? by + hydrator.hydratedStateOf( + traceName = "dualSim", + source = + iconViewModelFlow.flatMapLatest { viewModels -> + combine(viewModels.map { it.icon }) { icons -> + icons + .toList() + .filterIsInstance<SignalIconModel.Cellular>() + .takeIf { it.size == 2 } + ?.let { DualSim(it[0], it[1]) } + } + }, + initialValue = null, + ) + + val networkTypeIcon: Icon.Resource? by + hydrator.hydratedStateOf( + traceName = "networkTypeIcon", + source = + iconViewModelFlow.flatMapLatest { viewModels -> + viewModels.firstOrNull()?.networkTypeIcon ?: flowOf(null) + }, + initialValue = null, + ) + + val isIconVisible: Boolean by derivedStateOf { isStackable && dualSim != null } + + override suspend fun onActivated(): Nothing { + hydrator.activate() + } + + @AssistedFactory + interface Factory { + fun create(): StackedMobileIconViewModelKairos + } + + data class DualSim( + val primary: SignalIconModel.Cellular, + val secondary: SignalIconModel.Cellular, + ) +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt index cfd50973924d..6fada264e397 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt @@ -203,6 +203,8 @@ constructor( OngoingActivityChipBinder.createBinding( view.requireViewById(R.id.ongoing_activity_chip_secondary) ) + OngoingActivityChipBinder.updateTypefaces(primaryChipViewBinding) + OngoingActivityChipBinder.updateTypefaces(secondaryChipViewBinding) launch { combine( viewModel.ongoingActivityChipsLegacy, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java index 2b0bf1a3d343..f1f2b88e9943 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java @@ -461,6 +461,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene } } } + unregisterBackCallback(); if (logClose) { mUiEventLogger.logWithInstanceId( @@ -558,11 +559,6 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene @Override public void onVisibilityAggregated(boolean isVisible) { - if (isVisible) { - registerBackCallback(); - } else { - unregisterBackCallback(); - } super.onVisibilityAggregated(isVisible); mEditText.setEnabled(isVisible && !mSending); } @@ -623,6 +619,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene setAttachment(mEntry.remoteInputAttachment); updateSendButton(); + registerBackCallback(); } public void onNotificationUpdateOrReset() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt index d3af1e5b65fe..6d959be1c5f4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.policy import android.app.ActivityOptions +import android.app.Flags.notificationsRedesignTemplates import android.app.Notification import android.app.Notification.Action.SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY import android.app.PendingIntent @@ -53,7 +54,6 @@ import com.android.systemui.statusbar.SmartReplyController import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.headsup.HeadsUpManager import com.android.systemui.statusbar.notification.logging.NotificationLogger -import com.android.systemui.statusbar.notification.row.MagicActionBackgroundDrawable import com.android.systemui.statusbar.phone.KeyguardDismissUtil import com.android.systemui.statusbar.policy.InflatedSmartReplyState.SuppressedActions import com.android.systemui.statusbar.policy.SmartReplyView.SmartActions @@ -397,16 +397,21 @@ constructor( delayOnClickListener: Boolean, packageContext: Context, ): Button { - val isMagicAction = Flags.notificationMagicActionsTreatment() && + val isMagicAction = + Flags.notificationMagicActionsTreatment() && smartActions.fromAssistant && action.extras.getBoolean(Notification.Action.EXTRA_IS_MAGIC, false) - val layoutRes = if (isMagicAction) { - R.layout.magic_action_button - } else { - R.layout.smart_action_button - } - return (LayoutInflater.from(parent.context).inflate(layoutRes, parent, false) - as Button) + val layoutRes = + if (isMagicAction) { + R.layout.magic_action_button + } else { + if (notificationsRedesignTemplates()) { + R.layout.notification_2025_smart_action_button + } else { + R.layout.smart_action_button + } + } + return (LayoutInflater.from(parent.context).inflate(layoutRes, parent, false) as Button) .apply { text = action.title @@ -435,7 +440,6 @@ constructor( // Mark this as an Action button (layoutParams as SmartReplyView.LayoutParams).mButtonType = SmartButtonType.ACTION } - } private fun onSmartActionClick( @@ -499,9 +503,11 @@ constructor( replyIndex: Int, choice: CharSequence, delayOnClickListener: Boolean, - ): Button = - (LayoutInflater.from(parent.context).inflate(R.layout.smart_reply_button, parent, false) - as Button) + ): Button { + val layoutRes = + if (notificationsRedesignTemplates()) R.layout.notification_2025_smart_reply_button + else R.layout.smart_reply_button + return (LayoutInflater.from(parent.context).inflate(layoutRes, parent, false) as Button) .apply { text = choice val onClickListener = @@ -531,6 +537,7 @@ constructor( // Mark this as a Reply button (layoutParams as SmartReplyView.LayoutParams).mButtonType = SmartButtonType.REPLY } + } private fun onSmartReplyClick( entry: NotificationEntry, diff --git a/packages/SystemUI/src/com/android/systemui/topwindoweffects/TopLevelWindowEffects.kt b/packages/SystemUI/src/com/android/systemui/topwindoweffects/TopLevelWindowEffects.kt new file mode 100644 index 000000000000..c11e4c507914 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/topwindoweffects/TopLevelWindowEffects.kt @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.topwindoweffects; + +import android.content.Context +import android.graphics.PixelFormat +import android.view.Gravity +import android.view.WindowInsets +import android.view.WindowManager +import com.android.app.viewcapture.ViewCaptureAwareWindowManager +import com.android.systemui.CoreStartable +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor +import com.android.systemui.topwindoweffects.domain.interactor.SqueezeEffectInteractor +import com.android.systemui.topwindoweffects.ui.compose.EffectsWindowRoot +import com.android.systemui.topwindoweffects.ui.viewmodel.SqueezeEffectViewModel +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch +import javax.inject.Inject + +@SysUISingleton +class TopLevelWindowEffects @Inject constructor( + @Application private val context: Context, + @Application private val applicationScope: CoroutineScope, + private val windowManager: ViewCaptureAwareWindowManager, + private val squeezeEffectInteractor: SqueezeEffectInteractor, + private val keyEventInteractor: KeyEventInteractor, + private val viewModelFactory: SqueezeEffectViewModel.Factory +) : CoreStartable { + + override fun start() { + applicationScope.launch { + var root: EffectsWindowRoot? = null + squeezeEffectInteractor.isSqueezeEffectEnabled.collectLatest { enabled -> + // TODO: move window ops to a separate UI thread + if (enabled) { + keyEventInteractor.isPowerButtonDown.collectLatest { down -> + // TODO: ignore new window creation when ignoring short power press duration + if (down && root == null) { + root = EffectsWindowRoot( + context = context, + viewModelFactory = viewModelFactory, + onEffectFinished = { + if (root?.isAttachedToWindow == true) { + windowManager.removeView(root) + root = null + } + } + ) + root?.let { + windowManager.addView(it, getWindowManagerLayoutParams()) + } + } + } + } + } + } + } + + private fun getWindowManagerLayoutParams(): WindowManager.LayoutParams { + val lp = WindowManager.LayoutParams( + WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY, + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + or WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL + or WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN + or WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE, + PixelFormat.TRANSPARENT + ) + + lp.privateFlags = lp.privateFlags or + (WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS + or WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION + or WindowManager.LayoutParams.PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED + or WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED + or WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY) + + lp.layoutInDisplayCutoutMode = + WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS + + lp.title = "TopLevelWindowEffects" + lp.fitInsetsTypes = WindowInsets.Type.systemOverlays() + lp.gravity = Gravity.TOP + + return lp + } +} diff --git a/packages/SystemUI/src/com/android/systemui/topwindoweffects/dagger/SqueezeEffectRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/topwindoweffects/dagger/SqueezeEffectRepositoryModule.kt new file mode 100644 index 000000000000..5a2af9228771 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/topwindoweffects/dagger/SqueezeEffectRepositoryModule.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.topwindoweffects.dagger + +import com.android.systemui.topwindoweffects.data.repository.SqueezeEffectRepository +import com.android.systemui.topwindoweffects.data.repository.SqueezeEffectRepositoryImpl +import dagger.Binds +import dagger.Module + +@Module +interface SqueezeEffectRepositoryModule { + + @Binds + fun squeezeEffectRepository( + squeezeEffectRepositoryImpl: SqueezeEffectRepositoryImpl + ) : SqueezeEffectRepository +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/topwindoweffects/dagger/TopLevelWindowEffectsModule.kt b/packages/SystemUI/src/com/android/systemui/topwindoweffects/dagger/TopLevelWindowEffectsModule.kt new file mode 100644 index 000000000000..6fbfedc94774 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/topwindoweffects/dagger/TopLevelWindowEffectsModule.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.topwindoweffects.dagger + +import com.android.systemui.CoreStartable +import com.android.systemui.topwindoweffects.TopLevelWindowEffects +import dagger.Binds +import dagger.Module +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap + +@Module +interface TopLevelWindowEffectsModule { + + @Binds + @IntoMap + @ClassKey(TopLevelWindowEffects::class) + fun bindTopLevelWindowEffectsCoreStartable(impl: TopLevelWindowEffects): CoreStartable +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/topwindoweffects/data/repository/SqueezeEffectRepository.kt b/packages/SystemUI/src/com/android/systemui/topwindoweffects/data/repository/SqueezeEffectRepository.kt new file mode 100644 index 000000000000..9e0b25633d5f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/topwindoweffects/data/repository/SqueezeEffectRepository.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.topwindoweffects.data.repository + +import kotlinx.coroutines.flow.Flow + +interface SqueezeEffectRepository { + val isSqueezeEffectEnabled: Flow<Boolean> +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/topwindoweffects/data/repository/SqueezeEffectRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/topwindoweffects/data/repository/SqueezeEffectRepositoryImpl.kt new file mode 100644 index 000000000000..9e0feed35016 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/topwindoweffects/data/repository/SqueezeEffectRepositoryImpl.kt @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.topwindoweffects.data.repository + +import android.database.ContentObserver +import android.os.Handler +import android.provider.Settings.Global.POWER_BUTTON_LONG_PRESS +import com.android.internal.R +import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.shared.Flags +import com.android.systemui.util.settings.GlobalSettings +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOn +import javax.inject.Inject +import kotlin.coroutines.CoroutineContext + +@SysUISingleton +class SqueezeEffectRepositoryImpl @Inject constructor( + @Background private val bgHandler: Handler?, + @Background private val bgCoroutineContext: CoroutineContext, + private val globalSettings: GlobalSettings +) : SqueezeEffectRepository { + + override val isSqueezeEffectEnabled: Flow<Boolean> = conflatedCallbackFlow { + val observer = object : ContentObserver(bgHandler) { + override fun onChange(selfChange: Boolean) { + trySendWithFailureLogging(squeezeEffectEnabled, TAG, + "updated isSqueezeEffectEnabled") + } + } + trySendWithFailureLogging(squeezeEffectEnabled, TAG, "init isSqueezeEffectEnabled") + globalSettings.registerContentObserverAsync(POWER_BUTTON_LONG_PRESS, observer) + awaitClose { globalSettings.unregisterContentObserverAsync(observer) } + }.flowOn(bgCoroutineContext) + + private val squeezeEffectEnabled + get() = Flags.enableLppSqueezeEffect() && globalSettings.getInt( + POWER_BUTTON_LONG_PRESS, R.integer.config_longPressOnPowerBehavior + ) == 5 // 5 corresponds to launch assistant in config_longPressOnPowerBehavior + + companion object { + private const val TAG = "SqueezeEffectRepository" + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/topwindoweffects/domain/interactor/SqueezeEffectInteractor.kt b/packages/SystemUI/src/com/android/systemui/topwindoweffects/domain/interactor/SqueezeEffectInteractor.kt new file mode 100644 index 000000000000..879fde769aee --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/topwindoweffects/domain/interactor/SqueezeEffectInteractor.kt @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.topwindoweffects.domain.interactor + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.topwindoweffects.data.repository.SqueezeEffectRepository +import javax.inject.Inject + +@SysUISingleton +class SqueezeEffectInteractor @Inject constructor( + squeezeEffectRepository: SqueezeEffectRepository +) { + val isSqueezeEffectEnabled = squeezeEffectRepository.isSqueezeEffectEnabled +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/topwindoweffects/ui/compose/EffectsWindowRoot.kt b/packages/SystemUI/src/com/android/systemui/topwindoweffects/ui/compose/EffectsWindowRoot.kt new file mode 100644 index 000000000000..61448f45c0e2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/topwindoweffects/ui/compose/EffectsWindowRoot.kt @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.topwindoweffects.ui.compose + +import android.annotation.SuppressLint +import android.content.Context +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.AbstractComposeView +import com.android.systemui.compose.ComposeInitializer +import com.android.systemui.topwindoweffects.ui.viewmodel.SqueezeEffectViewModel + +@SuppressLint("ViewConstructor") +class EffectsWindowRoot( + context: Context, + private val onEffectFinished: () -> Unit, + private val viewModelFactory: SqueezeEffectViewModel.Factory +) : AbstractComposeView(context) { + + override fun onAttachedToWindow() { + ComposeInitializer.onAttachedToWindow(this) + super.onAttachedToWindow() + } + + override fun onDetachedFromWindow() { + super.onDetachedFromWindow() + ComposeInitializer.onDetachedFromWindow(this) + } + + @Composable + override fun Content() { + SqueezeEffect( + viewModelFactory = viewModelFactory, + onEffectFinished = onEffectFinished + ) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/topwindoweffects/ui/compose/SqueezeEffect.kt b/packages/SystemUI/src/com/android/systemui/topwindoweffects/ui/compose/SqueezeEffect.kt new file mode 100644 index 000000000000..9809b0592dcf --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/topwindoweffects/ui/compose/SqueezeEffect.kt @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.topwindoweffects.ui.compose + +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.tween +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Matrix +import androidx.compose.ui.graphics.drawscope.DrawScope +import androidx.compose.ui.graphics.drawscope.withTransform +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.VectorPainter +import androidx.compose.ui.graphics.vector.rememberVectorPainter +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.unit.dp +import com.android.systemui.lifecycle.rememberViewModel +import com.android.systemui.res.R +import com.android.systemui.topwindoweffects.ui.viewmodel.SqueezeEffectViewModel + +private val SqueezeEffectMaxThickness = 12.dp +private val SqueezeColor = Color.Black + +@Composable +fun SqueezeEffect( + viewModelFactory: SqueezeEffectViewModel.Factory, + onEffectFinished: () -> Unit, + modifier: Modifier = Modifier +) { + val viewModel = rememberViewModel(traceName = "SqueezeEffect") { viewModelFactory.create() } + val down = viewModel.isPowerButtonPressed + val longPressed = viewModel.isPowerButtonLongPressed + // TODO: Choose the correct resource based on primary / secondary display + val top = rememberVectorPainter(ImageVector.vectorResource(R.drawable.rounded_corner_top)) + val bottom = rememberVectorPainter(ImageVector.vectorResource(R.drawable.rounded_corner_bottom)) + + val squeezeProgress by animateFloatAsState( + targetValue = + if (down && !longPressed) { + 1f + } else { + 0f + }, + animationSpec = tween(durationMillis = 400), + finishedListener = { onEffectFinished() } + ) + + Canvas(modifier = modifier.fillMaxSize()) { + if (squeezeProgress <= 0) { + return@Canvas + } + + val squeezeThickness = SqueezeEffectMaxThickness.toPx() * squeezeProgress + + drawRect(color = SqueezeColor, size = Size(size.width, squeezeThickness)) + + drawRect( + color = SqueezeColor, + topLeft = Offset(0f, size.height - squeezeThickness), + size = Size(size.width, squeezeThickness) + ) + + drawRect(color = SqueezeColor, size = Size(squeezeThickness, size.height)) + + drawRect( + color = SqueezeColor, + topLeft = Offset(size.width - squeezeThickness, 0f), + size = Size(squeezeThickness, size.height) + ) + + drawTransform( + dx = squeezeThickness, + dy = squeezeThickness, + rotation = 0f, + corner = top + ) + + drawTransform( + dx = size.width - squeezeThickness, + dy = squeezeThickness, + rotation = 90f, + corner = top + ) + + drawTransform( + dx = squeezeThickness, + dy = size.height - squeezeThickness, + rotation = 270f, + corner = bottom + ) + + drawTransform( + dx = size.width - squeezeThickness, + dy = size.height - squeezeThickness, + rotation = 180f, + corner = bottom + ) + } +} + +private fun DrawScope.drawTransform( + dx: Float, + dy: Float, + rotation: Float = 0f, + corner: VectorPainter, +) { + withTransform(transformBlock = { + transform(matrix = Matrix().apply { + translate(dx, dy) + if (rotation != 0f) { + rotateZ(rotation) + } + }) + }) { + with(corner) { + draw(size = intrinsicSize) + } + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/topwindoweffects/ui/viewmodel/SqueezeEffectViewModel.kt b/packages/SystemUI/src/com/android/systemui/topwindoweffects/ui/viewmodel/SqueezeEffectViewModel.kt new file mode 100644 index 000000000000..1cab327740ce --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/topwindoweffects/ui/viewmodel/SqueezeEffectViewModel.kt @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.topwindoweffects.ui.viewmodel + +import androidx.compose.runtime.getValue +import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor +import com.android.systemui.lifecycle.ExclusiveActivatable +import com.android.systemui.lifecycle.Hydrator +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject + +class SqueezeEffectViewModel +@AssistedInject +constructor( + keyEventInteractor: KeyEventInteractor +) : ExclusiveActivatable() { + private val hydrator = Hydrator("SqueezeEffectViewModel.hydrator") + + val isPowerButtonPressed: Boolean by hydrator.hydratedStateOf( + traceName = "isPowerButtonPressed", + initialValue = false, + source = keyEventInteractor.isPowerButtonDown + ) + + val isPowerButtonLongPressed: Boolean by hydrator.hydratedStateOf( + traceName = "isPowerButtonLongPressed", + initialValue = false, + source = keyEventInteractor.isPowerButtonLongPressed + ) + + override suspend fun onActivated(): Nothing { + hydrator.activate() + } + + @AssistedFactory + interface Factory { + fun create(): SqueezeEffectViewModel + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt index eecea9228ea3..2f8da2eae68a 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt @@ -84,7 +84,7 @@ fun TutorialSelectionScreen( ), ) { val isCompactWindow = hasCompactWindowSize() - val padding = if (isCompactWindow) 24.dp else 60.dp + val padding = if (isCompactWindow) 24.dp else 48.dp val configuration = LocalConfiguration.current when (configuration.orientation) { Configuration.ORIENTATION_LANDSCAPE -> { @@ -121,7 +121,7 @@ fun TutorialSelectionScreen( // because other composables have weight 1, Done button will be positioned first DoneButton( onDoneButtonClicked = onDoneButtonClicked, - modifier = Modifier.padding(horizontal = padding), + modifier = Modifier.padding(start = padding, top = 0.dp, end = padding, bottom = 32.dp), ) } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/ui/slider/Slider.kt b/packages/SystemUI/src/com/android/systemui/volume/ui/slider/Slider.kt index 720d5507e224..f6582a005035 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/ui/slider/Slider.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/ui/slider/Slider.kt @@ -72,10 +72,10 @@ fun Slider( valueRange: ClosedFloatingPointRange<Float>, onValueChanged: (Float) -> Unit, onValueChangeFinished: ((Float) -> Unit)?, - stepDistance: Float, isEnabled: Boolean, accessibilityParams: AccessibilityParams, modifier: Modifier = Modifier, + stepDistance: Float = 0f, colors: SliderColors = SliderDefaults.colors(), interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, haptics: Haptics = Haptics.Disabled, @@ -83,7 +83,7 @@ fun Slider( isReverseDirection: Boolean = false, track: (@Composable (SliderState) -> Unit)? = null, ) { - require(stepDistance > 0) { "stepDistance must be positive" } + require(stepDistance >= 0) { "stepDistance must not be negative" } val coroutineScope = rememberCoroutineScope() val snappedValue = snapValue(value, valueRange, stepDistance) val hapticsViewModel = haptics.createViewModel(snappedValue, valueRange, interactionSource) @@ -192,16 +192,20 @@ private fun AccessibilityParams.createSemantics( setProgress { targetValue -> val targetDirection = when { - targetValue > value -> 1 - targetValue < value -> -1 - else -> 0 + targetValue > value -> 1f + targetValue < value -> -1f + else -> 0f + } + val offset = + if (stepDistance > 0) { + // advance to the next step when stepDistance is > 0 + targetDirection * stepDistance + } else { + // advance to the desired value otherwise + targetValue - value } - val newValue = - (value + targetDirection * stepDistance).coerceIn( - valueRange.start, - valueRange.endInclusive, - ) + val newValue = (value + offset).coerceIn(valueRange.start, valueRange.endInclusive) onValueChanged(newValue) true } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java index e8054c07eac8..8105ae0960ad 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java @@ -283,7 +283,6 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { () -> mSelectedUserInteractor, mock(UserTracker.class), mKosmos.getNotificationShadeWindowModel(), - mSecureSettings, mKosmos::getCommunalInteractor, mKosmos.getShadeLayoutParams()); mFeatureFlags = new FakeFeatureFlags(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt index b274f1c2c6df..d9c59d37b031 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt @@ -88,11 +88,6 @@ class KeyguardQuickAffordanceInteractorSceneContainerTest : SysuiTestCase() { companion object { private val INTENT = Intent("some.intent.action") - private val DRAWABLE = - mock<Icon> { - whenever(this.contentDescription) - .thenReturn(ContentDescription.Resource(res = CONTENT_DESCRIPTION_RESOURCE_ID)) - } private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337 @Parameters( @@ -337,7 +332,17 @@ class KeyguardQuickAffordanceInteractorSceneContainerTest : SysuiTestCase() { homeControls.setState( lockScreenState = - KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = DRAWABLE) + KeyguardQuickAffordanceConfig.LockScreenState.Visible( + icon = + mock<Icon> { + whenever(contentDescription) + .thenReturn( + ContentDescription.Resource( + res = CONTENT_DESCRIPTION_RESOURCE_ID + ) + ) + } + ) ) homeControls.onTriggeredResult = if (startActivity) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt index 175e8d4331a1..e048d462ef8d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt @@ -33,8 +33,8 @@ import android.platform.test.annotations.EnableFlags import android.platform.test.annotations.RequiresFlagsDisabled import android.platform.test.annotations.RequiresFlagsEnabled import android.platform.test.flag.junit.DeviceFlagsValueProvider +import android.platform.test.flag.junit.FlagsParameterization import android.testing.TestableLooper -import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast import com.android.settingslib.bluetooth.LocalBluetoothManager @@ -77,6 +77,8 @@ import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.Mockito.`when` as whenever import org.mockito.junit.MockitoJUnit import org.mockito.kotlin.eq +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters private const val KEY = "TEST_KEY" private const val KEY_OLD = "TEST_KEY_OLD" @@ -89,12 +91,24 @@ private const val BROADCAST_APP_NAME = "BROADCAST_APP_NAME" private const val NORMAL_APP_NAME = "NORMAL_APP_NAME" @SmallTest -@RunWith(AndroidJUnit4::class) +@RunWith(ParameterizedAndroidJunit4::class) @TestableLooper.RunWithLooper -public class MediaDeviceManagerTest : SysuiTestCase() { +public class MediaDeviceManagerTest(flags: FlagsParameterization) : SysuiTestCase() { - private companion object { + companion object { val OTHER_DEVICE_ICON_STUB = TestStubDrawable() + + @JvmStatic + @Parameters(name = "{0}") + fun getParams(): List<FlagsParameterization> { + return FlagsParameterization.progressionOf( + com.android.systemui.Flags.FLAG_MEDIA_CONTROLS_DEVICE_MANAGER_BACKGROUND_EXECUTION + ) + } + } + + init { + mSetFlagsRule.setFlagsParameterization(flags) } @get:Rule val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule() @@ -187,6 +201,8 @@ public class MediaDeviceManagerTest : SysuiTestCase() { @Test fun loadMediaData() { manager.onMediaDataLoaded(KEY, null, mediaData) + fakeBgExecutor.runAllReady() + fakeFgExecutor.runAllReady() verify(lmmFactory).create(PACKAGE) } @@ -195,6 +211,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { manager.onMediaDataLoaded(KEY, null, mediaData) manager.onMediaDataRemoved(KEY, false) fakeBgExecutor.runAllReady() + fakeFgExecutor.runAllReady() verify(lmm).unregisterCallback(any()) verify(muteAwaitManager).stopListening() } @@ -406,6 +423,8 @@ public class MediaDeviceManagerTest : SysuiTestCase() { manager.onMediaDataLoaded(KEY, null, mediaData) // WHEN the notification is removed manager.onMediaDataRemoved(KEY, true) + fakeBgExecutor.runAllReady() + fakeFgExecutor.runAllReady() // THEN the listener receives key removed event verify(listener).onKeyRemoved(eq(KEY), eq(true)) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java index 205ccea657df..9ad2235965bd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java @@ -406,11 +406,6 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase { } @Override - int getHeaderIconSize() { - return 10; - } - - @Override CharSequence getHeaderText() { return mHeaderTitle; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java index f0902e35b837..f1bf7c0bcf13 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java @@ -50,6 +50,7 @@ import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; import com.android.settingslib.media.BluetoothMediaDevice; import com.android.settingslib.media.LocalMediaManager; import com.android.settingslib.media.MediaDevice; +import com.android.settingslib.utils.ThreadUtils; import com.android.systemui.SysuiTestCase; import com.android.systemui.SysuiTestCaseExtKt; import com.android.systemui.animation.DialogTransitionAnimator; @@ -152,9 +153,9 @@ public class MediaOutputBroadcastDialogTest extends SysuiTestCase { volumePanelGlobalStateInteractor, mUserTracker); mMediaSwitchingController.mLocalMediaManager = mLocalMediaManager; - mMediaOutputBroadcastDialog = - new MediaOutputBroadcastDialog( - mContext, false, mBroadcastSender, mMediaSwitchingController); + mMediaOutputBroadcastDialog = new MediaOutputBroadcastDialog(mContext, false, + mBroadcastSender, mMediaSwitchingController, mContext.getMainExecutor(), + ThreadUtils.getBackgroundExecutor()); mMediaOutputBroadcastDialog.show(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java index d3ecb3d8c944..420fd6e33abc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java @@ -53,6 +53,7 @@ import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; import com.android.settingslib.flags.Flags; import com.android.settingslib.media.LocalMediaManager; import com.android.settingslib.media.MediaDevice; +import com.android.settingslib.utils.ThreadUtils; import com.android.systemui.SysuiTestCase; import com.android.systemui.SysuiTestCaseExtKt; import com.android.systemui.animation.DialogTransitionAnimator; @@ -455,6 +456,8 @@ public class MediaOutputDialogTest extends SysuiTestCase { controller, mDialogTransitionAnimator, mUiEventLogger, + mContext.getMainExecutor(), + ThreadUtils.getBackgroundExecutor(), true); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java index 88c2697fe2f2..5c26dac5eb30 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java @@ -1573,6 +1573,25 @@ public class MediaSwitchingControllerTest extends SysuiTestCase { assertThat(items.get(1).isFirstDeviceInGroup()).isFalse(); } + @EnableFlags(Flags.FLAG_ENABLE_OUTPUT_SWITCHER_DEVICE_GROUPING) + @Test + public void deviceListUpdateWithDifferentDevices_firstSelectedDeviceIsFirstDeviceInGroup() { + when(mLocalMediaManager.isPreferenceRouteListingExist()).thenReturn(true); + doReturn(mMediaDevices) + .when(mLocalMediaManager) + .getSelectedMediaDevice(); + mMediaSwitchingController.start(mCb); + reset(mCb); + mMediaSwitchingController.getMediaItemList().clear(); + mMediaSwitchingController.onDeviceListUpdate(mMediaDevices); + mMediaDevices.clear(); + mMediaDevices.add(mMediaDevice2); + mMediaSwitchingController.onDeviceListUpdate(mMediaDevices); + + List<MediaItem> items = mMediaSwitchingController.getMediaItemList(); + assertThat(items.get(0).isFirstDeviceInGroup()).isTrue(); + } + private int getNumberOfConnectDeviceButtons() { int numberOfConnectDeviceButtons = 0; for (MediaItem item : mMediaSwitchingController.getMediaItemList()) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentManagerTest.kt index c20a801cd5e3..a8bfbd18d0c5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentManagerTest.kt @@ -103,6 +103,8 @@ class InternetDetailsContentManagerTest : SysuiTestCase() { whenever(internetWifiEntry.hasInternetAccess()).thenReturn(true) whenever(wifiEntries.size).thenReturn(1) whenever(internetDetailsContentController.getDialogTitleText()).thenReturn(TITLE) + whenever(internetDetailsContentController.getSubtitleText(ArgumentMatchers.anyBoolean())) + .thenReturn("") whenever(internetDetailsContentController.getMobileNetworkTitle(ArgumentMatchers.anyInt())) .thenReturn(MOBILE_NETWORK_TITLE) whenever( @@ -128,15 +130,13 @@ class InternetDetailsContentManagerTest : SysuiTestCase() { internetDetailsContentController, canConfigMobileData = true, canConfigWifi = true, - coroutineScope = scope, - context = mContext, uiEventLogger = mock<UiEventLogger>(), handler = handler, backgroundExecutor = bgExecutor, keyguard = keyguard, ) - internetDetailsContentManager.bind(contentView) + internetDetailsContentManager.bind(contentView, scope) internetDetailsContentManager.adapter = internetAdapter internetDetailsContentManager.connectedWifiEntry = internetWifiEntry internetDetailsContentManager.wifiEntriesCount = wifiEntries.size @@ -777,6 +777,26 @@ class InternetDetailsContentManagerTest : SysuiTestCase() { } } + @Test + fun updateTitleAndSubtitle() { + assertThat(internetDetailsContentManager.title).isEqualTo("Internet") + assertThat(internetDetailsContentManager.subTitle).isEqualTo("") + + whenever(internetDetailsContentController.getDialogTitleText()).thenReturn("New title") + whenever(internetDetailsContentController.getSubtitleText(ArgumentMatchers.anyBoolean())) + .thenReturn("New subtitle") + + internetDetailsContentManager.updateContent(true) + bgExecutor.runAllReady() + + internetDetailsContentManager.internetContentData.observe( + internetDetailsContentManager.lifecycleOwner!! + ) { + assertThat(internetDetailsContentManager.title).isEqualTo("New title") + assertThat(internetDetailsContentManager.subTitle).isEqualTo("New subtitle") + } + } + companion object { private const val TITLE = "Internet" private const val MOBILE_NETWORK_TITLE = "Mobile Title" diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/LauncherProxyServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recents/LauncherProxyServiceTest.kt index 2d3f538689b3..155059ea5ed9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/recents/LauncherProxyServiceTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/recents/LauncherProxyServiceTest.kt @@ -36,6 +36,7 @@ import com.android.systemui.dump.DumpManager import com.android.systemui.keyguard.KeyguardUnlockAnimationController import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.keyguard.ui.view.InWindowLauncherUnlockAnimationManager +import com.android.systemui.log.assertLogsWtf import com.android.systemui.model.sysUiState import com.android.systemui.navigationbar.NavigationBarController import com.android.systemui.navigationbar.NavigationModeController @@ -221,7 +222,7 @@ class LauncherProxyServiceTest : SysuiTestCase() { `when`(processWrapper.isSystemUser).thenReturn(false) `when`(userManager.isVisibleBackgroundUsersSupported()).thenReturn(false) val spyContext = spy(context) - val ops = createLauncherProxyService(spyContext) + val ops = assertLogsWtf { createLauncherProxyService(spyContext) }.result ops.startConnectionToCurrentUser() verify(spyContext, times(0)).bindServiceAsUser(any(), any(), anyInt(), any()) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/ringtone/RingtonePlayerTest.java b/packages/SystemUI/tests/src/com/android/systemui/ringtone/RingtonePlayerTest.java new file mode 100644 index 000000000000..c231be181977 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/ringtone/RingtonePlayerTest.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.ringtone; + +import static org.junit.Assert.assertThrows; + +import android.media.AudioAttributes; +import android.media.AudioManager; +import android.net.Uri; +import android.os.Binder; +import android.os.UserHandle; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class RingtonePlayerTest extends SysuiTestCase { + + private AudioManager mAudioManager; + + private static final String TAG = "RingtonePlayerTest"; + + @Before + public void setup() throws Exception { + mAudioManager = getContext().getSystemService(AudioManager.class); + } + + @Test + public void testRingtonePlayerUriUserCheck() { + android.media.IRingtonePlayer irp = mAudioManager.getRingtonePlayer(); + final AudioAttributes aa = new AudioAttributes.Builder() + .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE).build(); + // get a UserId that doesn't belong to mine + final int otherUserId = UserHandle.myUserId() == 0 ? 10 : 0; + // build a URI that I shouldn't have access to + final Uri uri = new Uri.Builder() + .scheme("content").authority(otherUserId + "@media") + .appendPath("external").appendPath("downloads") + .appendPath("bogusPathThatDoesNotMatter.mp3") + .build(); + if (android.media.audio.Flags.ringtoneUserUriCheck()) { + assertThrows(SecurityException.class, () -> + irp.play(new Binder(), uri, aa, 1.0f /*volume*/, false /*looping*/) + ); + + assertThrows(SecurityException.class, () -> + irp.getTitle(uri)); + } + } + +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt index a64ff321cd4d..11aaeefe8214 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt @@ -134,10 +134,7 @@ class BrightnessDialogTest(val flags: FlagsParameterization) : SysuiTestCase() { val frame = activityRule.activity.requireViewById<View>(viewId) val lp = frame.layoutParams as ViewGroup.MarginLayoutParams - val horizontalMargin = - activityRule.activity.resources.getDimensionPixelSize( - R.dimen.notification_side_paddings - ) + val horizontalMargin = 0 assertThat(lp.leftMargin).isEqualTo(horizontalMargin) assertThat(lp.rightMargin).isEqualTo(horizontalMargin) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java index 81213caaa5f4..3570cf827808 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java @@ -77,7 +77,6 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifStabilityManager; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable; import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener; -import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationTestHelper; import com.android.systemui.statusbar.notification.shared.NotificationBundleUi; import com.android.systemui.util.time.FakeSystemClock; @@ -1795,7 +1794,7 @@ public class ShadeListBuilderTest extends SysuiTestCase { invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS); // THEN an exception is NOT thrown directly, but a WTF IS logged. - LogAssertKt.assertLogsWtfs(() -> { + LogAssertKt.assertRunnableLogsWtfs(() -> { dispatchBuild(); runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); }); @@ -1818,7 +1817,7 @@ public class ShadeListBuilderTest extends SysuiTestCase { addNotif(0, PACKAGE_2); invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1); - LogAssertKt.assertLogsWtfs(() -> { + LogAssertKt.assertRunnableLogsWtfs(() -> { Assert.assertThrows(IllegalStateException.class, () -> { dispatchBuild(); runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); @@ -1844,13 +1843,13 @@ public class ShadeListBuilderTest extends SysuiTestCase { addNotif(0, PACKAGE_2); invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS); - LogAssertKt.assertLogsWtfs(() -> { + LogAssertKt.assertRunnableLogsWtfs(() -> { dispatchBuild(); runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); }); invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS); - LogAssertKt.assertLogsWtfs(() -> { + LogAssertKt.assertRunnableLogsWtfs(() -> { // Note: dispatchBuild itself triggers a non-reentrant pipeline run. dispatchBuild(); runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); @@ -1874,7 +1873,7 @@ public class ShadeListBuilderTest extends SysuiTestCase { addNotif(0, PACKAGE_1); invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS); - LogAssertKt.assertLogsWtfs(() -> { + LogAssertKt.assertRunnableLogsWtfs(() -> { dispatchBuild(); runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); }); @@ -1897,7 +1896,7 @@ public class ShadeListBuilderTest extends SysuiTestCase { addNotif(0, PACKAGE_1); invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1); - LogAssertKt.assertLogsWtfs(() -> { + LogAssertKt.assertRunnableLogsWtfs(() -> { Assert.assertThrows(IllegalStateException.class, () -> { dispatchBuild(); runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); @@ -1922,7 +1921,7 @@ public class ShadeListBuilderTest extends SysuiTestCase { addNotif(0, PACKAGE_2); invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS); - LogAssertKt.assertLogsWtfs(() -> { + LogAssertKt.assertRunnableLogsWtfs(() -> { dispatchBuild(); runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); }); @@ -1945,7 +1944,7 @@ public class ShadeListBuilderTest extends SysuiTestCase { addNotif(0, PACKAGE_2); invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1); - LogAssertKt.assertLogsWtfs(() -> { + LogAssertKt.assertRunnableLogsWtfs(() -> { Assert.assertThrows(IllegalStateException.class, () -> { dispatchBuild(); runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); @@ -1970,7 +1969,7 @@ public class ShadeListBuilderTest extends SysuiTestCase { addNotif(0, PACKAGE_2); invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS); - LogAssertKt.assertLogsWtfs(() -> { + LogAssertKt.assertRunnableLogsWtfs(() -> { dispatchBuild(); runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); }); @@ -1993,7 +1992,7 @@ public class ShadeListBuilderTest extends SysuiTestCase { addNotif(0, PACKAGE_2); invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1); - LogAssertKt.assertLogsWtfs(() -> { + LogAssertKt.assertRunnableLogsWtfs(() -> { Assert.assertThrows(IllegalStateException.class, () -> { dispatchBuild(); runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java index cd2ea7d25699..4315c0f638ac 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java @@ -73,6 +73,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.PromotedNotificationUi; import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiForceExpanded; import com.android.systemui.statusbar.notification.row.ExpandableView.OnHeightChangedListener; import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper; @@ -936,11 +937,13 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { } @Test - @EnableFlags(PromotedNotificationUiForceExpanded.FLAG_NAME) + @EnableFlags({PromotedNotificationUi.FLAG_NAME, PromotedNotificationUiForceExpanded.FLAG_NAME}) public void isExpanded_sensitivePromotedNotification_notExpanded() throws Exception { // GIVEN final ExpandableNotificationRow row = mNotificationTestHelper.createRow(); - row.setPromotedOngoing(true); + NotificationEntry entry = mock(NotificationEntry.class); + when(entry.isPromotedOngoing()).thenReturn(true); + row.setEntry(entry); row.setSensitive(/* sensitive= */true, /* hideSensitive= */false); row.setHideSensitiveForIntrinsicHeight(/* hideSensitive= */true); @@ -949,11 +952,13 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { } @Test - @EnableFlags(PromotedNotificationUiForceExpanded.FLAG_NAME) + @EnableFlags({PromotedNotificationUi.FLAG_NAME, PromotedNotificationUiForceExpanded.FLAG_NAME}) public void isExpanded_promotedNotificationNotOnKeyguard_expanded() throws Exception { // GIVEN final ExpandableNotificationRow row = mNotificationTestHelper.createRow(); - row.setPromotedOngoing(true); + NotificationEntry entry = mock(NotificationEntry.class); + when(entry.isPromotedOngoing()).thenReturn(true); + row.setEntry(entry); row.setOnKeyguard(false); // THEN @@ -961,11 +966,13 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { } @Test - @EnableFlags(PromotedNotificationUiForceExpanded.FLAG_NAME) + @EnableFlags({PromotedNotificationUi.FLAG_NAME, PromotedNotificationUiForceExpanded.FLAG_NAME}) public void isExpanded_promotedNotificationAllowOnKeyguard_expanded() throws Exception { // GIVEN final ExpandableNotificationRow row = mNotificationTestHelper.createRow(); - row.setPromotedOngoing(true); + NotificationEntry entry = mock(NotificationEntry.class); + when(entry.isPromotedOngoing()).thenReturn(true); + row.setEntry(entry); row.setOnKeyguard(true); // THEN @@ -973,12 +980,14 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { } @Test - @EnableFlags(PromotedNotificationUiForceExpanded.FLAG_NAME) + @EnableFlags({PromotedNotificationUi.FLAG_NAME, PromotedNotificationUiForceExpanded.FLAG_NAME}) public void isExpanded_promotedNotificationIgnoreLockscreenConstraints_expanded() throws Exception { // GIVEN final ExpandableNotificationRow row = mNotificationTestHelper.createRow(); - row.setPromotedOngoing(true); + NotificationEntry entry = mock(NotificationEntry.class); + when(entry.isPromotedOngoing()).thenReturn(true); + row.setEntry(entry); row.setOnKeyguard(true); row.setIgnoreLockscreenConstraints(true); @@ -987,12 +996,14 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { } @Test - @EnableFlags(PromotedNotificationUiForceExpanded.FLAG_NAME) + @EnableFlags({PromotedNotificationUi.FLAG_NAME, PromotedNotificationUiForceExpanded.FLAG_NAME}) public void isExpanded_promotedNotificationSaveSpaceOnLockScreen_notExpanded() throws Exception { // GIVEN final ExpandableNotificationRow row = mNotificationTestHelper.createRow(); - row.setPromotedOngoing(true); + NotificationEntry entry = mock(NotificationEntry.class); + when(entry.isPromotedOngoing()).thenReturn(true); + row.setEntry(entry); row.setOnKeyguard(true); row.setSaveSpaceOnLockscreen(true); @@ -1001,12 +1012,14 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { } @Test - @EnableFlags(PromotedNotificationUiForceExpanded.FLAG_NAME) + @EnableFlags({PromotedNotificationUi.FLAG_NAME, PromotedNotificationUiForceExpanded.FLAG_NAME}) public void isExpanded_promotedNotificationNotSaveSpaceOnLockScreen_expanded() throws Exception { // GIVEN final ExpandableNotificationRow row = mNotificationTestHelper.createRow(); - row.setPromotedOngoing(true); + NotificationEntry entry = mock(NotificationEntry.class); + when(entry.isPromotedOngoing()).thenReturn(true); + row.setEntry(entry); row.setOnKeyguard(true); row.setSaveSpaceOnLockscreen(false); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java index 89b7bee7f286..a3616d20e11f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java @@ -128,6 +128,7 @@ import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.kosmos.KosmosJavaAdapter; +import com.android.systemui.media.NotificationMediaManager; import com.android.systemui.navigationbar.NavigationBarController; import com.android.systemui.notetask.NoteTaskController; import com.android.systemui.plugins.ActivityStarter; @@ -164,7 +165,6 @@ import com.android.systemui.statusbar.KeyguardIndicationController; import com.android.systemui.statusbar.LightRevealScrim; import com.android.systemui.statusbar.LockscreenShadeTransitionController; import com.android.systemui.statusbar.NotificationLockscreenUserManager; -import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationPresenter; import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.NotificationShadeDepthController; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java index ffe7750dadfa..4a0445d5543a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java @@ -269,14 +269,14 @@ public class RemoteInputViewTest extends SysuiTestCase { when(viewRoot.getOnBackInvokedDispatcher()).thenReturn(backInvokedDispatcher); view.setViewRootImpl(viewRoot); - /* verify that predictive back callback registered when RemoteInputView becomes visible */ - view.onVisibilityAggregated(true); + /* verify that predictive back callback registered when RemoteInputView gains focus */ + view.focus(); verify(backInvokedDispatcher).registerOnBackInvokedCallback( eq(OnBackInvokedDispatcher.PRIORITY_OVERLAY), onBackInvokedCallbackCaptor.capture()); - /* verify that same callback unregistered when RemoteInputView becomes invisible */ - view.onVisibilityAggregated(false); + /* verify that same callback unregistered when RemoteInputView loses focus */ + view.onDefocus(false, false, null); verify(backInvokedDispatcher).unregisterOnBackInvokedCallback( eq(onBackInvokedCallbackCaptor.getValue())); } @@ -299,13 +299,12 @@ public class RemoteInputViewTest extends SysuiTestCase { view.onVisibilityAggregated(true); view.setEditTextReferenceToSelf(); + view.focus(); /* capture the callback during registration */ verify(backInvokedDispatcher).registerOnBackInvokedCallback( eq(OnBackInvokedDispatcher.PRIORITY_OVERLAY), onBackInvokedCallbackCaptor.capture()); - view.focus(); - /* invoke the captured callback */ onBackInvokedCallbackCaptor.getValue().onBackInvoked(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index 341bd3a38999..8de931a7af40 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -33,6 +33,8 @@ import static com.android.wm.shell.Flags.FLAG_ENABLE_BUBBLE_BAR; import static com.google.common.truth.Truth.assertThat; +import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -55,8 +57,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; -import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow; - import android.app.ActivityManager; import android.app.IActivityManager; import android.app.INotificationManager; @@ -136,7 +136,6 @@ import com.android.systemui.statusbar.RankingBuilder; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.notification.NotifPipelineFlags; import com.android.systemui.statusbar.notification.collection.GroupEntry; -import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; @@ -166,7 +165,6 @@ import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvision import com.android.systemui.user.domain.interactor.SelectedUserInteractor; import com.android.systemui.util.FakeEventLog; import com.android.systemui.util.settings.FakeGlobalSettings; -import com.android.systemui.util.settings.FakeSettings; import com.android.systemui.util.settings.SystemSettings; import com.android.systemui.util.time.SystemClock; import com.android.wm.shell.Flags; @@ -206,8 +204,6 @@ import com.android.wm.shell.taskview.TaskViewRepository; import com.android.wm.shell.taskview.TaskViewTransitions; import com.android.wm.shell.transition.Transitions; -import kotlin.Lazy; - import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -218,9 +214,6 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.stubbing.Answer; -import platform.test.runner.parameterized.ParameterizedAndroidJunit4; -import platform.test.runner.parameterized.Parameters; - import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -229,6 +222,10 @@ import java.util.List; import java.util.Optional; import java.util.concurrent.Executor; +import kotlin.Lazy; +import platform.test.runner.parameterized.ParameterizedAndroidJunit4; +import platform.test.runner.parameterized.Parameters; + @SmallTest @RunWith(ParameterizedAndroidJunit4.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @@ -451,7 +448,6 @@ public class BubblesTest extends SysuiTestCase { () -> mSelectedUserInteractor, mUserTracker, mNotificationShadeWindowModel, - new FakeSettings(), mKosmos::getCommunalInteractor, mKosmos.getShadeLayoutParams() ); @@ -605,14 +601,19 @@ public class BubblesTest extends SysuiTestCase { // Get a reference to KeyguardStateController.Callback verify(mKeyguardStateController, atLeastOnce()) .addCallback(mKeyguardStateControllerCallbackCaptor.capture()); + + // Make sure mocks are set up for current user + switchUser(ActivityManager.getCurrentUser()); } @After public void tearDown() throws Exception { - ArrayList<Bubble> bubbles = new ArrayList<>(mBubbleData.getBubbles()); - for (int i = 0; i < bubbles.size(); i++) { - mBubbleController.removeBubble(bubbles.get(i).getKey(), - Bubbles.DISMISS_NO_LONGER_BUBBLE); + if (mBubbleData != null) { + ArrayList<Bubble> bubbles = new ArrayList<>(mBubbleData.getBubbles()); + for (int i = 0; i < bubbles.size(); i++) { + mBubbleController.removeBubble(bubbles.get(i).getKey(), + Bubbles.DISMISS_NO_LONGER_BUBBLE); + } } mTestableLooper.processAllMessages(); diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt index 0ba7c8574d85..a3d01355ac4c 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt @@ -47,6 +47,7 @@ import com.android.systemui.log.dagger.BiometricLog import com.android.systemui.log.dagger.BroadcastDispatcherLog import com.android.systemui.log.dagger.FaceAuthLog import com.android.systemui.log.dagger.SceneFrameworkLog +import com.android.systemui.media.NotificationMediaManager import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager import com.android.systemui.model.SysUiState import com.android.systemui.plugins.ActivityStarter @@ -57,7 +58,6 @@ import com.android.systemui.shared.system.ActivityManagerWrapper import com.android.systemui.statusbar.LockscreenShadeTransitionController import com.android.systemui.statusbar.NotificationListener import com.android.systemui.statusbar.NotificationLockscreenUserManager -import com.android.systemui.statusbar.NotificationMediaManager import com.android.systemui.statusbar.NotificationShadeDepthController import com.android.systemui.statusbar.SysuiStatusBarStateController import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt index 6f570a86b19e..cd4b09c5267a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt @@ -21,7 +21,6 @@ import com.android.systemui.biometrics.data.repository.fingerprintPropertyReposi import com.android.systemui.dump.dumpManager import com.android.systemui.keyevent.domain.interactor.keyEventInteractor import com.android.systemui.keyguard.data.repository.biometricSettingsRepository -import com.android.systemui.keyguard.domain.interactor.keyguardBypassInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.util.time.systemClock @@ -31,8 +30,6 @@ val Kosmos.deviceEntryHapticsInteractor by DeviceEntryHapticsInteractor( biometricSettingsRepository = biometricSettingsRepository, deviceEntryBiometricAuthInteractor = deviceEntryBiometricAuthInteractor, - deviceEntryFaceAuthInteractor = deviceEntryFaceAuthInteractor, - keyguardBypassInteractor = keyguardBypassInteractor, deviceEntryFingerprintAuthInteractor = deviceEntryFingerprintAuthInteractor, deviceEntrySourceInteractor = deviceEntrySourceInteractor, fingerprintPropertyRepository = fingerprintPropertyRepository, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyevent/data/repository/FakeKeyEventRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyevent/data/repository/FakeKeyEventRepository.kt index 97dab4987f6c..c9e25c31faa0 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyevent/data/repository/FakeKeyEventRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyevent/data/repository/FakeKeyEventRepository.kt @@ -27,9 +27,16 @@ class FakeKeyEventRepository @Inject constructor() : KeyEventRepository { private val _isPowerButtonDown = MutableStateFlow(false) override val isPowerButtonDown: Flow<Boolean> = _isPowerButtonDown.asStateFlow() + private val _isPowerButtonLongPressed = MutableStateFlow(false) + override val isPowerButtonLongPressed = _isPowerButtonLongPressed.asStateFlow() + fun setPowerButtonDown(isDown: Boolean) { _isPowerButtonDown.value = isDown } + + fun setPowerButtonLongPressed(isLongPressed: Boolean) { + _isPowerButtonLongPressed.value = isLongPressed + } } @Module diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorKosmos.kt index 255a780a84be..113059222aa2 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorKosmos.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.domain.interactor import android.content.applicationContext +import android.os.powerManager import android.view.accessibility.accessibilityManagerWrapper import com.android.internal.logging.uiEventLogger import com.android.systemui.broadcast.broadcastDispatcher @@ -26,6 +27,8 @@ import com.android.systemui.keyguard.data.repository.keyguardRepository import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.shade.pulsingGestureListener +import com.android.systemui.util.settings.data.repository.userAwareSecureSettingsRepository +import com.android.systemui.util.time.fakeSystemClock val Kosmos.keyguardTouchHandlingInteractor by Kosmos.Fixture { @@ -40,5 +43,8 @@ val Kosmos.keyguardTouchHandlingInteractor by accessibilityManager = accessibilityManagerWrapper, pulsingGestureListener = pulsingGestureListener, faceAuthInteractor = deviceEntryFaceAuthInteractor, + secureSettingsRepository = userAwareSecureSettingsRepository, + powerManager = powerManager, + systemClock = fakeSystemClock, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModelKosmos.kt index 2797b4409ff0..bf456978d983 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModelKosmos.kt @@ -48,5 +48,6 @@ val Kosmos.deviceEntryBackgroundViewModel by Fixture { primaryBouncerToLockscreenTransitionViewModel, lockscreenToDozingTransitionViewModel = lockscreenToDozingTransitionViewModel, glanceableHubToAodTransitionViewModel = glanceableHubToAodTransitionViewModel, + glanceableHubToLockscreenTransitionViewModel = glanceableHubToLockscreenTransitionViewModel, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt index 27ca0f867855..a9aa8cd5a7f9 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt @@ -84,6 +84,7 @@ val Kosmos.keyguardRootViewModel by Fixture { occludedToAodTransitionViewModel = occludedToAodTransitionViewModel, occludedToDozingTransitionViewModel = occludedToDozingTransitionViewModel, occludedToLockscreenTransitionViewModel = occludedToLockscreenTransitionViewModel, + occludedToPrimaryBouncerTransitionViewModel = occludedToPrimaryBouncerTransitionViewModel, offToLockscreenTransitionViewModel = offToLockscreenTransitionViewModel, primaryBouncerToAodTransitionViewModel = primaryBouncerToAodTransitionViewModel, primaryBouncerToGoneTransitionViewModel = primaryBouncerToGoneTransitionViewModel, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/util/KeyguardTransitionRepositorySpySubject.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/util/KeyguardTransitionRepositorySpySubject.kt index 11f0c19ffa67..7093a941485a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/util/KeyguardTransitionRepositorySpySubject.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/util/KeyguardTransitionRepositorySpySubject.kt @@ -115,14 +115,14 @@ private constructor( fun assertThat( repository: KeyguardTransitionRepository ): KeyguardTransitionRepositorySpySubject = - assertAbout { failureMetadata, repository: KeyguardTransitionRepository -> + assertAbout { failureMetadata, repository: KeyguardTransitionRepository? -> if (!Mockito.mockingDetails(repository).isSpy) { fail( "Cannot assert on a non-spy KeyguardTransitionRepository. " + "Use Mockito.spy(keyguardTransitionRepository)." ) } - KeyguardTransitionRepositorySpySubject(failureMetadata, repository) + KeyguardTransitionRepositorySpySubject(failureMetadata, repository!!) } .that(repository) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/log/LogAssert.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/log/LogAssert.kt index 5e67182d7353..b41ceff5f581 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/log/LogAssert.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/log/LogAssert.kt @@ -13,89 +13,103 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.android.systemui.log import android.util.Log import android.util.Log.TerribleFailureHandler -import junit.framework.Assert +import com.google.common.truth.Truth.assertWithMessage +import java.util.concurrent.Callable -/** Asserts that the given block does not make a call to Log.wtf */ -fun assertDoesNotLogWtf( +/** Asserts that [notLoggingBlock] does not make a call to [Log.wtf] */ +fun <T> assertDoesNotLogWtf( message: String = "Expected Log.wtf not to be called", - notLoggingBlock: () -> Unit, -) { + notLoggingBlock: () -> T, +): T { var caught: TerribleFailureLog? = null val newHandler = TerribleFailureHandler { tag, failure, system -> caught = TerribleFailureLog(tag, failure, system) } val oldHandler = Log.setWtfHandler(newHandler) - try { - notLoggingBlock() - } finally { - Log.setWtfHandler(oldHandler) - } + val result = + try { + notLoggingBlock() + } finally { + Log.setWtfHandler(oldHandler) + } caught?.let { throw AssertionError("$message: $it", it.failure) } + return result } -fun assertDoesNotLogWtf( - message: String = "Expected Log.wtf not to be called", - notLoggingRunnable: Runnable, -) = assertDoesNotLogWtf(message = message) { notLoggingRunnable.run() } - -/** - * Assert that the given block makes a call to Log.wtf - * - * @return the details of the log - */ -fun assertLogsWtf( +/** Assert that [loggingBlock] makes a call to [Log.wtf] */ +@JvmOverloads +fun <T> assertLogsWtf( message: String = "Expected Log.wtf to be called", allowMultiple: Boolean = false, - loggingBlock: () -> Unit, -): TerribleFailureLog { - var caught: TerribleFailureLog? = null - var count = 0 + loggingBlock: () -> T, +): WtfBlockResult<T> { + val caught = mutableListOf<TerribleFailureLog>() val newHandler = TerribleFailureHandler { tag, failure, system -> - if (caught == null) { - caught = TerribleFailureLog(tag, failure, system) - } - count++ + caught.add(TerribleFailureLog(tag, failure, system)) } val oldHandler = Log.setWtfHandler(newHandler) - try { - loggingBlock() - } finally { - Log.setWtfHandler(oldHandler) - } - Assert.assertNotNull(message, caught) - if (!allowMultiple && count != 1) { - Assert.fail("Unexpectedly caught Log.Wtf $count times; expected only 1. First: $caught") + val result = + try { + loggingBlock() + } finally { + Log.setWtfHandler(oldHandler) + } + assertWithMessage(message).that(caught).isNotEmpty() + if (!allowMultiple) { + assertWithMessage("Unexpectedly caught Log.Wtf multiple times").that(caught).hasSize(1) } - return caught!! + return WtfBlockResult(caught, result) } +/** Assert that [loggingCallable] makes a call to [Log.wtf] */ @JvmOverloads -fun assertLogsWtf( +fun <T> assertLogsWtf( message: String = "Expected Log.wtf to be called", allowMultiple: Boolean = false, - loggingRunnable: Runnable, -): TerribleFailureLog = - assertLogsWtf(message = message, allowMultiple = allowMultiple) { loggingRunnable.run() } + loggingCallable: Callable<T>, +): WtfBlockResult<T> = + assertLogsWtf(message = message, allowMultiple = allowMultiple, loggingCallable::call) -fun assertLogsWtfs( +/** Assert that [loggingBlock] makes at least one call to [Log.wtf] */ +@JvmOverloads +fun <T> assertLogsWtfs( message: String = "Expected Log.wtf to be called once or more", - loggingBlock: () -> Unit, -): TerribleFailureLog = assertLogsWtf(message, allowMultiple = true, loggingBlock) + loggingBlock: () -> T, +): WtfBlockResult<T> = assertLogsWtf(message, allowMultiple = true, loggingBlock) +/** Assert that [loggingCallable] makes at least one call to [Log.wtf] */ @JvmOverloads -fun assertLogsWtfs( +fun <T> assertLogsWtfs( message: String = "Expected Log.wtf to be called once or more", - loggingRunnable: Runnable, -): TerribleFailureLog = assertLogsWtfs(message) { loggingRunnable.run() } + loggingCallable: Callable<T>, +): WtfBlockResult<T> = assertLogsWtf(message, allowMultiple = true, loggingCallable) /** The data passed to [TerribleFailureHandler.onTerribleFailure] */ data class TerribleFailureLog( val tag: String, val failure: Log.TerribleFailure, - val system: Boolean + val system: Boolean, ) + +/** The [Log.wtf] logs and return value of the block */ +data class WtfBlockResult<T>(val logs: List<TerribleFailureLog>, val result: T) + +/** Assert that [loggingRunnable] makes a call to [Log.wtf] */ +@JvmOverloads +fun assertRunnableLogsWtf( + message: String = "Expected Log.wtf to be called", + allowMultiple: Boolean = false, + loggingRunnable: Runnable, +): WtfBlockResult<Unit> = + assertLogsWtf(message = message, allowMultiple = allowMultiple) { loggingRunnable.run() } + +/** Assert that [loggingRunnable] makes at least one call to [Log.wtf] */ +@JvmOverloads +fun assertRunnableLogsWtfs( + message: String = "Expected Log.wtf to be called once or more", + loggingRunnable: Runnable, +): WtfBlockResult<Unit> = assertRunnableLogsWtf(message, allowMultiple = true, loggingRunnable) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/log/LogWtfHandlerRule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/log/LogWtfHandlerRule.kt index e639326bd7a1..0e348c88f058 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/log/LogWtfHandlerRule.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/log/LogWtfHandlerRule.kt @@ -24,21 +24,23 @@ import org.junit.runners.model.Statement class LogWtfHandlerRule : TestRule { - private var started = false - private var handler = ThrowAndFailAtEnd + private var failureLogExemptions = mutableListOf<FailureLogExemption>() override fun apply(base: Statement, description: Description): Statement { return object : Statement() { override fun evaluate() { - started = true + val handler = TerribleFailureTestHandler() val originalWtfHandler = Log.setWtfHandler(handler) var failure: Throwable? = null try { base.evaluate() } catch (ex: Throwable) { - failure = ex.runAndAddSuppressed { handler.onTestFailure(ex) } + failure = ex } finally { - failure = failure.runAndAddSuppressed { handler.onTestFinished() } + failure = + runAndAddSuppressed(failure) { + handler.onTestFinished(failureLogExemptions) + } Log.setWtfHandler(originalWtfHandler) } if (failure != null) { @@ -48,74 +50,52 @@ class LogWtfHandlerRule : TestRule { } } - fun Throwable?.runAndAddSuppressed(block: () -> Unit): Throwable? { + /** Adds a log failure exemption. Exemptions are evaluated at the end of the test. */ + fun addFailureLogExemption(exemption: FailureLogExemption) { + failureLogExemptions.add(exemption) + } + + /** Clears and sets exemptions. Exemptions are evaluated at the end of the test. */ + fun resetFailureLogExemptions(vararg exemptions: FailureLogExemption) { + failureLogExemptions = exemptions.toMutableList() + } + + private fun runAndAddSuppressed(currentError: Throwable?, block: () -> Unit): Throwable? { try { block() } catch (t: Throwable) { - if (this == null) { + if (currentError == null) { return t } - addSuppressed(t) + currentError.addSuppressed(t) } - return this + return currentError } - fun setWtfHandler(handler: TerribleFailureTestHandler) { - check(!started) { "Should only be called before the test starts" } - this.handler = handler - } - - fun interface TerribleFailureTestHandler : TerribleFailureHandler { - fun onTestFailure(failure: Throwable) {} - fun onTestFinished() {} - } - - companion object Handlers { - val ThrowAndFailAtEnd - get() = - object : TerribleFailureTestHandler { - val failures = mutableListOf<Log.TerribleFailure>() - - override fun onTerribleFailure( - tag: String, - what: Log.TerribleFailure, - system: Boolean - ) { - failures.add(what) - throw what - } + private class TerribleFailureTestHandler : TerribleFailureHandler { + private val failureLogs = mutableListOf<FailureLog>() - override fun onTestFailure(failure: Throwable) { - super.onTestFailure(failure) - } + override fun onTerribleFailure(tag: String, what: Log.TerribleFailure, system: Boolean) { + failureLogs.add(FailureLog(tag = tag, failure = what, system = system)) + } - override fun onTestFinished() { - if (failures.isNotEmpty()) { - throw AssertionError("Unexpected Log.wtf calls: $failures", failures[0]) - } - } + fun onTestFinished(exemptions: List<FailureLogExemption>) { + val failures = + failureLogs.filter { failureLog -> + !exemptions.any { it.isFailureLogExempt(failureLog) } } + if (failures.isNotEmpty()) { + throw AssertionError("Unexpected Log.wtf calls: $failures", failures[0].failure) + } + } + } - val JustThrow = TerribleFailureTestHandler { _, what, _ -> throw what } - - val JustFailAtEnd - get() = - object : TerribleFailureTestHandler { - val failures = mutableListOf<Log.TerribleFailure>() - - override fun onTerribleFailure( - tag: String, - what: Log.TerribleFailure, - system: Boolean - ) { - failures.add(what) - } + /** All the information from a call to [Log.wtf] that was handed to [TerribleFailureHandler] */ + data class FailureLog(val tag: String, val failure: Log.TerribleFailure, val system: Boolean) - override fun onTestFinished() { - if (failures.isNotEmpty()) { - throw AssertionError("Unexpected Log.wtf calls: $failures", failures[0]) - } - } - } + /** An interface for exempting a [FailureLog] from causing a test failure. */ + fun interface FailureLogExemption { + /** Determines whether a log should be except from failing the test. */ + fun isFailureLogExempt(log: FailureLog): Boolean } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeTileDetailsViewModel.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeTileDetailsViewModel.kt index 4f8d5a14e390..9457de18b3b9 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeTileDetailsViewModel.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeTileDetailsViewModel.kt @@ -18,18 +18,14 @@ package com.android.systemui.qs import com.android.systemui.plugins.qs.TileDetailsViewModel -class FakeTileDetailsViewModel(var tileSpec: String?) : TileDetailsViewModel() { +class FakeTileDetailsViewModel(var tileSpec: String?) : TileDetailsViewModel { private var _clickOnSettingsButton = 0 override fun clickOnSettingsButton() { _clickOnSettingsButton++ } - override fun getTitle(): String { - return tileSpec ?: " Fake title" - } + override val title = tileSpec ?: " Fake title" - override fun getSubTitle(): String { - return tileSpec ?: "Fake sub title" - } + override val subTitle = tileSpec ?: "Fake sub title" } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/ToolbarViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/ToolbarViewModelKosmos.kt index 75ca311689ce..4aa4a2b1a73a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/ToolbarViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/ToolbarViewModelKosmos.kt @@ -18,6 +18,7 @@ package com.android.systemui.qs.panels.ui.viewmodel.toolbar import android.content.applicationContext import com.android.systemui.classifier.domain.interactor.falsingInteractor +import com.android.systemui.development.ui.viewmodel.buildNumberViewModelFactory import com.android.systemui.globalactions.globalActionsDialogLite import com.android.systemui.kosmos.Kosmos import com.android.systemui.qs.footerActionsInteractor @@ -29,6 +30,7 @@ val Kosmos.toolbarViewModelFactory by override fun create(): ToolbarViewModel { return ToolbarViewModel( editModeButtonViewModelFactory, + buildNumberViewModelFactory, footerActionsInteractor, { globalActionsDialogLite }, falsingInteractor, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerSubject.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerSubject.kt index e09f2806ce5c..c1e689c0165c 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerSubject.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerSubject.kt @@ -68,8 +68,8 @@ private constructor( ): QSTileIntentUserInputHandlerSubject = Truth.assertAbout { failureMetadata: FailureMetadata, - subject: FakeQSTileIntentUserInputHandler -> - QSTileIntentUserInputHandlerSubject(failureMetadata, subject) + subject: FakeQSTileIntentUserInputHandler? -> + QSTileIntentUserInputHandlerSubject(failureMetadata, subject!!) } .that(handler) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt index aa29808bd9ee..657a95a3261e 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt @@ -63,7 +63,7 @@ private constructor(failureMetadata: FailureMetadata, subject: QSTileState?) : companion object { /** Returns a factory to be used with [Truth.assertAbout]. */ - fun states(): Factory<QSTileStateSubject, QSTileState?> { + fun states(): Factory<QSTileStateSubject, QSTileState> { return Factory { failureMetadata: FailureMetadata, subject: QSTileState? -> QSTileStateSubject(failureMetadata, subject) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/TileSubject.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/TileSubject.kt index d2351dc8ae18..cc8b44b0dade 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/TileSubject.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/TileSubject.kt @@ -59,7 +59,7 @@ class TileSubject private constructor(failureMetadata: FailureMetadata, subject: companion object { /** Returns a factory to be used with [Truth.assertAbout]. */ - fun tiles(): Factory<TileSubject, Tile?> { + fun tiles(): Factory<TileSubject, Tile> { return Factory { failureMetadata: FailureMetadata, subject: Tile? -> TileSubject(failureMetadata, subject) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/EntryAdapterFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/EntryAdapterFactoryKosmos.kt index e99f61e7cd13..067e420b89c3 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/EntryAdapterFactoryKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/EntryAdapterFactoryKosmos.kt @@ -25,12 +25,13 @@ import com.android.systemui.statusbar.notification.people.peopleNotificationIden import com.android.systemui.statusbar.notification.row.icon.notificationIconStyleProvider val Kosmos.entryAdapterFactory by -Kosmos.Fixture { - EntryAdapterFactoryImpl( - mockNotificationActivityStarter, - metricsLogger, - peopleNotificationIdentifier, - notificationIconStyleProvider, - visualStabilityCoordinator, - ) -}
\ No newline at end of file + Kosmos.Fixture { + EntryAdapterFactoryImpl( + mockNotificationActivityStarter, + metricsLogger, + peopleNotificationIdentifier, + notificationIconStyleProvider, + visualStabilityCoordinator, + mockNotificationActionClickManager, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt index ff4fbf9f0e94..6a674ca29ca4 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt @@ -37,6 +37,7 @@ import com.android.systemui.flags.FakeFeatureFlagsClassic import com.android.systemui.flags.FeatureFlagsClassic import com.android.systemui.flags.Flags import com.android.systemui.log.logcatLogBuffer +import com.android.systemui.media.NotificationMediaManager import com.android.systemui.media.controls.util.MediaFeatureFlag import com.android.systemui.media.dialog.MediaOutputDialogManager import com.android.systemui.plugins.ActivityStarter @@ -45,7 +46,6 @@ import com.android.systemui.settings.UserTracker import com.android.systemui.shared.system.ActivityManagerWrapper import com.android.systemui.shared.system.DevicePolicyManagerWrapper import com.android.systemui.shared.system.PackageManagerWrapper -import com.android.systemui.statusbar.NotificationMediaManager import com.android.systemui.statusbar.NotificationRemoteInputManager import com.android.systemui.statusbar.NotificationShadeWindowController import com.android.systemui.statusbar.RankingBuilder @@ -375,6 +375,7 @@ class ExpandableNotificationRowBuilder( Mockito.mock(PeopleNotificationIdentifier::class.java), Mockito.mock(NotificationIconStyleProvider::class.java), Mockito.mock(VisualStabilityCoordinator::class.java), + Mockito.mock(NotificationActionClickManager::class.java), ) .create(entry) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/NotificationActionClickManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/NotificationActionClickManagerKosmos.kt new file mode 100644 index 000000000000..8e62ae8825f3 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/NotificationActionClickManagerKosmos.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row + +import com.android.systemui.kosmos.Kosmos +import org.mockito.kotlin.mock + +var Kosmos.mockNotificationActionClickManager: NotificationActionClickManager by + Kosmos.Fixture { mock<NotificationActionClickManager>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallTestHelper.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallTestHelper.kt index 8ff7c7d01fb3..3e96fd7c729f 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallTestHelper.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallTestHelper.kt @@ -17,6 +17,8 @@ package com.android.systemui.statusbar.phone.ongoingcall.shared.model import android.app.PendingIntent +import com.android.systemui.activity.data.repository.activityManagerRepository +import com.android.systemui.activity.data.repository.fake import com.android.systemui.kosmos.Kosmos import com.android.systemui.statusbar.StatusBarIconView import com.android.systemui.statusbar.core.StatusBarConnectedDisplays @@ -38,6 +40,7 @@ fun inCallModel( notificationKey: String = "test", appName: String = "", promotedContent: PromotedNotificationContentModel? = null, + isAppVisible: Boolean = false, ) = OngoingCallModel.InCall( startTimeMs, @@ -46,6 +49,7 @@ fun inCallModel( notificationKey, appName, promotedContent, + isAppVisible, ) object OngoingCallTestHelper { @@ -77,8 +81,10 @@ object OngoingCallTestHelper { contentIntent: PendingIntent? = null, uid: Int = DEFAULT_UID, appName: String = "Fake name", + isAppVisible: Boolean = false, ) { if (StatusBarChipsModernization.isEnabled) { + activityManagerRepository.fake.startingIsAppVisibleValue = isAppVisible activeNotificationListRepository.addNotif( activeNotificationModel( key = key, @@ -100,6 +106,7 @@ object OngoingCallTestHelper { notificationKey = key, appName = appName, promotedContent = promotedContent, + isAppVisible = isAppVisible, ) ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileDomainInteractorKairosKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileDomainInteractorKairosKosmos.kt new file mode 100644 index 000000000000..d9a327b99c7f --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileDomainInteractorKairosKosmos.kt @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.mobile.domain.interactor + +import android.content.applicationContext +import com.android.systemui.flags.featureFlagsClassic +import com.android.systemui.kairos.ActivatedKairosFixture +import com.android.systemui.kairos.ExperimentalKairosApi +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.log.table.logcatTableLogBuffer +import com.android.systemui.statusbar.pipeline.mobile.data.repository.mobileConnectionsRepositoryKairos +import com.android.systemui.statusbar.pipeline.shared.data.repository.connectivityRepository +import com.android.systemui.statusbar.policy.data.repository.userSetupRepository +import com.android.systemui.util.carrierConfigTracker + +@ExperimentalKairosApi +val Kosmos.mobileIconsInteractorKairos by ActivatedKairosFixture { + MobileIconsInteractorKairosImpl( + mobileConnectionsRepositoryKairos, + carrierConfigTracker, + logcatTableLogBuffer(this), + connectivityRepository, + userSetupRepository, + applicationContext, + featureFlagsClassic, + ) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelKairosKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelKairosKosmos.kt new file mode 100644 index 000000000000..3ee33802e9d5 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/StackedMobileIconViewModelKairosKosmos.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel + +import com.android.systemui.kosmos.Kosmos + +val Kosmos.stackedMobileIconViewModelKairos by + Kosmos.Fixture { StackedMobileIconViewModelKairos(mobileIconsViewModel) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/topwindoweffects/data/repository/FakeSqueezeEffectRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/topwindoweffects/data/repository/FakeSqueezeEffectRepository.kt new file mode 100644 index 000000000000..2d2a81586515 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/topwindoweffects/data/repository/FakeSqueezeEffectRepository.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.topwindoweffects.data.repository + +import kotlinx.coroutines.flow.MutableStateFlow + +class FakeSqueezeEffectRepository : SqueezeEffectRepository { + override val isSqueezeEffectEnabled = MutableStateFlow(false) +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/topwindoweffects/data/repository/SqueezeEffectRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/topwindoweffects/data/repository/SqueezeEffectRepositoryKosmos.kt new file mode 100644 index 000000000000..aa8bb6b1e104 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/topwindoweffects/data/repository/SqueezeEffectRepositoryKosmos.kt @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.topwindoweffects.data.repository + +import com.android.systemui.kosmos.Kosmos + +val Kosmos.fakeSqueezeEffectRepository by Kosmos.Fixture { FakeSqueezeEffectRepository() }
\ No newline at end of file diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/truth/correspondence/FakeUiEvent.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/truth/correspondence/FakeUiEvent.kt index 48cd345b1f68..6c10526a17fe 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/truth/correspondence/FakeUiEvent.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/truth/correspondence/FakeUiEvent.kt @@ -24,7 +24,7 @@ import com.google.common.truth.Correspondence object FakeUiEvent { val EVENT_ID = Correspondence.transforming<FakeUiEvent, Int>( - { it?.eventId }, + { it.eventId }, "has a eventId of", ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/truth/correspondence/LogMaker.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/truth/correspondence/LogMaker.kt index 3f0a95248d9c..9664bf3d6cc9 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/truth/correspondence/LogMaker.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/truth/correspondence/LogMaker.kt @@ -23,7 +23,7 @@ import com.google.common.truth.Correspondence object LogMaker { val CATEGORY = Correspondence.transforming<LogMaker, Int>( - { it?.category }, + { it.category }, "has a category of", ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/CarrierConfigTrackerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/CarrierConfigTrackerKosmos.kt new file mode 100644 index 000000000000..58473693954c --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/CarrierConfigTrackerKosmos.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.util + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.util.mockito.mockFixture + +var Kosmos.carrierConfigTracker: CarrierConfigTracker by mockFixture() diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodUtils.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodUtils.java index 3e2c4051b792..f25ae6a34242 100644 --- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodUtils.java +++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodUtils.java @@ -128,28 +128,6 @@ public class RavenwoodUtils { runOnHandlerSync(getMainHandler(), r); } - public static class MockitoHelper { - private MockitoHelper() { - } - - /** - * Allow verifyZeroInteractions to work on ravenwood. It was replaced with a different - * method on. (Maybe we should do it in Ravenizer.) - */ - public static void verifyZeroInteractions(Object... mocks) { - if (RavenwoodRule.isOnRavenwood()) { - // Mockito 4 or later - reflectMethod("org.mockito.Mockito", "verifyNoInteractions", Object[].class) - .callStatic(new Object[]{mocks}); - } else { - // Mockito 2 - reflectMethod("org.mockito.Mockito", "verifyZeroInteractions", Object[].class) - .callStatic(new Object[]{mocks}); - } - } - } - - /** * Wrap the given {@link Supplier} to become memoized. * diff --git a/ravenwood/scripts/extract-last-soong-commands.py b/ravenwood/scripts/extract-last-soong-commands.py index 0629b77029e0..b8d6f2042389 100755 --- a/ravenwood/scripts/extract-last-soong-commands.py +++ b/ravenwood/scripts/extract-last-soong-commands.py @@ -55,7 +55,7 @@ def main(args): if s.startswith("verbose"): continue - if re.match('^\[.*bootstrap blueprint', s): + if re.match('^\\[.*bootstrap blueprint', s): continue s = s.rstrip() diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig index b52b3dabd47d..64a4a4ae3c0d 100644 --- a/services/accessibility/accessibility.aconfig +++ b/services/accessibility/accessibility.aconfig @@ -260,6 +260,16 @@ flag { } flag { + name: "pointer_up_motion_event_in_touch_exploration" + namespace: "accessibility" + description: "Allows POINTER_UP motionEvents to trigger during touch exploration." + bug: "374930391" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "proxy_use_apps_on_virtual_device_listener" namespace: "accessibility" description: "Fixes race condition described in b/286587811" diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index c49151dd5e30..573c591cb504 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -521,15 +521,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Nullable IBinder focusedToken) { return AccessibilityManagerService.this.handleKeyGestureEvent(event); } - - @Override - public boolean isKeyGestureSupported(int gestureType) { - return switch (gestureType) { - case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION, - KeyGestureEvent.KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK -> true; - default -> false; - }; - } }; @VisibleForTesting diff --git a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java index 0f6f86b39458..cc93d0887d89 100644 --- a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java +++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java @@ -25,6 +25,7 @@ import static android.view.accessibility.AccessibilityManager.AUTOCLICK_IGNORE_M import static com.android.server.accessibility.autoclick.AutoclickIndicatorView.SHOW_INDICATOR_DELAY_TIME; import static com.android.server.accessibility.autoclick.AutoclickTypePanel.AUTOCLICK_TYPE_LEFT_CLICK; import static com.android.server.accessibility.autoclick.AutoclickTypePanel.AUTOCLICK_TYPE_RIGHT_CLICK; +import static com.android.server.accessibility.autoclick.AutoclickTypePanel.AUTOCLICK_TYPE_SCROLL; import static com.android.server.accessibility.autoclick.AutoclickTypePanel.AutoclickType; import static com.android.server.accessibility.autoclick.AutoclickTypePanel.ClickPanelControllerInterface; @@ -87,6 +88,7 @@ public class AutoclickController extends BaseEventStreamTransformation { @VisibleForTesting AutoclickIndicatorScheduler mAutoclickIndicatorScheduler; @VisibleForTesting AutoclickIndicatorView mAutoclickIndicatorView; @VisibleForTesting AutoclickTypePanel mAutoclickTypePanel; + @VisibleForTesting AutoclickScrollPanel mAutoclickScrollPanel; private WindowManager mWindowManager; // Default click type is left-click. @@ -98,6 +100,11 @@ public class AutoclickController extends BaseEventStreamTransformation { @Override public void handleAutoclickTypeChange(@AutoclickType int clickType) { mActiveClickType = clickType; + + // Hide scroll panel when type is not scroll. + if (clickType != AUTOCLICK_TYPE_SCROLL && mAutoclickScrollPanel != null) { + mAutoclickScrollPanel.hide(); + } } @Override @@ -161,6 +168,7 @@ public class AutoclickController extends BaseEventStreamTransformation { mWindowManager = mContext.getSystemService(WindowManager.class); mAutoclickTypePanel = new AutoclickTypePanel(mContext, mWindowManager, mUserId, clickPanelController); + mAutoclickScrollPanel = new AutoclickScrollPanel(mContext, mWindowManager); mAutoclickTypePanel.show(); mWindowManager.addView(mAutoclickIndicatorView, mAutoclickIndicatorView.getLayoutParams()); @@ -189,6 +197,10 @@ public class AutoclickController extends BaseEventStreamTransformation { mClickScheduler.cancel(); } + if (mAutoclickScrollPanel != null) { + mAutoclickScrollPanel.hide(); + } + super.clearEvents(inputSource); } @@ -209,6 +221,11 @@ public class AutoclickController extends BaseEventStreamTransformation { mWindowManager.removeView(mAutoclickIndicatorView); mAutoclickTypePanel.hide(); } + + if (mAutoclickScrollPanel != null) { + mAutoclickScrollPanel.hide(); + mAutoclickScrollPanel = null; + } } private void handleMouseMotion(MotionEvent event, int policyFlags) { @@ -231,7 +248,11 @@ public class AutoclickController extends BaseEventStreamTransformation { private boolean isPaused() { return Flags.enableAutoclickIndicator() && mAutoclickTypePanel.isPaused() - && !mAutoclickTypePanel.isHovered(); + && !isHovered(); + } + + private boolean isHovered() { + return Flags.enableAutoclickIndicator() && mAutoclickTypePanel.isHovered(); } private void cancelPendingClick() { @@ -478,6 +499,8 @@ public class AutoclickController extends BaseEventStreamTransformation { private int mEventPolicyFlags; /** Current meta state. This value will be used as meta state for click event sequence. */ private int mMetaState; + /** Last observed panel hovered state when click was scheduled. */ + private boolean mHoveredState; /** * The current anchor's coordinates. Should be ignored if #mLastMotionEvent is null. @@ -631,6 +654,7 @@ public class AutoclickController extends BaseEventStreamTransformation { } mLastMotionEvent = MotionEvent.obtain(event); mEventPolicyFlags = policyFlags; + mHoveredState = isHovered(); if (useAsAnchor) { final int pointerIndex = mLastMotionEvent.getActionIndex(); @@ -687,6 +711,14 @@ public class AutoclickController extends BaseEventStreamTransformation { return; } + // Handle scroll type specially, show scroll panel instead of sending click events. + if (mActiveClickType == AutoclickTypePanel.AUTOCLICK_TYPE_SCROLL) { + if (mAutoclickScrollPanel != null) { + mAutoclickScrollPanel.show(); + } + return; + } + final int pointerIndex = mLastMotionEvent.getActionIndex(); if (mTempPointerProperties == null) { @@ -704,14 +736,18 @@ public class AutoclickController extends BaseEventStreamTransformation { final long now = SystemClock.uptimeMillis(); - // TODO(b/395094903): always triggers left-click when the cursor hovers over the - // autoclick type panel, to always allow users to change a different click type. - // Otherwise, if one chooses the right-click, this user won't be able to rely on - // autoclick to select other click types. - final int actionButton = - mActiveClickType == AUTOCLICK_TYPE_RIGHT_CLICK - ? BUTTON_SECONDARY - : BUTTON_PRIMARY; + int actionButton; + if (mHoveredState) { + // Always triggers left-click when the cursor hovers over the autoclick type + // panel, to always allow users to change a different click type. Otherwise, if + // one chooses the right-click, this user won't be able to rely on autoclick to + // select other click types. + actionButton = BUTTON_PRIMARY; + } else { + actionButton = mActiveClickType == AUTOCLICK_TYPE_RIGHT_CLICK + ? BUTTON_SECONDARY + : BUTTON_PRIMARY; + } MotionEvent downEvent = MotionEvent.obtain( diff --git a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickScrollPanel.java b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickScrollPanel.java new file mode 100644 index 000000000000..86f79a83ea28 --- /dev/null +++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickScrollPanel.java @@ -0,0 +1,105 @@ +/* + * Copyright 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.accessibility.autoclick; + +import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; + +import android.content.Context; +import android.graphics.PixelFormat; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.WindowInsets; +import android.view.WindowManager; + +import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; + +import com.android.internal.R; + +public class AutoclickScrollPanel { + private final Context mContext; + private final View mContentView; + private final WindowManager mWindowManager; + private boolean mInScrollMode = false; + + public AutoclickScrollPanel(Context context, WindowManager windowManager) { + mContext = context; + mWindowManager = windowManager; + mContentView = LayoutInflater.from(context).inflate( + R.layout.accessibility_autoclick_scroll_panel, null); + } + + /** + * Shows the autoclick scroll panel. + */ + public void show() { + if (mInScrollMode) { + return; + } + mWindowManager.addView(mContentView, getLayoutParams()); + mInScrollMode = true; + } + + /** + * Hides the autoclick scroll panel. + */ + public void hide() { + if (!mInScrollMode) { + return; + } + mWindowManager.removeView(mContentView); + mInScrollMode = false; + } + + /** + * Retrieves the layout params for AutoclickScrollPanel, used when it's added to the Window + * Manager. + */ + @NonNull + private WindowManager.LayoutParams getLayoutParams() { + final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(); + layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; + layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; + layoutParams.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; + layoutParams.setFitInsetsTypes(WindowInsets.Type.statusBars()); + layoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; + layoutParams.format = PixelFormat.TRANSLUCENT; + layoutParams.setTitle(AutoclickScrollPanel.class.getSimpleName()); + layoutParams.accessibilityTitle = + mContext.getString(R.string.accessibility_autoclick_scroll_panel_title); + layoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT; + layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT; + layoutParams.gravity = Gravity.CENTER; + return layoutParams; + } + + @VisibleForTesting + public boolean isVisible() { + return mInScrollMode; + } + + @VisibleForTesting + public View getContentViewForTesting() { + return mContentView; + } + + @VisibleForTesting + public WindowManager.LayoutParams getLayoutParamsForTesting() { + return getLayoutParams(); + } +} diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java index fb329430acb2..b02fe2752a62 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java @@ -653,6 +653,14 @@ public class TouchExplorer extends BaseEventStreamTransformation case ACTION_UP: handleActionUp(event, rawEvent, policyFlags); break; + case ACTION_POINTER_UP: + if (com.android.server.accessibility.Flags + .pointerUpMotionEventInTouchExploration()) { + if (mState.isServiceDetectingGestures()) { + mAms.sendMotionEventToListeningServices(rawEvent); + } + } + break; default: break; } diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java index cd46b38272c2..568abd196735 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java @@ -26,6 +26,8 @@ import android.view.Display; import android.view.MotionEvent; import android.view.accessibility.AccessibilityEvent; +import androidx.annotation.VisibleForTesting; + import com.android.server.accessibility.AccessibilityManagerService; /** @@ -73,7 +75,8 @@ public class TouchState { private int mState = STATE_CLEAR; // Helper class to track received pointers. // Todo: collapse or hide this class so multiple classes don't modify it. - private final ReceivedPointerTracker mReceivedPointerTracker; + @VisibleForTesting + public final ReceivedPointerTracker mReceivedPointerTracker; // The most recently received motion event. private MotionEvent mLastReceivedEvent; // The accompanying raw event without any transformations. @@ -219,8 +222,19 @@ public class TouchState { startTouchInteracting(); break; case AccessibilityEvent.TYPE_TOUCH_INTERACTION_END: - setState(STATE_CLEAR); - // We will clear when we actually handle the next ACTION_DOWN. + // When interaction ends, check if there are still down pointers. + // If there are any down pointers, go directly to TouchExploring instead. + if (com.android.server.accessibility.Flags + .pointerUpMotionEventInTouchExploration()) { + if (mReceivedPointerTracker.mReceivedPointersDown > 0) { + startTouchExploring(); + } else { + setState(STATE_CLEAR); + // We will clear when we actually handle the next ACTION_DOWN. + } + } else { + setState(STATE_CLEAR); + } break; case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START: startTouchExploring(); @@ -419,7 +433,8 @@ public class TouchState { private final PointerDownInfo[] mReceivedPointers = new PointerDownInfo[MAX_POINTER_COUNT]; // Which pointers are down. - private int mReceivedPointersDown; + @VisibleForTesting + public int mReceivedPointersDown; // The edge flags of the last received down event. private int mLastReceivedDownEdgeFlags; diff --git a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java index 6ccf5e47ca6c..59566677b1fc 100644 --- a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java +++ b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java @@ -39,6 +39,14 @@ import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_ import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__DISPLAY_PRESENTATION_TYPE__INLINE; import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__DISPLAY_PRESENTATION_TYPE__MENU; import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__DISPLAY_PRESENTATION_TYPE__UNKNOWN_AUTOFILL_DISPLAY_PRESENTATION_TYPE; +import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__FILL_DIALOG_NOT_SHOWN_REASON__REASON_DELAY_AFTER_ANIMATION_END; +import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__FILL_DIALOG_NOT_SHOWN_REASON__REASON_FILL_DIALOG_DISABLED; +import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__FILL_DIALOG_NOT_SHOWN_REASON__REASON_LAST_TRIGGERED_ID_CHANGED; +import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__FILL_DIALOG_NOT_SHOWN_REASON__REASON_SCREEN_HAS_CREDMAN_FIELD; +import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__FILL_DIALOG_NOT_SHOWN_REASON__REASON_TIMEOUT_AFTER_DELAY; +import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__FILL_DIALOG_NOT_SHOWN_REASON__REASON_TIMEOUT_SINCE_IME_ANIMATED; +import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__FILL_DIALOG_NOT_SHOWN_REASON__REASON_UNKNOWN; +import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__FILL_DIALOG_NOT_SHOWN_REASON__REASON_WAIT_FOR_IME_ANIMATION; import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__ANY_SHOWN; import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_ACTIVITY_FINISHED; import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_FILL_REQUEST_FAILED; @@ -157,8 +165,24 @@ public final class PresentationStatsEventLogger { DETECTION_PREFER_PCC }) @Retention(RetentionPolicy.SOURCE) - public @interface DetectionPreference { - } + public @interface DetectionPreference {} + + /** + * The fill dialog not shown reason. These are wrappers around + * {@link com.android.os.AtomsProto.AutofillPresentationEventReported.FillDialogNotShownReason}. + */ + @IntDef(prefix = {"FILL_DIALOG_NOT_SHOWN_REASON"}, value = { + FILL_DIALOG_NOT_SHOWN_REASON_UNKNOWN, + FILL_DIALOG_NOT_SHOWN_REASON_FILL_DIALOG_DISABLED, + FILL_DIALOG_NOT_SHOWN_REASON_SCREEN_HAS_CREDMAN_FIELD, + FILL_DIALOG_NOT_SHOWN_REASON_LAST_TRIGGERED_ID_CHANGED, + FILL_DIALOG_NOT_SHOWN_REASON_WAIT_FOR_IME_ANIMATION, + FILL_DIALOG_NOT_SHOWN_REASON_TIMEOUT_SINCE_IME_ANIMATED, + FILL_DIALOG_NOT_SHOWN_REASON_DELAY_AFTER_ANIMATION_END, + FILL_DIALOG_NOT_SHOWN_REASON_TIMEOUT_AFTER_DELAY + }) + @Retention(RetentionPolicy.SOURCE) + public @interface FillDialogNotShownReason {} public static final int NOT_SHOWN_REASON_ANY_SHOWN = AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__ANY_SHOWN; @@ -219,6 +243,25 @@ public final class PresentationStatsEventLogger { public static final int DETECTION_PREFER_PCC = AUTOFILL_FILL_RESPONSE_REPORTED__DETECTION_PREFERENCE__DETECTION_PREFER_PCC; + // Values for AutofillFillResponseReported.fill_dialog_not_shown_reason + public static final int FILL_DIALOG_NOT_SHOWN_REASON_UNKNOWN = + AUTOFILL_PRESENTATION_EVENT_REPORTED__FILL_DIALOG_NOT_SHOWN_REASON__REASON_UNKNOWN; + public static final int FILL_DIALOG_NOT_SHOWN_REASON_FILL_DIALOG_DISABLED = + AUTOFILL_PRESENTATION_EVENT_REPORTED__FILL_DIALOG_NOT_SHOWN_REASON__REASON_FILL_DIALOG_DISABLED; + public static final int FILL_DIALOG_NOT_SHOWN_REASON_SCREEN_HAS_CREDMAN_FIELD = + AUTOFILL_PRESENTATION_EVENT_REPORTED__FILL_DIALOG_NOT_SHOWN_REASON__REASON_SCREEN_HAS_CREDMAN_FIELD; + public static final int FILL_DIALOG_NOT_SHOWN_REASON_LAST_TRIGGERED_ID_CHANGED = + AUTOFILL_PRESENTATION_EVENT_REPORTED__FILL_DIALOG_NOT_SHOWN_REASON__REASON_LAST_TRIGGERED_ID_CHANGED; + public static final int FILL_DIALOG_NOT_SHOWN_REASON_WAIT_FOR_IME_ANIMATION = + AUTOFILL_PRESENTATION_EVENT_REPORTED__FILL_DIALOG_NOT_SHOWN_REASON__REASON_WAIT_FOR_IME_ANIMATION; + public static final int FILL_DIALOG_NOT_SHOWN_REASON_TIMEOUT_SINCE_IME_ANIMATED = + AUTOFILL_PRESENTATION_EVENT_REPORTED__FILL_DIALOG_NOT_SHOWN_REASON__REASON_TIMEOUT_SINCE_IME_ANIMATED; + public static final int FILL_DIALOG_NOT_SHOWN_REASON_DELAY_AFTER_ANIMATION_END = + AUTOFILL_PRESENTATION_EVENT_REPORTED__FILL_DIALOG_NOT_SHOWN_REASON__REASON_DELAY_AFTER_ANIMATION_END; + public static final int FILL_DIALOG_NOT_SHOWN_REASON_TIMEOUT_AFTER_DELAY = + AUTOFILL_PRESENTATION_EVENT_REPORTED__FILL_DIALOG_NOT_SHOWN_REASON__REASON_TIMEOUT_AFTER_DELAY; + + private static final int DEFAULT_VALUE_INT = -1; private final int mSessionId; @@ -871,6 +914,43 @@ public final class PresentationStatsEventLogger { } /** + * Set fill_dialog_not_shown_reason + * @param reason + */ + public void maybeSetFillDialogNotShownReason(@FillDialogNotShownReason int reason) { + mEventInternal.ifPresent(event -> { + if ((event.mFillDialogNotShownReason + == FILL_DIALOG_NOT_SHOWN_REASON_DELAY_AFTER_ANIMATION_END + || event.mFillDialogNotShownReason + == FILL_DIALOG_NOT_SHOWN_REASON_WAIT_FOR_IME_ANIMATION) && reason + == FILL_DIALOG_NOT_SHOWN_REASON_TIMEOUT_SINCE_IME_ANIMATED) { + event.mFillDialogNotShownReason = FILL_DIALOG_NOT_SHOWN_REASON_TIMEOUT_AFTER_DELAY; + } else { + event.mFillDialogNotShownReason = reason; + } + }); + } + + /** + * Set fill_dialog_ready_to_show_ms + * @param val + */ + public void maybeSetFillDialogReadyToShowMs(long val) { + mEventInternal.ifPresent(event -> { + event.mFillDialogReadyToShowMs = (int) (val - mSessionStartTimestamp); + }); + } + + /** + * Set ime_animation_finish_ms + * @param val + */ + public void maybeSetImeAnimationFinishMs(long val) { + mEventInternal.ifPresent(event -> { + event.mImeAnimationFinishMs = (int) (val - mSessionStartTimestamp); + }); + } + /** * Set the log contains relayout metrics. * This is being added as a temporary measure to add logging. * In future, when we map Session's old view states to the new autofill id's as part of fixing @@ -959,7 +1039,13 @@ public final class PresentationStatsEventLogger { + " event.notExpiringResponseDuringAuthCount=" + event.mFixExpireResponseDuringAuthCount + " event.notifyViewEnteredIgnoredDuringAuthCount=" - + event.mNotifyViewEnteredIgnoredDuringAuthCount); + + event.mNotifyViewEnteredIgnoredDuringAuthCount + + " event.fillDialogNotShownReason=" + + event.mFillDialogNotShownReason + + " event.fillDialogReadyToShowMs=" + + event.mFillDialogReadyToShowMs + + " event.imeAnimationFinishMs=" + + event.mImeAnimationFinishMs); } // TODO(b/234185326): Distinguish empty responses from other no presentation reasons. @@ -1020,7 +1106,10 @@ public final class PresentationStatsEventLogger { event.mViewFilledSuccessfullyOnRefillCount, event.mViewFailedOnRefillCount, event.mFixExpireResponseDuringAuthCount, - event.mNotifyViewEnteredIgnoredDuringAuthCount); + event.mNotifyViewEnteredIgnoredDuringAuthCount, + event.mFillDialogNotShownReason, + event.mFillDialogReadyToShowMs, + event.mImeAnimationFinishMs); mEventInternal = Optional.empty(); } @@ -1087,6 +1176,9 @@ public final class PresentationStatsEventLogger { // Following are not logged and used only for internal logic boolean shouldResetShownCount = false; boolean mHasRelayoutLog = false; + @FillDialogNotShownReason int mFillDialogNotShownReason = FILL_DIALOG_NOT_SHOWN_REASON_UNKNOWN; + int mFillDialogReadyToShowMs = DEFAULT_VALUE_INT; + int mImeAnimationFinishMs = DEFAULT_VALUE_INT; PresentationStatsEventInternal() {} } diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 6fdb2b6b83f7..ff3bf2acb080 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -64,6 +64,7 @@ import static com.android.server.autofill.FillResponseEventLogger.DETECTION_PREF import static com.android.server.autofill.FillResponseEventLogger.DETECTION_PREFER_PCC; import static com.android.server.autofill.FillResponseEventLogger.DETECTION_PREFER_UNKNOWN; import static com.android.server.autofill.FillResponseEventLogger.HAVE_SAVE_TRIGGER_ID; +import static com.android.server.autofill.FillResponseEventLogger.RESPONSE_STATUS_CANCELLED; import static com.android.server.autofill.FillResponseEventLogger.RESPONSE_STATUS_FAILURE; import static com.android.server.autofill.FillResponseEventLogger.RESPONSE_STATUS_SESSION_DESTROYED; import static com.android.server.autofill.FillResponseEventLogger.RESPONSE_STATUS_SUCCESS; @@ -80,6 +81,13 @@ import static com.android.server.autofill.PresentationStatsEventLogger.AUTHENTIC import static com.android.server.autofill.PresentationStatsEventLogger.AUTHENTICATION_RESULT_SUCCESS; import static com.android.server.autofill.PresentationStatsEventLogger.AUTHENTICATION_TYPE_DATASET_AUTHENTICATION; import static com.android.server.autofill.PresentationStatsEventLogger.AUTHENTICATION_TYPE_FULL_AUTHENTICATION; +import static com.android.server.autofill.PresentationStatsEventLogger.FILL_DIALOG_NOT_SHOWN_REASON_DELAY_AFTER_ANIMATION_END; +import static com.android.server.autofill.PresentationStatsEventLogger.FILL_DIALOG_NOT_SHOWN_REASON_FILL_DIALOG_DISABLED; +import static com.android.server.autofill.PresentationStatsEventLogger.FILL_DIALOG_NOT_SHOWN_REASON_LAST_TRIGGERED_ID_CHANGED; +import static com.android.server.autofill.PresentationStatsEventLogger.FILL_DIALOG_NOT_SHOWN_REASON_SCREEN_HAS_CREDMAN_FIELD; +import static com.android.server.autofill.PresentationStatsEventLogger.FILL_DIALOG_NOT_SHOWN_REASON_TIMEOUT_SINCE_IME_ANIMATED; +import static com.android.server.autofill.PresentationStatsEventLogger.FILL_DIALOG_NOT_SHOWN_REASON_UNKNOWN; +import static com.android.server.autofill.PresentationStatsEventLogger.FILL_DIALOG_NOT_SHOWN_REASON_WAIT_FOR_IME_ANIMATION; import static com.android.server.autofill.PresentationStatsEventLogger.NOT_SHOWN_REASON_ANY_SHOWN; import static com.android.server.autofill.PresentationStatsEventLogger.NOT_SHOWN_REASON_NO_FOCUS; import static com.android.server.autofill.PresentationStatsEventLogger.NOT_SHOWN_REASON_REQUEST_FAILED; @@ -1416,6 +1424,15 @@ final class Session // Remove the FillContext as there will never be a response for the service if (canceledRequest != INVALID_REQUEST_ID && mContexts != null) { + // Start a new FillResponse logger for the cancellation case. + mFillResponseEventLogger.startLogForNewResponse(); + mFillResponseEventLogger.maybeSetRequestId(canceledRequest); + mFillResponseEventLogger.maybeSetAppPackageUid(uid); + mFillResponseEventLogger.maybeSetResponseStatus(RESPONSE_STATUS_CANCELLED); + mFillResponseEventLogger.maybeSetLatencyFillResponseReceivedMillis( + (int) (SystemClock.elapsedRealtime() - mLatencyBaseTime)); + mFillResponseEventLogger.logAndEndEvent(); + final int numContexts = mContexts.size(); // It is most likely the last context, hence search backwards @@ -5612,6 +5629,10 @@ final class Session synchronized (mLock) { final ViewState currentView = mViewStates.get(mCurrentViewId); currentView.setState(ViewState.STATE_FILL_DIALOG_SHOWN); + // Set fill_dialog_not_shown_reason to unknown (a.k.a shown). It is needed due + // to possible SHOW_FILL_DIALOG_WAIT. + mPresentationStatsEventLogger.maybeSetFillDialogNotShownReason( + FILL_DIALOG_NOT_SHOWN_REASON_UNKNOWN); } // Just show fill dialog once per fill request, so disabled after shown. // Note: Cannot disable before requestShowFillDialog() because the method @@ -5715,6 +5736,15 @@ final class Session private boolean isFillDialogUiEnabled() { synchronized (mLock) { + if (mSessionFlags.mFillDialogDisabled) { + mPresentationStatsEventLogger.maybeSetFillDialogNotShownReason( + FILL_DIALOG_NOT_SHOWN_REASON_FILL_DIALOG_DISABLED); + } + if (mSessionFlags.mScreenHasCredmanField) { + // Prefer to log "HAS_CREDMAN_FIELD" over "FILL_DIALOG_DISABLED". + mPresentationStatsEventLogger.maybeSetFillDialogNotShownReason( + FILL_DIALOG_NOT_SHOWN_REASON_SCREEN_HAS_CREDMAN_FIELD); + } return !mSessionFlags.mFillDialogDisabled && !mSessionFlags.mScreenHasCredmanField; } } @@ -5779,6 +5809,8 @@ final class Session || !ArrayUtils.contains(mLastFillDialogTriggerIds, filledId)) { // Last fill dialog triggered ids are changed. if (sDebug) Log.w(TAG, "Last fill dialog triggered ids are changed."); + mPresentationStatsEventLogger.maybeSetFillDialogNotShownReason( + FILL_DIALOG_NOT_SHOWN_REASON_LAST_TRIGGERED_ID_CHANGED); return SHOW_FILL_DIALOG_NO; } @@ -5805,6 +5837,8 @@ final class Session // we need to wait for animation to happen. We can't return from here yet. // This is the situation #2 described above. Log.d(TAG, "Waiting for ime animation to complete before showing fill dialog"); + mPresentationStatsEventLogger.maybeSetFillDialogNotShownReason( + FILL_DIALOG_NOT_SHOWN_REASON_WAIT_FOR_IME_ANIMATION); mFillDialogRunnable = createFillDialogEvalRunnable( response, filledId, filterText, flags); return SHOW_FILL_DIALOG_WAIT; @@ -5814,9 +5848,15 @@ final class Session // max of start input time or the ime finish time long effectiveDuration = currentTimestampMs - Math.max(mLastInputStartTime, mImeAnimationFinishTimeMs); + mPresentationStatsEventLogger.maybeSetFillDialogReadyToShowMs( + currentTimestampMs); + mPresentationStatsEventLogger.maybeSetImeAnimationFinishMs( + Math.max(mLastInputStartTime, mImeAnimationFinishTimeMs)); if (effectiveDuration >= mFillDialogTimeoutMs) { Log.d(TAG, "Fill dialog not shown since IME has been up for more time than " + mFillDialogTimeoutMs + "ms"); + mPresentationStatsEventLogger.maybeSetFillDialogNotShownReason( + FILL_DIALOG_NOT_SHOWN_REASON_TIMEOUT_SINCE_IME_ANIMATED); return SHOW_FILL_DIALOG_NO; } else if (effectiveDuration < mFillDialogMinWaitAfterImeAnimationMs) { // we need to wait for some time after animation ends @@ -5824,6 +5864,8 @@ final class Session response, filledId, filterText, flags); mHandler.postDelayed(runnable, mFillDialogMinWaitAfterImeAnimationMs - effectiveDuration); + mPresentationStatsEventLogger.maybeSetFillDialogNotShownReason( + FILL_DIALOG_NOT_SHOWN_REASON_DELAY_AFTER_ANIMATION_END); return SHOW_FILL_DIALOG_WAIT; } } diff --git a/services/backup/java/com/android/server/backup/BackupAgentConnectionManager.java b/services/backup/java/com/android/server/backup/BackupAgentConnectionManager.java index 9a353fbc45bf..867cd51e1c2b 100644 --- a/services/backup/java/com/android/server/backup/BackupAgentConnectionManager.java +++ b/services/backup/java/com/android/server/backup/BackupAgentConnectionManager.java @@ -44,6 +44,7 @@ import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.server.LocalServices; +import com.android.server.backup.BackupRestoreTask.CancellationReason; import com.android.server.backup.internal.LifecycleOperationStorage; import java.util.Set; @@ -298,20 +299,22 @@ public class BackupAgentConnectionManager { // Offload operation cancellation off the main thread as the cancellation callbacks // might call out to BackupTransport. Other operations started on the same package // before the cancellation callback has executed will also be cancelled by the callback. - Runnable cancellationRunnable = () -> { - // handleCancel() causes the PerformFullTransportBackupTask to go on to - // tearDownAgentAndKill: that will unbindBackupAgent in the Activity Manager, so - // that the package being backed up doesn't get stuck in restricted mode until the - // backup time-out elapses. - for (int token : mOperationStorage.operationTokensForPackage(packageName)) { - if (DEBUG) { - Slog.d(TAG, - mUserIdMsg + "agentDisconnected: will handleCancel(all) for token:" - + Integer.toHexString(token)); - } - mUserBackupManagerService.handleCancel(token, true /* cancelAll */); - } - }; + Runnable cancellationRunnable = + () -> { + // On handleCancel(), the operation will call unbindAgent() which will make + // sure the app doesn't get stuck in restricted mode. + for (int token : mOperationStorage.operationTokensForPackage(packageName)) { + if (DEBUG) { + Slog.d( + TAG, + mUserIdMsg + + "agentDisconnected: cancelling for token:" + + Integer.toHexString(token)); + } + mUserBackupManagerService.handleCancel( + token, CancellationReason.AGENT_DISCONNECTED); + } + }; getThreadForCancellation(cancellationRunnable).start(); mAgentConnectLock.notifyAll(); diff --git a/services/backup/java/com/android/server/backup/BackupRestoreTask.java b/services/backup/java/com/android/server/backup/BackupRestoreTask.java index acaab0c54191..7ec5f0d786ed 100644 --- a/services/backup/java/com/android/server/backup/BackupRestoreTask.java +++ b/services/backup/java/com/android/server/backup/BackupRestoreTask.java @@ -16,9 +16,12 @@ package com.android.server.backup; -/** - * Interface and methods used by the asynchronous-with-timeout backup/restore operations. - */ +import android.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** Interface and methods used by the asynchronous-with-timeout backup/restore operations. */ public interface BackupRestoreTask { // Execute one tick of whatever state machine the task implements @@ -27,6 +30,24 @@ public interface BackupRestoreTask { // An operation that wanted a callback has completed void operationComplete(long result); - // An operation that wanted a callback has timed out - void handleCancel(boolean cancelAll); + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + CancellationReason.TIMEOUT, + CancellationReason.AGENT_DISCONNECTED, + CancellationReason.EXTERNAL, + CancellationReason.SCHEDULED_JOB_STOPPED, + }) + @interface CancellationReason { + // The task timed out. + int TIMEOUT = 0; + // The agent went away before the task was able to finish (e.g. due to an app crash). + int AGENT_DISCONNECTED = 1; + // An external caller cancelled the operation (e.g. via BackupManager#cancelBackups). + int EXTERNAL = 2; + // The job scheduler has stopped an ongoing scheduled backup pass. + int SCHEDULED_JOB_STOPPED = 3; + } + + /** The task is cancelled for the given {@link CancellationReason}. */ + void handleCancel(@CancellationReason int cancellationReason); } diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java index 2143aaaa4cd6..b3af444ff9bd 100644 --- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java +++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java @@ -102,6 +102,7 @@ import com.android.internal.util.Preconditions; import com.android.server.AppWidgetBackupBridge; import com.android.server.EventLogTags; import com.android.server.LocalServices; +import com.android.server.backup.BackupRestoreTask.CancellationReason; import com.android.server.backup.OperationStorage.OpState; import com.android.server.backup.OperationStorage.OpType; import com.android.server.backup.fullbackup.FullBackupEntry; @@ -168,6 +169,7 @@ import java.util.Random; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.IntConsumer; /** System service that performs backup/restore operations. */ public class UserBackupManagerService { @@ -1816,11 +1818,9 @@ public class UserBackupManagerService { for (Integer token : operationsToCancel) { mOperationStorage.cancelOperation( - token, /* cancelAll */ - true, - operationType -> { - /* no callback needed here */ - }); + token, + operationType -> {}, // no callback needed here + CancellationReason.EXTERNAL); } // We don't want the backup jobs to kick in any time soon. // Reschedules them to run in the distant future. @@ -1897,19 +1897,17 @@ public class UserBackupManagerService { } /** Cancel the operation associated with {@code token}. */ - public void handleCancel(int token, boolean cancelAll) { + public void handleCancel(int token, @CancellationReason int cancellationReason) { // Remove all pending timeout messages of types OpType.BACKUP_WAIT and // OpType.RESTORE_WAIT. On the other hand, OP_TYPE_BACKUP cannot time out and // doesn't require cancellation. - mOperationStorage.cancelOperation( - token, - cancelAll, - operationType -> { - if (operationType == OpType.BACKUP_WAIT - || operationType == OpType.RESTORE_WAIT) { - mBackupHandler.removeMessages(getMessageIdForOperationType(operationType)); + IntConsumer timeoutCallback = + opType -> { + if (opType == OpType.BACKUP_WAIT || opType == OpType.RESTORE_WAIT) { + mBackupHandler.removeMessages(getMessageIdForOperationType(opType)); } - }); + }; + mOperationStorage.cancelOperation(token, timeoutCallback, cancellationReason); } /** Returns {@code true} if a backup is currently running, else returns {@code false}. */ @@ -2219,20 +2217,17 @@ public class UserBackupManagerService { // offload the mRunningFullBackupTask.handleCancel() call to another thread, // as we might have to wait for mCancelLock Runnable endFullBackupRunnable = - new Runnable() { - @Override - public void run() { - PerformFullTransportBackupTask pftbt = null; - synchronized (mQueueLock) { - if (mRunningFullBackupTask != null) { - pftbt = mRunningFullBackupTask; - } - } - if (pftbt != null) { - Slog.i(TAG, mLogIdMsg + "Telling running backup to stop"); - pftbt.handleCancel(true); + () -> { + PerformFullTransportBackupTask pftbt = null; + synchronized (mQueueLock) { + if (mRunningFullBackupTask != null) { + pftbt = mRunningFullBackupTask; } } + if (pftbt != null) { + Slog.i(TAG, mLogIdMsg + "Telling running backup to stop"); + pftbt.handleCancel(CancellationReason.SCHEDULED_JOB_STOPPED); + } }; new Thread(endFullBackupRunnable, "end-full-backup").start(); } diff --git a/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java b/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java index ebb1194c7c4a..b173f76e5f6f 100644 --- a/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java +++ b/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java @@ -25,6 +25,7 @@ import static com.android.server.backup.UserBackupManagerService.SHARED_BACKUP_A import android.annotation.UserIdInt; import android.app.ApplicationThreadConstants; import android.app.IBackupAgent; +import android.app.backup.BackupManagerMonitor; import android.app.backup.BackupTransport; import android.app.backup.FullBackupDataOutput; import android.content.pm.ApplicationInfo; @@ -268,6 +269,12 @@ public class FullBackupEngine { mBackupManagerMonitorEventSender.monitorAgentLoggingResults(mPkg, mAgent); } catch (IOException e) { Slog.e(TAG, "Error backing up " + mPkg.packageName + ": " + e.getMessage()); + // This is likely due to the app process dying. + mBackupManagerMonitorEventSender.monitorEvent( + BackupManagerMonitor.LOG_EVENT_ID_FULL_BACKUP_AGENT_PIPE_BROKEN, + mPkg, + BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT, + /* extras= */ null); result = BackupTransport.AGENT_ERROR; } finally { try { diff --git a/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java index 0d4364e14e03..7fc9ed3e0213 100644 --- a/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java +++ b/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java @@ -484,7 +484,7 @@ public class PerformAdbBackupTask extends FullBackupTask implements BackupRestor } @Override - public void handleCancel(boolean cancelAll) { + public void handleCancel(@CancellationReason int cancellationReason) { final PackageInfo target = mCurrentTarget; Slog.w(TAG, "adb backup cancel of " + target); if (target != null) { diff --git a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java index c182c2618fdf..48d21c3a222f 100644 --- a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java +++ b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java @@ -162,7 +162,7 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba // This is true when a backup operation for some package is in progress. private volatile boolean mIsDoingBackup; - private volatile boolean mCancelAll; + private volatile boolean mCancelled; private final int mCurrentOpToken; private final BackupAgentTimeoutParameters mAgentTimeoutParameters; private final BackupEligibilityRules mBackupEligibilityRules; @@ -199,7 +199,7 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba if (backupManagerService.isBackupOperationInProgress()) { Slog.d(TAG, "Skipping full backup. A backup is already in progress."); - mCancelAll = true; + mCancelled = true; return; } @@ -287,25 +287,31 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba } @Override - public void handleCancel(boolean cancelAll) { + public void handleCancel(@CancellationReason int cancellationReason) { synchronized (mCancelLock) { - // We only support 'cancelAll = true' case for this task. Cancelling of a single package - - // due to timeout is handled by SinglePackageBackupRunner and + // This callback is only used for cancelling the entire backup operation. Cancelling of + // a single package due to timeout is handled by SinglePackageBackupRunner and // SinglePackageBackupPreflight. + if (cancellationReason == CancellationReason.TIMEOUT) { + Slog.wtf(TAG, "This task cannot time out"); + return; + } - if (!cancelAll) { - Slog.wtf(TAG, "Expected cancelAll to be true."); + // We don't cancel the entire operation if a single agent is disconnected unexpectedly. + // SinglePackageBackupRunner and SinglePackageBackupPreflight will receive the same + // callback and fail gracefully. The operation should then continue to the next package. + if (cancellationReason == CancellationReason.AGENT_DISCONNECTED) { + return; } - if (mCancelAll) { + if (mCancelled) { Slog.d(TAG, "Ignoring duplicate cancel call."); return; } - mCancelAll = true; + mCancelled = true; if (mIsDoingBackup) { - mUserBackupManagerService.handleCancel(mBackupRunnerOpToken, cancelAll); + mUserBackupManagerService.handleCancel(mBackupRunnerOpToken, cancellationReason); try { // If we're running a backup we should be connected to a transport BackupTransportClient transport = @@ -410,7 +416,7 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba int backupPackageStatus; long quota = Long.MAX_VALUE; synchronized (mCancelLock) { - if (mCancelAll) { + if (mCancelled) { break; } backupPackageStatus = transport.performFullBackup(currentPackage, @@ -478,7 +484,7 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba if (nRead > 0) { out.write(buffer, 0, nRead); synchronized (mCancelLock) { - if (!mCancelAll) { + if (!mCancelled) { backupPackageStatus = transport.sendBackupData(nRead); } } @@ -509,7 +515,7 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba synchronized (mCancelLock) { mIsDoingBackup = false; // If mCancelCurrent is true, we have already called cancelFullBackup(). - if (!mCancelAll) { + if (!mCancelled) { if (backupRunnerResult == BackupTransport.TRANSPORT_OK) { // If we were otherwise in a good state, now interpret the final // result based on what finishBackup() returns. If we're in a @@ -607,7 +613,7 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba .sendBackupOnPackageResult(mBackupObserver, packageName, BackupManager.ERROR_BACKUP_CANCELLED); Slog.w(TAG, "Backup cancelled. package=" + packageName + - ", cancelAll=" + mCancelAll); + ", entire session cancelled=" + mCancelled); EventLog.writeEvent(EventLogTags.FULL_BACKUP_CANCELLED, packageName); mUserBackupManagerService.getBackupAgentConnectionManager().unbindAgent( currentPackage.applicationInfo, /* allowKill= */ true); @@ -654,7 +660,7 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba } finally { - if (mCancelAll) { + if (mCancelled) { backupRunStatus = BackupManager.ERROR_BACKUP_CANCELLED; } @@ -820,7 +826,7 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba } @Override - public void handleCancel(boolean cancelAll) { + public void handleCancel(@CancellationReason int cancellationReason) { if (DEBUG) { Slog.i(TAG, "Preflight cancelled; failing"); } @@ -974,7 +980,7 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba public void operationComplete(long result) { /* intentionally empty */ } @Override - public void handleCancel(boolean cancelAll) { + public void handleCancel(@CancellationReason int cancellationReason) { Slog.w(TAG, "Full backup cancel of " + mTarget.packageName); mBackupManagerMonitorEventSender.monitorEvent( @@ -984,7 +990,7 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba /* extras= */ null); mIsCancelled = true; // Cancel tasks spun off by this task. - mUserBackupManagerService.handleCancel(mEphemeralToken, cancelAll); + mUserBackupManagerService.handleCancel(mEphemeralToken, cancellationReason); mUserBackupManagerService.getBackupAgentConnectionManager().unbindAgent( mTarget.applicationInfo, /* allowKill= */ true); // Free up everyone waiting on this task and its children. diff --git a/services/backup/java/com/android/server/backup/internal/BackupHandler.java b/services/backup/java/com/android/server/backup/internal/BackupHandler.java index 87cf8a313651..464dc2dfe1ec 100644 --- a/services/backup/java/com/android/server/backup/internal/BackupHandler.java +++ b/services/backup/java/com/android/server/backup/internal/BackupHandler.java @@ -34,6 +34,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.server.EventLogTags; import com.android.server.backup.BackupAgentTimeoutParameters; import com.android.server.backup.BackupRestoreTask; +import com.android.server.backup.BackupRestoreTask.CancellationReason; import com.android.server.backup.DataChangedJournal; import com.android.server.backup.OperationStorage; import com.android.server.backup.TransportManager; @@ -410,8 +411,8 @@ public class BackupHandler extends Handler { case MSG_BACKUP_OPERATION_TIMEOUT: case MSG_RESTORE_OPERATION_TIMEOUT: { - Slog.d(TAG, "Timeout message received for token=" + Integer.toHexString(msg.arg1)); - backupManagerService.handleCancel(msg.arg1, false); + Slog.d(TAG, "Timeout for token=" + Integer.toHexString(msg.arg1)); + backupManagerService.handleCancel(msg.arg1, CancellationReason.TIMEOUT); break; } diff --git a/services/backup/java/com/android/server/backup/internal/LifecycleOperationStorage.java b/services/backup/java/com/android/server/backup/internal/LifecycleOperationStorage.java index 0b974e2d0a8a..5aacb2f4f007 100644 --- a/services/backup/java/com/android/server/backup/internal/LifecycleOperationStorage.java +++ b/services/backup/java/com/android/server/backup/internal/LifecycleOperationStorage.java @@ -24,6 +24,7 @@ import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.server.backup.BackupRestoreTask; +import com.android.server.backup.BackupRestoreTask.CancellationReason; import com.android.server.backup.OperationStorage; import com.google.android.collect.Sets; @@ -296,20 +297,18 @@ public class LifecycleOperationStorage implements OperationStorage { } /** - * Cancel the operation associated with {@code token}. Cancellation may be - * propagated to the operation's callback (a {@link BackupRestoreTask}) if - * the operation has one, and the cancellation is due to the operation - * timing out. + * Cancel the operation associated with {@code token}. Cancellation may be propagated to the + * operation's callback (a {@link BackupRestoreTask}) if the operation has one, and the + * cancellation is due to the operation timing out. * * @param token the operation token specified when registering the operation - * @param cancelAll this is passed on when propagating the cancellation - * @param operationTimedOutCallback a lambda that is invoked with the - * operation type where the operation is - * cancelled due to timeout, allowing the - * caller to do type-specific clean-ups. + * @param operationTimedOutCallback a lambda that is invoked with the operation type where the + * operation is cancelled due to timeout, allowing the caller to do type-specific clean-ups. */ public void cancelOperation( - int token, boolean cancelAll, IntConsumer operationTimedOutCallback) { + int token, + IntConsumer operationTimedOutCallback, + @CancellationReason int cancellationReason) { // Notify any synchronous waiters Operation op = null; synchronized (mOperationsLock) { @@ -343,7 +342,7 @@ public class LifecycleOperationStorage implements OperationStorage { if (DEBUG) { Slog.v(TAG, "[UserID:" + mUserId + " Invoking cancel on " + op.callback); } - op.callback.handleCancel(cancelAll); + op.callback.handleCancel(cancellationReason); } } } diff --git a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java index 494b9d59a238..8e7a23ccbb25 100644 --- a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java +++ b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java @@ -171,7 +171,7 @@ import java.util.concurrent.atomic.AtomicInteger; * complete backup should be performed. * * <p>This task is designed to run on a dedicated thread, with the exception of the {@link - * #handleCancel(boolean)} method, which can be called from any thread. + * BackupRestoreTask#handleCancel(int)} method, which can be called from any thread. */ // TODO: Stop poking into BMS state and doing things for it (e.g. synchronizing on public locks) // TODO: Consider having the caller responsible for some clean-up (like resetting state) @@ -1208,13 +1208,13 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { * * <p>Note: This method is inherently racy since there are no guarantees about how much of the * task will be executed after you made the call. - * - * @param cancelAll MUST be {@code true}. Will be removed. */ @Override - public void handleCancel(boolean cancelAll) { + public void handleCancel(@CancellationReason int cancellationReason) { // This is called in a thread different from the one that executes method run(). - Preconditions.checkArgument(cancelAll, "Can't partially cancel a key-value backup task"); + Preconditions.checkArgument( + cancellationReason != CancellationReason.TIMEOUT, + "Key-value backup task cannot time out"); markCancel(); waitCancel(); } diff --git a/services/backup/java/com/android/server/backup/restore/AdbRestoreFinishedLatch.java b/services/backup/java/com/android/server/backup/restore/AdbRestoreFinishedLatch.java index cb491c6f384e..f1829b6966a8 100644 --- a/services/backup/java/com/android/server/backup/restore/AdbRestoreFinishedLatch.java +++ b/services/backup/java/com/android/server/backup/restore/AdbRestoreFinishedLatch.java @@ -79,7 +79,7 @@ public class AdbRestoreFinishedLatch implements BackupRestoreTask { } @Override - public void handleCancel(boolean cancelAll) { + public void handleCancel(@CancellationReason int cancellationReason) { Slog.w(TAG, "adb onRestoreFinished() timed out"); mLatch.countDown(); mOperationStorage.removeOperation(mCurrentOpToken); diff --git a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java index 707ae03b3964..20f103cdfab4 100644 --- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java +++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java @@ -589,6 +589,7 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { monitoringExtras); Slog.e(TAG, "Failure getting next package name"); EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE); + mStatus = BackupTransport.TRANSPORT_ERROR; nextState = UnifiedRestoreState.FINAL; return; } else if (mRestoreDescription == RestoreDescription.NO_MORE_PACKAGES) { @@ -1307,7 +1308,7 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { // The app has timed out handling a restoring file @Override - public void handleCancel(boolean cancelAll) { + public void handleCancel(@CancellationReason int cancellationReason) { mOperationStorage.removeOperation(mEphemeralOpToken); Slog.w(TAG, "Full-data restore target timed out; shutting down"); Bundle monitoringExtras = addRestoreOperationTypeToEvent(/* extras= */ null); @@ -1555,7 +1556,7 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { // A call to agent.doRestore() or agent.doRestoreFinished() has timed out @Override - public void handleCancel(boolean cancelAll) { + public void handleCancel(@CancellationReason int cancellationReason) { mOperationStorage.removeOperation(mEphemeralOpToken); Slog.e(TAG, "Timeout restoring application " + mCurrentPackage.packageName); Bundle monitoringExtras = addRestoreOperationTypeToEvent(/* extras= */ null); diff --git a/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorDumpsysUtils.java b/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorDumpsysUtils.java index fad59d23a6dc..855c72acd7ca 100644 --- a/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorDumpsysUtils.java +++ b/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorDumpsysUtils.java @@ -389,6 +389,8 @@ public class BackupManagerMonitorDumpsysUtils { "Agent failure during restore"; case BackupManagerMonitor.LOG_EVENT_ID_FAILED_TO_READ_DATA_FROM_TRANSPORT -> "Failed to read data from Transport"; + case BackupManagerMonitor.LOG_EVENT_ID_FULL_BACKUP_AGENT_PIPE_BROKEN -> + "LOG_EVENT_ID_FULL_BACKUP_AGENT_PIPE_BROKEN"; default -> "Unknown log event ID: " + code; }; return id; diff --git a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java index cd9285cdfe91..cbee8391458d 100644 --- a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java +++ b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java @@ -58,13 +58,16 @@ import android.os.Handler; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.UserHandle; +import android.util.ArraySet; import android.util.Slog; import com.android.internal.R; import com.android.server.companion.CompanionDeviceManagerService; import com.android.server.companion.utils.PackageUtils; +import java.util.Arrays; import java.util.List; +import java.util.Set; /** * Class responsible for handling incoming {@link AssociationRequest}s. @@ -130,6 +133,12 @@ public class AssociationRequestsProcessor { private static final int ASSOCIATE_WITHOUT_PROMPT_MAX_PER_TIME_WINDOW = 5; private static final long ASSOCIATE_WITHOUT_PROMPT_WINDOW_MS = 60 * 60 * 1000; // 60 min; + // Set of profiles for which the association dialog cannot be skipped. + private static final Set<String> DEVICE_PROFILES_WITH_REQUIRED_CONFIRMATION = new ArraySet<>( + Arrays.asList( + AssociationRequest.DEVICE_PROFILE_APP_STREAMING, + AssociationRequest.DEVICE_PROFILE_NEARBY_DEVICE_STREAMING)); + private final @NonNull Context mContext; private final @NonNull PackageManagerInternal mPackageManagerInternal; private final @NonNull AssociationStore mAssociationStore; @@ -174,6 +183,7 @@ public class AssociationRequestsProcessor { // 2a. Check if association can be created without launching UI (i.e. CDM needs NEITHER // to perform discovery NOR to collect user consent). if (request.isSelfManaged() && !request.isForceConfirmation() + && !DEVICE_PROFILES_WITH_REQUIRED_CONFIRMATION.contains(request.getDeviceProfile()) && !willAddRoleHolder(request, packageName, userId)) { // 2a.1. Create association right away. createAssociationAndNotifyApplication(request, packageName, userId, diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java index 93b4de856463..caf535ce7a40 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java @@ -142,14 +142,6 @@ public class VirtualDeviceManagerService extends SystemService { @GuardedBy("mVirtualDeviceManagerLock") private ArrayMap<String, AssociationInfo> mActiveAssociations = new ArrayMap<>(); - private final CompanionDeviceManager.OnAssociationsChangedListener mCdmAssociationListener = - new CompanionDeviceManager.OnAssociationsChangedListener() { - @Override - public void onAssociationsChanged(@NonNull List<AssociationInfo> associations) { - syncVirtualDevicesToCdmAssociations(associations); - } - }; - private class StrongAuthTracker extends LockPatternUtils.StrongAuthTracker { final Set<Integer> mUsersInLockdown = new ArraySet<>(); @@ -348,33 +340,6 @@ public class VirtualDeviceManagerService extends SystemService { return true; } - private void syncVirtualDevicesToCdmAssociations(List<AssociationInfo> associations) { - Set<VirtualDeviceImpl> virtualDevicesToRemove = new HashSet<>(); - synchronized (mVirtualDeviceManagerLock) { - if (mVirtualDevices.size() == 0) { - return; - } - - Set<Integer> activeAssociationIds = new HashSet<>(associations.size()); - for (AssociationInfo association : associations) { - activeAssociationIds.add(association.getId()); - } - - for (int i = 0; i < mVirtualDevices.size(); i++) { - VirtualDeviceImpl virtualDevice = mVirtualDevices.valueAt(i); - int deviceAssociationId = virtualDevice.getAssociationId(); - if (deviceAssociationId != CDM_ASSOCIATION_ID_NONE - && !activeAssociationIds.contains(deviceAssociationId)) { - virtualDevicesToRemove.add(virtualDevice); - } - } - } - - for (VirtualDeviceImpl virtualDevice : virtualDevicesToRemove) { - virtualDevice.close(); - } - } - void onCdmAssociationsChanged(List<AssociationInfo> associations) { ArrayMap<String, AssociationInfo> vdmAssociations = new ArrayMap<>(); for (int i = 0; i < associations.size(); ++i) { diff --git a/services/core/Android.bp b/services/core/Android.bp index 14d9d3f0c0a1..decac40d20f8 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -122,10 +122,10 @@ genrule { } genrule { - name: "statslog-mediarouter-java-gen", - tools: ["stats-log-api-gen"], - cmd: "$(location stats-log-api-gen) --java $(out) --module mediarouter --javaPackage com.android.server.media --javaClass MediaRouterStatsLog", - out: ["com/android/server/media/MediaRouterStatsLog.java"], + name: "statslog-mediarouter-java-gen", + tools: ["stats-log-api-gen"], + cmd: "$(location stats-log-api-gen) --java $(out) --module mediarouter --javaPackage com.android.server.media --javaClass MediaRouterStatsLog", + out: ["com/android/server/media/MediaRouterStatsLog.java"], } java_library_static { @@ -138,6 +138,7 @@ java_library_static { "ondeviceintelligence_conditionally", ], srcs: [ + ":android.hardware.audio.effect-V1-java-source", ":android.hardware.tv.hdmi.connection-V1-java-source", ":android.hardware.tv.hdmi.earc-V1-java-source", ":android.hardware.tv.mediaquality-V1-java-source", diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java index 96b30d4e1285..d60c6c534871 100644 --- a/services/core/java/com/android/server/Watchdog.java +++ b/services/core/java/com/android/server/Watchdog.java @@ -165,7 +165,6 @@ public class Watchdog implements Dumpable { "android.hardware.sensors@1.0::ISensors", "android.hardware.sensors@2.0::ISensors", "android.hardware.sensors@2.1::ISensors", - "android.hardware.vibrator@1.0::IVibrator", "android.hardware.vr@1.0::IVr", "android.system.suspend@1.0::ISystemSuspend" ); diff --git a/services/core/java/com/android/server/adb/AdbDebuggingManager.java b/services/core/java/com/android/server/adb/AdbDebuggingManager.java index a73a991bc6a6..658ea4c27e4c 100644 --- a/services/core/java/com/android/server/adb/AdbDebuggingManager.java +++ b/services/core/java/com/android/server/adb/AdbDebuggingManager.java @@ -130,8 +130,6 @@ import java.util.concurrent.atomic.AtomicBoolean; */ public class AdbDebuggingManager { private static final String TAG = AdbDebuggingManager.class.getSimpleName(); - private static final boolean DEBUG = false; - private static final boolean MDNS_DEBUG = false; private static final String ADBD_SOCKET = "adbd"; private static final String ADB_DIRECTORY = "misc/adb"; @@ -156,8 +154,6 @@ public class AdbDebuggingManager { @Nullable private final File mUserKeyFile; @Nullable private final File mTempKeysFile; - private static final String WIFI_PERSISTENT_CONFIG_PROPERTY = - "persist.adb.tls_server.enable"; private static final String WIFI_PERSISTENT_GUID = "persist.adb.wifi.guid"; private static final int PAIRING_CODE_LENGTH = 6; @@ -261,12 +257,10 @@ public class AdbDebuggingManager { mHandler.sendMessage(msg); boolean paired = native_pairing_wait(); - if (DEBUG) { - if (mPublicKey != null) { - Slog.i(TAG, "Pairing succeeded key=" + mPublicKey); - } else { - Slog.i(TAG, "Pairing failed"); - } + if (mPublicKey != null) { + Slog.i(TAG, "Pairing succeeded key=" + mPublicKey); + } else { + Slog.i(TAG, "Pairing failed"); } mNsdManager.unregisterService(this); @@ -307,7 +301,7 @@ public class AdbDebuggingManager { @Override public void onServiceRegistered(NsdServiceInfo serviceInfo) { - if (MDNS_DEBUG) Slog.i(TAG, "Registered pairing service: " + serviceInfo); + Slog.i(TAG, "Registered pairing service: " + serviceInfo); } @Override @@ -319,7 +313,7 @@ public class AdbDebuggingManager { @Override public void onServiceUnregistered(NsdServiceInfo serviceInfo) { - if (MDNS_DEBUG) Slog.i(TAG, "Unregistered pairing service: " + serviceInfo); + Slog.i(TAG, "Unregistered pairing service: " + serviceInfo); } @Override @@ -354,7 +348,7 @@ public class AdbDebuggingManager { @Override public void run() { - if (DEBUG) Slog.d(TAG, "Starting adb port property poller"); + Slog.d(TAG, "Starting adb port property poller"); // Once adbwifi is enabled, we poll the service.adb.tls.port // system property until we get the port, or -1 on failure. // Let's also limit the polling to 10 seconds, just in case @@ -390,7 +384,7 @@ public class AdbDebuggingManager { class PortListenerImpl implements AdbConnectionPortListener { public void onPortReceived(int port) { - if (DEBUG) Slog.d(TAG, "Received tls port=" + port); + Slog.d(TAG, "Received tls port=" + port); Message msg = mHandler.obtainMessage(port > 0 ? AdbDebuggingHandler.MSG_SERVER_CONNECTED : AdbDebuggingHandler.MSG_SERVER_DISCONNECTED); @@ -419,11 +413,11 @@ public class AdbDebuggingManager { @Override public void run() { - if (DEBUG) Slog.d(TAG, "Entering thread"); + Slog.d(TAG, "Entering thread"); while (true) { synchronized (this) { if (mStopped) { - if (DEBUG) Slog.d(TAG, "Exiting thread"); + Slog.d(TAG, "Exiting thread"); return; } try { @@ -448,7 +442,7 @@ public class AdbDebuggingManager { LocalSocketAddress.Namespace.RESERVED); mInputStream = null; - if (DEBUG) Slog.d(TAG, "Creating socket"); + Slog.d(TAG, "Creating socket"); mSocket = new LocalSocket(LocalSocket.SOCKET_SEQPACKET); mSocket.connect(address); @@ -549,7 +543,7 @@ public class AdbDebuggingManager { } private void closeSocketLocked() { - if (DEBUG) Slog.d(TAG, "Closing socket"); + Slog.d(TAG, "Closing socket"); try { if (mOutputStream != null) { mOutputStream.close(); @@ -859,7 +853,7 @@ public class AdbDebuggingManager { private void startAdbDebuggingThread() { ++mAdbEnabledRefCount; - if (DEBUG) Slog.i(TAG, "startAdbDebuggingThread ref=" + mAdbEnabledRefCount); + Slog.i(TAG, "startAdbDebuggingThread ref=" + mAdbEnabledRefCount); if (mAdbEnabledRefCount > 1) { return; } @@ -875,7 +869,7 @@ public class AdbDebuggingManager { private void stopAdbDebuggingThread() { --mAdbEnabledRefCount; - if (DEBUG) Slog.i(TAG, "stopAdbDebuggingThread ref=" + mAdbEnabledRefCount); + Slog.i(TAG, "stopAdbDebuggingThread ref=" + mAdbEnabledRefCount); if (mAdbEnabledRefCount > 0) { return; } @@ -1093,7 +1087,7 @@ public class AdbDebuggingManager { intentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); mContext.registerReceiver(mBroadcastReceiver, intentFilter); - SystemProperties.set(WIFI_PERSISTENT_CONFIG_PROPERTY, "1"); + SystemProperties.set(AdbService.WIFI_PERSISTENT_CONFIG_PROPERTY, "1"); mConnectionPortPoller = new AdbDebuggingManager.AdbConnectionPortPoller(mPortListener); mConnectionPortPoller.start(); @@ -1101,7 +1095,7 @@ public class AdbDebuggingManager { startAdbDebuggingThread(); mAdbWifiEnabled = true; - if (DEBUG) Slog.i(TAG, "adb start wireless adb"); + Slog.i(TAG, "adb start wireless adb"); break; } case MSG_ADBDWIFI_DISABLE: @@ -1143,7 +1137,7 @@ public class AdbDebuggingManager { intentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); mContext.registerReceiver(mBroadcastReceiver, intentFilter); - SystemProperties.set(WIFI_PERSISTENT_CONFIG_PROPERTY, "1"); + SystemProperties.set(AdbService.WIFI_PERSISTENT_CONFIG_PROPERTY, "1"); mConnectionPortPoller = new AdbDebuggingManager.AdbConnectionPortPoller(mPortListener); mConnectionPortPoller.start(); @@ -1151,7 +1145,7 @@ public class AdbDebuggingManager { startAdbDebuggingThread(); mAdbWifiEnabled = true; - if (DEBUG) Slog.i(TAG, "adb start wireless adb"); + Slog.i(TAG, "adb start wireless adb"); break; case MSG_ADBWIFI_DENY: Settings.Global.putInt(mContentResolver, @@ -1259,7 +1253,7 @@ public class AdbDebuggingManager { break; } case MSG_ADBD_SOCKET_CONNECTED: { - if (DEBUG) Slog.d(TAG, "adbd socket connected"); + Slog.d(TAG, "adbd socket connected"); if (mAdbWifiEnabled) { // In scenarios where adbd is restarted, the tls port may change. mConnectionPortPoller = @@ -1269,7 +1263,7 @@ public class AdbDebuggingManager { break; } case MSG_ADBD_SOCKET_DISCONNECTED: { - if (DEBUG) Slog.d(TAG, "adbd socket disconnected"); + Slog.d(TAG, "adbd socket disconnected"); if (mConnectionPortPoller != null) { mConnectionPortPoller.cancelAndWait(); mConnectionPortPoller = null; @@ -1477,7 +1471,7 @@ public class AdbDebuggingManager { } private void updateUIPairCode(String code) { - if (DEBUG) Slog.i(TAG, "updateUIPairCode: " + code); + Slog.i(TAG, "updateUIPairCode: " + code); Intent intent = new Intent(AdbManager.WIRELESS_DEBUG_PAIRING_RESULT_ACTION); intent.putExtra(AdbManager.WIRELESS_PAIRING_CODE_EXTRA, code); diff --git a/services/core/java/com/android/server/adb/AdbService.java b/services/core/java/com/android/server/adb/AdbService.java index 55d8dba69626..aae48daa5dde 100644 --- a/services/core/java/com/android/server/adb/AdbService.java +++ b/services/core/java/com/android/server/adb/AdbService.java @@ -222,15 +222,14 @@ public class AdbService extends IAdbManager.Stub { } } - private static final String TAG = "AdbService"; - private static final boolean DEBUG = false; + private static final String TAG = AdbService.class.getSimpleName(); /** * The persistent property which stores whether adb is enabled or not. * May also contain vendor-specific default functions for testing purposes. */ private static final String USB_PERSISTENT_CONFIG_PROPERTY = "persist.sys.usb.config"; - private static final String WIFI_PERSISTENT_CONFIG_PROPERTY = "persist.adb.tls_server.enable"; + static final String WIFI_PERSISTENT_CONFIG_PROPERTY = "persist.adb.tls_server.enable"; private final Context mContext; private final ContentResolver mContentResolver; @@ -256,7 +255,7 @@ public class AdbService extends IAdbManager.Stub { * SystemServer}. */ public void systemReady() { - if (DEBUG) Slog.d(TAG, "systemReady"); + Slog.d(TAG, "systemReady"); /* * Use the normal bootmode persistent prop to maintain state of adb across @@ -287,7 +286,7 @@ public class AdbService extends IAdbManager.Stub { * Called in response to {@code SystemService.PHASE_BOOT_COMPLETED} from {@code SystemServer}. */ public void bootCompleted() { - if (DEBUG) Slog.d(TAG, "boot completed"); + Slog.d(TAG, "boot completed"); if (mDebuggingManager != null) { mDebuggingManager.setAdbEnabled(mIsAdbUsbEnabled, AdbTransportType.USB); mDebuggingManager.setAdbEnabled(mIsAdbWifiEnabled, AdbTransportType.WIFI); @@ -429,17 +428,13 @@ public class AdbService extends IAdbManager.Stub { @Override public void registerCallback(IAdbCallback callback) throws RemoteException { - if (DEBUG) { - Slog.d(TAG, "Registering callback " + callback); - } + Slog.d(TAG, "Registering callback " + callback); mCallbacks.register(callback); } @Override public void unregisterCallback(IAdbCallback callback) throws RemoteException { - if (DEBUG) { - Slog.d(TAG, "Unregistering callback " + callback); - } + Slog.d(TAG, "Unregistering callback " + callback); mCallbacks.unregister(callback); } /** @@ -500,11 +495,8 @@ public class AdbService extends IAdbManager.Stub { } private void setAdbEnabled(boolean enable, byte transportType) { - if (DEBUG) { - Slog.d(TAG, "setAdbEnabled(" + enable + "), mIsAdbUsbEnabled=" + mIsAdbUsbEnabled - + ", mIsAdbWifiEnabled=" + mIsAdbWifiEnabled + ", transportType=" - + transportType); - } + Slog.d(TAG, "setAdbEnabled(" + enable + "), mIsAdbUsbEnabled=" + mIsAdbUsbEnabled + + ", mIsAdbWifiEnabled=" + mIsAdbWifiEnabled + ", transportType=" + transportType); if (transportType == AdbTransportType.USB && enable != mIsAdbUsbEnabled) { mIsAdbUsbEnabled = enable; @@ -549,20 +541,14 @@ public class AdbService extends IAdbManager.Stub { mDebuggingManager.setAdbEnabled(enable, transportType); } - if (DEBUG) { - Slog.d(TAG, "Broadcasting enable = " + enable + ", type = " + transportType); - } + Slog.d(TAG, "Broadcasting enable = " + enable + ", type = " + transportType); mCallbacks.broadcast((callback) -> { - if (DEBUG) { - Slog.d(TAG, "Sending enable = " + enable + ", type = " + transportType - + " to " + callback); - } + Slog.d(TAG, "Sending enable = " + enable + ", type = " + transportType + " to " + + callback); try { callback.onDebuggingChanged(enable, transportType); } catch (RemoteException ex) { - if (DEBUG) { - Slog.d(TAG, "Unable to send onDebuggingChanged:", ex); - } + Slog.w(TAG, "Unable to send onDebuggingChanged:", ex); } }); } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index b0b34d0ab9c4..76ba0054583b 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -16190,14 +16190,16 @@ public class ActivityManagerService extends IActivityManager.Stub return mUserController.switchUser(targetUserId); } + @Nullable @Override - public String getSwitchingFromUserMessage() { - return mUserController.getSwitchingFromSystemUserMessage(); + public String getSwitchingFromUserMessage(@UserIdInt int userId) { + return mUserController.getSwitchingFromUserMessage(userId); } + @Nullable @Override - public String getSwitchingToUserMessage() { - return mUserController.getSwitchingToSystemUserMessage(); + public String getSwitchingToUserMessage(@UserIdInt int userId) { + return mUserController.getSwitchingToUserMessage(userId); } @Override @@ -16938,13 +16940,13 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override - public void setSwitchingFromSystemUserMessage(String switchingFromSystemUserMessage) { - mUserController.setSwitchingFromSystemUserMessage(switchingFromSystemUserMessage); + public void setSwitchingFromUserMessage(@UserIdInt int userId, @Nullable String message) { + mUserController.setSwitchingFromUserMessage(userId, message); } @Override - public void setSwitchingToSystemUserMessage(String switchingToSystemUserMessage) { - mUserController.setSwitchingToSystemUserMessage(switchingToSystemUserMessage); + public void setSwitchingToUserMessage(@UserIdInt int userId, @Nullable String message) { + mUserController.setSwitchingToUserMessage(userId, message); } @Override diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java index db0562f5750a..508c01802156 100644 --- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java +++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java @@ -810,7 +810,7 @@ class BroadcastProcessQueue { * Return the broadcast being actively dispatched in this process. */ public @NonNull BroadcastRecord getActive() { - return Objects.requireNonNull(mActive); + return Objects.requireNonNull(mActive, toString()); } /** @@ -818,7 +818,7 @@ class BroadcastProcessQueue { * being actively dispatched in this process. */ public int getActiveIndex() { - Objects.requireNonNull(mActive); + Objects.requireNonNull(mActive, toString()); return mActiveIndex; } diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java index c76a0d0ac59a..d276b9a94791 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java @@ -606,8 +606,9 @@ class BroadcastQueueImpl extends BroadcastQueue { } else { mRunningColdStart.reEnqueueActiveBroadcast(); } - demoteFromRunningLocked(mRunningColdStart); + final BroadcastProcessQueue queue = mRunningColdStart; clearRunningColdStart(); + demoteFromRunningLocked(queue); enqueueUpdateRunningList(); } @@ -1527,6 +1528,15 @@ class BroadcastQueueImpl extends BroadcastQueue { final int cookie = traceBegin("demoteFromRunning"); // We've drained running broadcasts; maybe move back to runnable + if (mRunningColdStart == queue) { + // TODO: b/399020479 - Remove wtf log once we identify the case where mRunningColdStart + // is not getting cleared. + // If this queue is mRunningColdStart, then it should have been cleared before + // it is demoted. Log a wtf if this isn't the case. + Slog.wtf(TAG, "mRunningColdStart has not been cleared; mRunningColdStart.app: " + + mRunningColdStart.app + " , queue.app: " + queue.app, + new IllegalStateException()); + } queue.makeActiveIdle(); queue.traceProcessEnd(); @@ -2332,12 +2342,6 @@ class BroadcastQueueImpl extends BroadcastQueue { @VisibleForTesting @GuardedBy("mService") - @Nullable BroadcastProcessQueue removeProcessQueue(@NonNull ProcessRecord app) { - return removeProcessQueue(app.processName, app.info.uid); - } - - @VisibleForTesting - @GuardedBy("mService") @Nullable BroadcastProcessQueue removeProcessQueue(@NonNull String processName, int uid) { BroadcastProcessQueue prev = null; diff --git a/services/core/java/com/android/server/am/ConnectionRecord.java b/services/core/java/com/android/server/am/ConnectionRecord.java index 31704c442290..4e1d77c26129 100644 --- a/services/core/java/com/android/server/am/ConnectionRecord.java +++ b/services/core/java/com/android/server/am/ConnectionRecord.java @@ -142,6 +142,10 @@ final class ConnectionRecord implements OomAdjusterModernImpl.Connection{ | Context.BIND_BYPASS_USER_NETWORK_RESTRICTIONS); } + @Override + public boolean transmitsCpuTime() { + return !hasFlag(Context.BIND_ALLOW_FREEZE); + } public long getFlags() { return flags; @@ -273,6 +277,9 @@ final class ConnectionRecord implements OomAdjusterModernImpl.Connection{ if (hasFlag(Context.BIND_INCLUDE_CAPABILITIES)) { sb.append("CAPS "); } + if (hasFlag(Context.BIND_ALLOW_FREEZE)) { + sb.append("!CPU "); + } if (serviceDead) { sb.append("DEAD "); } diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index 336a35e7a7e3..fa35da30bf4b 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -2802,7 +2802,7 @@ public class OomAdjuster { // we check the final procstate, and remove it if the procsate is below BFGS. capability |= getBfslCapabilityFromClient(client); - capability |= getCpuCapabilityFromClient(client); + capability |= getCpuCapabilityFromClient(cr, client); if (cr.notHasFlag(Context.BIND_WAIVE_PRIORITY)) { if (cr.hasFlag(Context.BIND_INCLUDE_CAPABILITIES)) { @@ -3259,7 +3259,7 @@ public class OomAdjuster { // we check the final procstate, and remove it if the procsate is below BFGS. capability |= getBfslCapabilityFromClient(client); - capability |= getCpuCapabilityFromClient(client); + capability |= getCpuCapabilityFromClient(conn, client); if (clientProcState >= PROCESS_STATE_CACHED_ACTIVITY) { // If the other app is cached for any reason, for purposes here @@ -3502,10 +3502,13 @@ public class OomAdjuster { /** * @return the CPU capability from a client (of a service binding or provider). */ - private static int getCpuCapabilityFromClient(ProcessRecord client) { - // Just grant CPU capability every time - // TODO(b/370817323): Populate with reasons to not propagate cpu capability across bindings. - return client.mState.getCurCapability() & PROCESS_CAPABILITY_CPU_TIME; + private static int getCpuCapabilityFromClient(OomAdjusterModernImpl.Connection conn, + ProcessRecord client) { + if (conn == null || conn.transmitsCpuTime()) { + return client.mState.getCurCapability() & PROCESS_CAPABILITY_CPU_TIME; + } else { + return 0; + } } /** diff --git a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java index 1b7e8f0bd244..7e7b5685cf13 100644 --- a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java +++ b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java @@ -635,6 +635,15 @@ public class OomAdjusterModernImpl extends OomAdjuster { * Returns true if this connection can propagate capabilities. */ boolean canAffectCapabilities(); + + /** + * Returns whether this connection transmits PROCESS_CAPABILITY_CPU_TIME to the host, if the + * client possesses it. + */ + default boolean transmitsCpuTime() { + // Always lend this capability by default. + return true; + } } /** diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java index 3a041fd3b38a..cfd22fbdeece 100644 --- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java +++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java @@ -187,10 +187,12 @@ public class SettingsToPropertiesMapper { "crumpet", "dck_framework", "desktop_apps", + "desktop_audio", "desktop_better_together", "desktop_bsp", "desktop_camera", "desktop_connectivity", + "desktop_dev_experience", "desktop_display", "desktop_commercial", "desktop_firmware", @@ -199,10 +201,14 @@ public class SettingsToPropertiesMapper { "desktop_input", "desktop_kernel", "desktop_ml", + "desktop_networking", "desktop_serviceability", "desktop_oobe", "desktop_peripherals", + "desktop_personalization", "desktop_pnp", + "desktop_privacy", + "desktop_release", "desktop_security", "desktop_stats", "desktop_sysui", diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index 18f3500b2d56..40a9bbec3598 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -340,16 +340,16 @@ class UserController implements Handler.Callback { private volatile ArraySet<String> mCurWaitingUserSwitchCallbacks; /** - * Messages for switching from {@link android.os.UserHandle#SYSTEM}. + * Message shown when switching from a user. */ @GuardedBy("mLock") - private String mSwitchingFromSystemUserMessage; + private final SparseArray<String> mSwitchingFromUserMessage = new SparseArray<>(); /** - * Messages for switching to {@link android.os.UserHandle#SYSTEM}. + * Message shown when switching to a user. */ @GuardedBy("mLock") - private String mSwitchingToSystemUserMessage; + private final SparseArray<String> mSwitchingToUserMessage = new SparseArray<>(); /** * Callbacks that are still active after {@link #getUserSwitchTimeoutMs} @@ -2271,8 +2271,8 @@ class UserController implements Handler.Callback { private void showUserSwitchDialog(Pair<UserInfo, UserInfo> fromToUserPair) { // The dialog will show and then initiate the user switch by calling startUserInForeground mInjector.showUserSwitchingDialog(fromToUserPair.first, fromToUserPair.second, - getSwitchingFromSystemUserMessageUnchecked(), - getSwitchingToSystemUserMessageUnchecked(), + getSwitchingFromUserMessageUnchecked(fromToUserPair.first.id), + getSwitchingToUserMessageUnchecked(fromToUserPair.second.id), /* onShown= */ () -> sendStartUserSwitchFgMessage(fromToUserPair.second.id)); } @@ -3388,41 +3388,45 @@ class UserController implements Handler.Callback { return mLockPatternUtils.isLockScreenDisabled(userId); } - void setSwitchingFromSystemUserMessage(String switchingFromSystemUserMessage) { + void setSwitchingFromUserMessage(@UserIdInt int user, @Nullable String message) { synchronized (mLock) { - mSwitchingFromSystemUserMessage = switchingFromSystemUserMessage; + mSwitchingFromUserMessage.put(user, message); } } - void setSwitchingToSystemUserMessage(String switchingToSystemUserMessage) { + void setSwitchingToUserMessage(@UserIdInt int user, @Nullable String message) { synchronized (mLock) { - mSwitchingToSystemUserMessage = switchingToSystemUserMessage; + mSwitchingToUserMessage.put(user, message); } } // Called by AMS, must check permission - String getSwitchingFromSystemUserMessage() { - checkHasManageUsersPermission("getSwitchingFromSystemUserMessage()"); + @Nullable + String getSwitchingFromUserMessage(@UserIdInt int userId) { + checkHasManageUsersPermission("getSwitchingFromUserMessage()"); - return getSwitchingFromSystemUserMessageUnchecked(); + return getSwitchingFromUserMessageUnchecked(userId); } // Called by AMS, must check permission - String getSwitchingToSystemUserMessage() { - checkHasManageUsersPermission("getSwitchingToSystemUserMessage()"); + @Nullable + String getSwitchingToUserMessage(@UserIdInt int userId) { + checkHasManageUsersPermission("getSwitchingToUserMessage()"); - return getSwitchingToSystemUserMessageUnchecked(); + return getSwitchingToUserMessageUnchecked(userId); } - private String getSwitchingFromSystemUserMessageUnchecked() { + @Nullable + private String getSwitchingFromUserMessageUnchecked(@UserIdInt int userId) { synchronized (mLock) { - return mSwitchingFromSystemUserMessage; + return mSwitchingFromUserMessage.get(userId); } } - private String getSwitchingToSystemUserMessageUnchecked() { + @Nullable + private String getSwitchingToUserMessageUnchecked(@UserIdInt int userId) { synchronized (mLock) { - return mSwitchingToSystemUserMessage; + return mSwitchingToUserMessage.get(userId); } } @@ -3518,12 +3522,8 @@ class UserController implements Handler.Callback { + mIsBroadcastSentForSystemUserStarted); pw.println(" mIsBroadcastSentForSystemUserStarting:" + mIsBroadcastSentForSystemUserStarting); - if (mSwitchingFromSystemUserMessage != null) { - pw.println(" mSwitchingFromSystemUserMessage: " + mSwitchingFromSystemUserMessage); - } - if (mSwitchingToSystemUserMessage != null) { - pw.println(" mSwitchingToSystemUserMessage: " + mSwitchingToSystemUserMessage); - } + pw.println(" mSwitchingFromUserMessage:" + mSwitchingFromUserMessage); + pw.println(" mSwitchingToUserMessage:" + mSwitchingToUserMessage); pw.println(" mLastUserUnlockingUptime: " + mLastUserUnlockingUptime); } } @@ -4046,7 +4046,7 @@ class UserController implements Handler.Callback { } void showUserSwitchingDialog(UserInfo fromUser, UserInfo toUser, - String switchingFromSystemUserMessage, String switchingToSystemUserMessage, + @Nullable String switchingFromUserMessage, @Nullable String switchingToUserMessage, @NonNull Runnable onShown) { if (mService.mContext.getPackageManager() .hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) { @@ -4059,7 +4059,7 @@ class UserController implements Handler.Callback { synchronized (mUserSwitchingDialogLock) { dismissUserSwitchingDialog(null); mUserSwitchingDialog = new UserSwitchingDialog(mService.mContext, fromUser, toUser, - mHandler, switchingFromSystemUserMessage, switchingToSystemUserMessage); + mHandler, switchingFromUserMessage, switchingToUserMessage); mUserSwitchingDialog.show(onShown); } } diff --git a/services/core/java/com/android/server/am/UserSwitchingDialog.java b/services/core/java/com/android/server/am/UserSwitchingDialog.java index 223e0b79ec0b..f4e733a0c99f 100644 --- a/services/core/java/com/android/server/am/UserSwitchingDialog.java +++ b/services/core/java/com/android/server/am/UserSwitchingDialog.java @@ -52,6 +52,7 @@ import com.android.internal.R; import com.android.internal.util.ObjectUtils; import com.android.internal.util.UserIcons; +import java.util.Objects; import java.util.concurrent.atomic.AtomicBoolean; /** @@ -75,21 +76,23 @@ class UserSwitchingDialog extends Dialog { protected final UserInfo mOldUser; protected final UserInfo mNewUser; - private final String mSwitchingFromSystemUserMessage; - private final String mSwitchingToSystemUserMessage; + @Nullable + private final String mSwitchingFromUserMessage; + @Nullable + private final String mSwitchingToUserMessage; protected final Context mContext; private final int mTraceCookie; UserSwitchingDialog(Context context, UserInfo oldUser, UserInfo newUser, Handler handler, - String switchingFromSystemUserMessage, String switchingToSystemUserMessage) { + @Nullable String switchingFromUserMessage, @Nullable String switchingToUserMessage) { super(context, R.style.Theme_Material_NoActionBar_Fullscreen); mContext = context; mOldUser = oldUser; mNewUser = newUser; mHandler = handler; - mSwitchingFromSystemUserMessage = switchingFromSystemUserMessage; - mSwitchingToSystemUserMessage = switchingToSystemUserMessage; + mSwitchingFromUserMessage = switchingFromUserMessage; + mSwitchingToUserMessage = switchingToUserMessage; mDisableAnimations = SystemProperties.getBoolean( "debug.usercontroller.disable_user_switching_dialog_animations", false); mTraceCookie = UserHandle.MAX_SECONDARY_USER_ID * oldUser.id + newUser.id; @@ -166,14 +169,14 @@ class UserSwitchingDialog extends Dialog { : R.string.demo_starting_message); } - final String message = - mOldUser.id == UserHandle.USER_SYSTEM ? mSwitchingFromSystemUserMessage - : mNewUser.id == UserHandle.USER_SYSTEM ? mSwitchingToSystemUserMessage : null; + if (mSwitchingFromUserMessage != null || mSwitchingToUserMessage != null) { + if (mSwitchingFromUserMessage != null && mSwitchingToUserMessage != null) { + return mSwitchingFromUserMessage + " " + mSwitchingToUserMessage; + } + return Objects.requireNonNullElse(mSwitchingFromUserMessage, mSwitchingToUserMessage); + } - return message != null ? message - // If switchingFromSystemUserMessage or switchingToSystemUserMessage is null, - // fallback to system message. - : res.getString(R.string.user_switching_message, mNewUser.name); + return res.getString(R.string.user_switching_message, mNewUser.name); } @Override diff --git a/services/core/java/com/android/server/appop/AttributedOp.java b/services/core/java/com/android/server/appop/AttributedOp.java index 9dd09cef88f9..40085ed89294 100644 --- a/services/core/java/com/android/server/appop/AttributedOp.java +++ b/services/core/java/com/android/server/appop/AttributedOp.java @@ -113,7 +113,7 @@ final class AttributedOp { mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, parent.packageName, persistentDeviceId, tag, uidState, flags, accessTime, AppOpsManager.ATTRIBUTION_FLAGS_NONE, AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE, - DiscreteOpsRegistry.ACCESS_TYPE_NOTE_OP, accessCount); + accessCount); } /** @@ -257,8 +257,7 @@ final class AttributedOp { if (isStarted) { mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, parent.packageName, persistentDeviceId, tag, uidState, flags, startTime, - attributionFlags, attributionChainId, - DiscreteOpsRegistry.ACCESS_TYPE_START_OP, 1); + attributionFlags, attributionChainId, 1); } } @@ -344,9 +343,7 @@ final class AttributedOp { mAppOpsService.mHistoricalRegistry.increaseOpAccessDuration(parent.op, parent.uid, parent.packageName, persistentDeviceId, tag, event.getUidState(), event.getFlags(), finishedEvent.getNoteTime(), finishedEvent.getDuration(), - event.getAttributionFlags(), event.getAttributionChainId(), - isPausing ? DiscreteOpsRegistry.ACCESS_TYPE_PAUSE_OP - : DiscreteOpsRegistry.ACCESS_TYPE_FINISH_OP); + event.getAttributionFlags(), event.getAttributionChainId()); if (!isPausing) { mAppOpsService.mInProgressStartOpEventPool.release(event); @@ -454,7 +451,7 @@ final class AttributedOp { mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, parent.packageName, persistentDeviceId, tag, event.getUidState(), event.getFlags(), startTime, event.getAttributionFlags(), - event.getAttributionChainId(), DiscreteOpsRegistry.ACCESS_TYPE_RESUME_OP, 1); + event.getAttributionChainId(), 1); if (shouldSendActive) { mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid, parent.packageName, tag, event.getVirtualDeviceId(), true, diff --git a/services/core/java/com/android/server/appop/DiscreteOpsDbHelper.java b/services/core/java/com/android/server/appop/DiscreteOpsDbHelper.java index 86f5d9bd637f..c53e4bdc2205 100644 --- a/services/core/java/com/android/server/appop/DiscreteOpsDbHelper.java +++ b/services/core/java/com/android/server/appop/DiscreteOpsDbHelper.java @@ -189,11 +189,11 @@ class DiscreteOpsDbHelper extends SQLiteOpenHelper { @AppOpsManager.HistoricalOpsRequestFilter int requestFilters, int uidFilter, @Nullable String packageNameFilter, @Nullable String attributionTagFilter, IntArray opCodesFilter, int opFlagsFilter, - long beginTime, long endTime, int limit, String orderByColumn) { + long beginTime, long endTime, int limit, String orderByColumn, boolean ascending) { List<SQLCondition> conditions = prepareConditions(beginTime, endTime, requestFilters, uidFilter, packageNameFilter, attributionTagFilter, opCodesFilter, opFlagsFilter); - String sql = buildSql(conditions, orderByColumn, limit); + String sql = buildSql(conditions, orderByColumn, ascending, limit); long startTime = 0; if (Flags.sqliteDiscreteOpEventLoggingEnabled()) { startTime = SystemClock.elapsedRealtime(); @@ -249,7 +249,8 @@ class DiscreteOpsDbHelper extends SQLiteOpenHelper { return results; } - private String buildSql(List<SQLCondition> conditions, String orderByColumn, int limit) { + private String buildSql(List<SQLCondition> conditions, String orderByColumn, boolean ascending, + int limit) { StringBuilder sql = new StringBuilder(DiscreteOpsTable.SELECT_TABLE_DATA); if (!conditions.isEmpty()) { sql.append(" WHERE "); @@ -264,6 +265,7 @@ class DiscreteOpsDbHelper extends SQLiteOpenHelper { if (orderByColumn != null) { sql.append(" ORDER BY ").append(orderByColumn); + sql.append(ascending ? " ASC " : " DESC "); } if (limit > 0) { sql.append(" LIMIT ").append(limit); diff --git a/services/core/java/com/android/server/appop/DiscreteOpsMigrationHelper.java b/services/core/java/com/android/server/appop/DiscreteOpsMigrationHelper.java index c38ee55b4f42..29e78be93da9 100644 --- a/services/core/java/com/android/server/appop/DiscreteOpsMigrationHelper.java +++ b/services/core/java/com/android/server/appop/DiscreteOpsMigrationHelper.java @@ -40,7 +40,16 @@ public class DiscreteOpsMigrationHelper { static void migrateDiscreteOpsToXml(DiscreteOpsSqlRegistry sqlRegistry, DiscreteOpsXmlRegistry xmlRegistry) { List<DiscreteOpsSqlRegistry.DiscreteOp> sqlOps = sqlRegistry.getAllDiscreteOps(); - DiscreteOpsXmlRegistry.DiscreteOps xmlOps = getXmlDiscreteOps(sqlOps); + + // Only migrate configured discrete ops. Sqlite may contain all runtime ops, and more. + List<DiscreteOpsSqlRegistry.DiscreteOp> filteredList = new ArrayList<>(); + for (DiscreteOpsSqlRegistry.DiscreteOp opEvent: sqlOps) { + if (DiscreteOpsRegistry.isDiscreteOp(opEvent.getOpCode(), opEvent.getOpFlags())) { + filteredList.add(opEvent); + } + } + + DiscreteOpsXmlRegistry.DiscreteOps xmlOps = getXmlDiscreteOps(filteredList); xmlRegistry.migrateSqliteData(xmlOps); sqlRegistry.deleteDatabase(); } diff --git a/services/core/java/com/android/server/appop/DiscreteOpsRegistry.java b/services/core/java/com/android/server/appop/DiscreteOpsRegistry.java index 12c35ae92cbe..70b7016fbb90 100644 --- a/services/core/java/com/android/server/appop/DiscreteOpsRegistry.java +++ b/services/core/java/com/android/server/appop/DiscreteOpsRegistry.java @@ -16,6 +16,9 @@ package com.android.server.appop; +import static android.app.AppOpsManager.OP_ACCESS_ACCESSIBILITY; +import static android.app.AppOpsManager.OP_ACCESS_NOTIFICATIONS; +import static android.app.AppOpsManager.OP_BIND_ACCESSIBILITY_SERVICE; import static android.app.AppOpsManager.OP_CAMERA; import static android.app.AppOpsManager.OP_COARSE_LOCATION; import static android.app.AppOpsManager.OP_EMERGENCY_LOCATION; @@ -23,30 +26,24 @@ import static android.app.AppOpsManager.OP_FINE_LOCATION; import static android.app.AppOpsManager.OP_FLAG_SELF; import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED; import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXY; +import static android.app.AppOpsManager.OP_GPS; import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION; import static android.app.AppOpsManager.OP_MONITOR_LOCATION; import static android.app.AppOpsManager.OP_PHONE_CALL_CAMERA; import static android.app.AppOpsManager.OP_PHONE_CALL_MICROPHONE; -import static android.app.AppOpsManager.OP_PROCESS_OUTGOING_CALLS; +import static android.app.AppOpsManager.OP_READ_DEVICE_IDENTIFIERS; import static android.app.AppOpsManager.OP_READ_HEART_RATE; -import static android.app.AppOpsManager.OP_READ_ICC_SMS; import static android.app.AppOpsManager.OP_READ_OXYGEN_SATURATION; import static android.app.AppOpsManager.OP_READ_SKIN_TEMPERATURE; -import static android.app.AppOpsManager.OP_READ_SMS; import static android.app.AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO; import static android.app.AppOpsManager.OP_RECEIVE_SANDBOX_TRIGGER_AUDIO; import static android.app.AppOpsManager.OP_RECORD_AUDIO; import static android.app.AppOpsManager.OP_RESERVED_FOR_TESTING; -import static android.app.AppOpsManager.OP_SEND_SMS; -import static android.app.AppOpsManager.OP_SMS_FINANCIAL_TRANSACTIONS; -import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW; -import static android.app.AppOpsManager.OP_WRITE_ICC_SMS; -import static android.app.AppOpsManager.OP_WRITE_SMS; +import static android.app.AppOpsManager.OP_RUN_IN_BACKGROUND; import static java.lang.Long.min; import static java.lang.Math.max; -import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.AppOpsManager; @@ -54,16 +51,13 @@ import android.os.AsyncTask; import android.os.Build; import android.permission.flags.Flags; import android.provider.DeviceConfig; +import android.util.IntArray; import android.util.Slog; -import com.android.internal.util.ArrayUtils; -import com.android.internal.util.FrameworkStatsLog; - import java.io.PrintWriter; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.text.SimpleDateFormat; import java.time.Duration; +import java.util.Arrays; import java.util.Date; import java.util.Set; @@ -100,21 +94,37 @@ abstract class DiscreteOpsRegistry { static final String PROPERTY_DISCRETE_HISTORY_QUANTIZATION = "discrete_history_quantization_millis"; static final String PROPERTY_DISCRETE_FLAGS = "discrete_history_op_flags"; + // Comma separated app ops list config for testing i.e. "1,2,3,4" static final String PROPERTY_DISCRETE_OPS_LIST = "discrete_history_ops_cslist"; - static final String DEFAULT_DISCRETE_OPS = OP_FINE_LOCATION + "," + OP_COARSE_LOCATION + // These ops are deemed important for detecting a malicious app, and are recorded. + static final int[] IMPORTANT_OPS_FOR_SECURITY = new int[] { + OP_GPS, + OP_ACCESS_NOTIFICATIONS, + OP_RUN_IN_BACKGROUND, + OP_BIND_ACCESSIBILITY_SERVICE, + OP_ACCESS_ACCESSIBILITY, + OP_READ_DEVICE_IDENTIFIERS, + OP_MONITOR_HIGH_POWER_LOCATION, + OP_MONITOR_LOCATION + }; + + // These are additional ops, which are not backed by runtime permissions, but are recorded. + static final int[] ADDITIONAL_DISCRETE_OPS = new int[] { + OP_PHONE_CALL_MICROPHONE, + OP_RECEIVE_AMBIENT_TRIGGER_AUDIO, + OP_RECEIVE_SANDBOX_TRIGGER_AUDIO, + OP_PHONE_CALL_CAMERA, + OP_EMERGENCY_LOCATION, + OP_RESERVED_FOR_TESTING + }; + + // Legacy ops captured in discrete database. + private static final String LEGACY_OPS = OP_FINE_LOCATION + "," + OP_COARSE_LOCATION + "," + OP_EMERGENCY_LOCATION + "," + OP_CAMERA + "," + OP_RECORD_AUDIO + "," + OP_PHONE_CALL_MICROPHONE + "," + OP_PHONE_CALL_CAMERA + "," + OP_RECEIVE_AMBIENT_TRIGGER_AUDIO + "," + OP_RECEIVE_SANDBOX_TRIGGER_AUDIO + "," + OP_READ_HEART_RATE + "," + OP_READ_OXYGEN_SATURATION + "," + OP_READ_SKIN_TEMPERATURE + "," + OP_RESERVED_FOR_TESTING; - static final int[] sDiscreteOpsToLog = - new int[]{OP_FINE_LOCATION, OP_COARSE_LOCATION, OP_EMERGENCY_LOCATION, OP_CAMERA, - OP_RECORD_AUDIO, OP_PHONE_CALL_MICROPHONE, OP_PHONE_CALL_CAMERA, - OP_RECEIVE_AMBIENT_TRIGGER_AUDIO, OP_RECEIVE_SANDBOX_TRIGGER_AUDIO, OP_READ_SMS, - OP_WRITE_SMS, OP_SEND_SMS, OP_READ_ICC_SMS, OP_WRITE_ICC_SMS, - OP_SMS_FINANCIAL_TRANSACTIONS, OP_SYSTEM_ALERT_WINDOW, OP_MONITOR_LOCATION, - OP_MONITOR_HIGH_POWER_LOCATION, OP_PROCESS_OUTGOING_CALLS, - }; static final long DEFAULT_DISCRETE_HISTORY_CUTOFF = Duration.ofDays(7).toMillis(); static final long MAXIMUM_DISCRETE_HISTORY_CUTOFF = Duration.ofDays(30).toMillis(); @@ -126,7 +136,7 @@ abstract class DiscreteOpsRegistry { // in case of duplicate op events. static long sDiscreteHistoryQuantization; - static int[] sDiscreteOps; + static int[] sDiscreteOps = new int[0]; static int sDiscreteFlags; static final int OP_FLAGS_DISCRETE = OP_FLAG_SELF | OP_FLAG_TRUSTED_PROXIED @@ -134,27 +144,6 @@ abstract class DiscreteOpsRegistry { boolean mDebugMode = false; - static final int ACCESS_TYPE_NOTE_OP = - FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__NOTE_OP; - static final int ACCESS_TYPE_START_OP = - FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__START_OP; - static final int ACCESS_TYPE_FINISH_OP = - FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__FINISH_OP; - static final int ACCESS_TYPE_PAUSE_OP = - FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__PAUSE_OP; - static final int ACCESS_TYPE_RESUME_OP = - FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__RESUME_OP; - - @Retention(RetentionPolicy.SOURCE) - @IntDef(prefix = {"ACCESS_TYPE_"}, value = { - ACCESS_TYPE_NOTE_OP, - ACCESS_TYPE_START_OP, - ACCESS_TYPE_FINISH_OP, - ACCESS_TYPE_PAUSE_OP, - ACCESS_TYPE_RESUME_OP - }) - @interface AccessType {} - void systemReady() { DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_PRIVACY, AsyncTask.THREAD_POOL_EXECUTOR, (DeviceConfig.Properties p) -> { @@ -166,8 +155,7 @@ abstract class DiscreteOpsRegistry { abstract void recordDiscreteAccess(int uid, String packageName, @NonNull String deviceId, int op, @Nullable String attributionTag, @AppOpsManager.OpFlags int flags, @AppOpsManager.UidState int uidState, long accessTime, long accessDuration, - @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId, - @DiscreteOpsRegistry.AccessType int accessType); + @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId); /** * A periodic callback from {@link AppOpsService} to flush the in memory events to disk. @@ -218,7 +206,7 @@ abstract class DiscreteOpsRegistry { } static boolean isDiscreteOp(int op, @AppOpsManager.OpFlags int flags) { - if (!ArrayUtils.contains(sDiscreteOps, op)) { + if (Arrays.binarySearch(sDiscreteOps, op) < 0) { return false; } if ((flags & (sDiscreteFlags)) == 0) { @@ -227,9 +215,6 @@ abstract class DiscreteOpsRegistry { return true; } - // could this be impl detail of discrete registry, just one test is using the method - // abstract DiscreteRegistry.DiscreteOps getAllDiscreteOps(); - private void setDiscreteHistoryParameters(DeviceConfig.Properties p) { if (p.getKeyset().contains(PROPERTY_DISCRETE_HISTORY_CUTOFF)) { sDiscreteHistoryCutoff = p.getLong(PROPERTY_DISCRETE_HISTORY_CUTOFF, @@ -251,11 +236,42 @@ abstract class DiscreteOpsRegistry { } else { sDiscreteHistoryQuantization = DEFAULT_DISCRETE_HISTORY_QUANTIZATION; } - sDiscreteFlags = p.getKeyset().contains(PROPERTY_DISCRETE_FLAGS) ? sDiscreteFlags = - p.getInt(PROPERTY_DISCRETE_FLAGS, OP_FLAGS_DISCRETE) : OP_FLAGS_DISCRETE; - sDiscreteOps = p.getKeyset().contains(PROPERTY_DISCRETE_OPS_LIST) ? parseOpsList( - p.getString(PROPERTY_DISCRETE_OPS_LIST, DEFAULT_DISCRETE_OPS)) : parseOpsList( - DEFAULT_DISCRETE_OPS); + sDiscreteFlags = p.getKeyset().contains(PROPERTY_DISCRETE_FLAGS) + ? p.getInt(PROPERTY_DISCRETE_FLAGS, OP_FLAGS_DISCRETE) : OP_FLAGS_DISCRETE; + String opsListConfig = p.getString(PROPERTY_DISCRETE_OPS_LIST, null); + sDiscreteOps = opsListConfig == null ? getDefaultOpsList() : parseOpsList(opsListConfig); + + Arrays.sort(sDiscreteOps); + } + + // App ops backed by runtime/dangerous permissions. + private static IntArray getRuntimePermissionOps() { + IntArray runtimeOps = new IntArray(); + for (int op = 0; op < AppOpsManager._NUM_OP; op++) { + if (AppOpsManager.opIsRuntimePermission(op)) { + runtimeOps.add(op); + } + } + return runtimeOps; + } + + /** + * @return an array of app ops captured into discrete database. + */ + private static int[] getDefaultOpsList() { + if (!(Flags.recordAllRuntimeAppopsSqlite() && Flags.enableSqliteAppopsAccesses())) { + return getDefaultLegacyOps(); + } + + IntArray discreteOpsArray = getRuntimePermissionOps(); + discreteOpsArray.addAll(IMPORTANT_OPS_FOR_SECURITY); + discreteOpsArray.addAll(ADDITIONAL_DISCRETE_OPS); + + return discreteOpsArray.toArray(); + } + + private static int[] getDefaultLegacyOps() { + return parseOpsList(LEGACY_OPS); } private static int[] parseOpsList(String opsList) { @@ -273,32 +289,8 @@ abstract class DiscreteOpsRegistry { } } catch (NumberFormatException e) { Slog.e(TAG, "Failed to parse Discrete ops list: " + e.getMessage()); - return parseOpsList(DEFAULT_DISCRETE_OPS); + return getDefaultOpsList(); } return result; } - - /** - * Whether app op access tacking is enabled and a metric event should be logged. - */ - static boolean shouldLogAccess(int op) { - return Flags.appopAccessTrackingLoggingEnabled() - && ArrayUtils.contains(sDiscreteOpsToLog, op); - } - - String getAttributionTag(String attributionTag, String packageName) { - if (attributionTag == null || packageName == null) { - return attributionTag; - } - int firstChar = 0; - if (attributionTag.startsWith(packageName)) { - firstChar = packageName.length(); - if (firstChar < attributionTag.length() && attributionTag.charAt(firstChar) - == '.') { - firstChar++; - } - } - return attributionTag.substring(firstChar); - } - } diff --git a/services/core/java/com/android/server/appop/DiscreteOpsSqlRegistry.java b/services/core/java/com/android/server/appop/DiscreteOpsSqlRegistry.java index dc11be9aadb6..0e1fbf3a6d1a 100644 --- a/services/core/java/com/android/server/appop/DiscreteOpsSqlRegistry.java +++ b/services/core/java/com/android/server/appop/DiscreteOpsSqlRegistry.java @@ -36,7 +36,6 @@ import android.util.IntArray; import android.util.LongSparseArray; import android.util.Slog; -import com.android.internal.util.FrameworkStatsLog; import com.android.server.ServiceThread; import java.io.File; @@ -97,15 +96,7 @@ public class DiscreteOpsSqlRegistry extends DiscreteOpsRegistry { void recordDiscreteAccess(int uid, String packageName, @NonNull String deviceId, int op, @Nullable String attributionTag, int flags, int uidState, - long accessTime, long accessDuration, int attributionFlags, int attributionChainId, - int accessType) { - if (shouldLogAccess(op)) { - FrameworkStatsLog.write(FrameworkStatsLog.APP_OP_ACCESS_TRACKED, uid, op, accessType, - uidState, flags, attributionFlags, - getAttributionTag(attributionTag, packageName), - attributionChainId); - } - + long accessTime, long accessDuration, int attributionFlags, int attributionChainId) { if (!isDiscreteOp(op, flags)) { return; } @@ -189,7 +180,7 @@ public class DiscreteOpsSqlRegistry extends DiscreteOpsRegistry { ChronoUnit.MILLIS).toEpochMilli()); List<DiscreteOp> discreteOps = mDiscreteOpsDbHelper.getDiscreteOps(filter, uidFilter, packageNameFilter, attributionTagFilter, opCodes, opFlagsFilter, beginTimeMillis, - endTimeMillis, -1, null); + endTimeMillis, -1, null, false); LongSparseArray<AttributionChain> attributionChains = null; if (assembleChains) { @@ -222,14 +213,15 @@ public class DiscreteOpsSqlRegistry extends DiscreteOpsRegistry { @AppOpsManager.HistoricalOpsRequestFilter int filter, int dumpOp, @NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix, int nDiscreteOps) { - writeAndClearOldAccessHistory(); + // flush the cache into database before dump. + mDiscreteOpsDbHelper.insertDiscreteOps(mDiscreteOpCache.getAllEventsAndClear()); IntArray opCodes = new IntArray(); if (dumpOp != AppOpsManager.OP_NONE) { opCodes.add(dumpOp); } List<DiscreteOp> discreteOps = mDiscreteOpsDbHelper.getDiscreteOps(filter, uidFilter, packageNameFilter, attributionTagFilter, opCodes, 0, -1, - -1, nDiscreteOps, DiscreteOpsTable.Columns.ACCESS_TIME); + -1, nDiscreteOps, DiscreteOpsTable.Columns.ACCESS_TIME, false); pw.print(prefix); pw.print("Largest chain id: "); diff --git a/services/core/java/com/android/server/appop/DiscreteOpsTestingShim.java b/services/core/java/com/android/server/appop/DiscreteOpsTestingShim.java index 1523cca86607..909a04c44ae5 100644 --- a/services/core/java/com/android/server/appop/DiscreteOpsTestingShim.java +++ b/services/core/java/com/android/server/appop/DiscreteOpsTestingShim.java @@ -48,15 +48,13 @@ class DiscreteOpsTestingShim extends DiscreteOpsRegistry { @Override void recordDiscreteAccess(int uid, String packageName, @NonNull String deviceId, int op, @Nullable String attributionTag, int flags, int uidState, long accessTime, - long accessDuration, int attributionFlags, int attributionChainId, int accessType) { + long accessDuration, int attributionFlags, int attributionChainId) { long start = SystemClock.uptimeMillis(); mXmlRegistry.recordDiscreteAccess(uid, packageName, deviceId, op, attributionTag, flags, - uidState, accessTime, accessDuration, attributionFlags, attributionChainId, - accessType); + uidState, accessTime, accessDuration, attributionFlags, attributionChainId); long start2 = SystemClock.uptimeMillis(); mSqlRegistry.recordDiscreteAccess(uid, packageName, deviceId, op, attributionTag, flags, - uidState, accessTime, accessDuration, attributionFlags, attributionChainId, - accessType); + uidState, accessTime, accessDuration, attributionFlags, attributionChainId); long end = SystemClock.uptimeMillis(); long xmlTimeTaken = start2 - start; long sqlTimeTaken = end - start2; diff --git a/services/core/java/com/android/server/appop/DiscreteOpsXmlRegistry.java b/services/core/java/com/android/server/appop/DiscreteOpsXmlRegistry.java index a6e3fc7cc66a..20706b659ffb 100644 --- a/services/core/java/com/android/server/appop/DiscreteOpsXmlRegistry.java +++ b/services/core/java/com/android/server/appop/DiscreteOpsXmlRegistry.java @@ -45,7 +45,6 @@ import android.util.Xml; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.ArrayUtils; -import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.XmlUtils; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; @@ -159,15 +158,7 @@ class DiscreteOpsXmlRegistry extends DiscreteOpsRegistry { void recordDiscreteAccess(int uid, String packageName, @NonNull String deviceId, int op, @Nullable String attributionTag, @AppOpsManager.OpFlags int flags, @AppOpsManager.UidState int uidState, long accessTime, long accessDuration, - @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId, - @AccessType int accessType) { - if (shouldLogAccess(op)) { - FrameworkStatsLog.write(FrameworkStatsLog.APP_OP_ACCESS_TRACKED, uid, op, accessType, - uidState, flags, attributionFlags, - getAttributionTag(attributionTag, packageName), - attributionChainId); - } - + @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId) { if (!isDiscreteOp(op, flags)) { return; } diff --git a/services/core/java/com/android/server/appop/HistoricalRegistry.java b/services/core/java/com/android/server/appop/HistoricalRegistry.java index d267e0d9e536..06e43e8ec68d 100644 --- a/services/core/java/com/android/server/appop/HistoricalRegistry.java +++ b/services/core/java/com/android/server/appop/HistoricalRegistry.java @@ -497,7 +497,7 @@ final class HistoricalRegistry { @NonNull String deviceId, @Nullable String attributionTag, @UidState int uidState, @OpFlags int flags, long accessTime, @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId, - @DiscreteOpsRegistry.AccessType int accessType, int accessCount) { + int accessCount) { synchronized (mInMemoryLock) { if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) { if (!isPersistenceInitializedMLocked()) { @@ -510,7 +510,7 @@ final class HistoricalRegistry { mDiscreteRegistry.recordDiscreteAccess(uid, packageName, deviceId, op, attributionTag, flags, uidState, accessTime, -1, attributionFlags, - attributionChainId, accessType); + attributionChainId); } } } @@ -533,8 +533,7 @@ final class HistoricalRegistry { void increaseOpAccessDuration(int op, int uid, @NonNull String packageName, @NonNull String deviceId, @Nullable String attributionTag, @UidState int uidState, @OpFlags int flags, long eventStartTime, long increment, - @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId, - @DiscreteOpsRegistry.AccessType int accessType) { + @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId) { synchronized (mInMemoryLock) { if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) { if (!isPersistenceInitializedMLocked()) { @@ -546,7 +545,7 @@ final class HistoricalRegistry { attributionTag, uidState, flags, increment); mDiscreteRegistry.recordDiscreteAccess(uid, packageName, deviceId, op, attributionTag, flags, uidState, eventStartTime, increment, - attributionFlags, attributionChainId, accessType); + attributionFlags, attributionChainId); } } } diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index ada1cd73f775..766456134b20 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -4997,6 +4997,8 @@ public class AudioService extends IAudioService.Stub pw.println("\tcom.android.media.audio.disablePrescaleAbsoluteVolume:" + disablePrescaleAbsoluteVolume()); pw.println("\tcom.android.media.audio.setStreamVolumeOrder - EOL"); + pw.println("\tandroid.media.audio.ringtoneUserUriCheck:" + + android.media.audio.Flags.ringtoneUserUriCheck()); pw.println("\tandroid.media.audio.roForegroundAudioControl:" + roForegroundAudioControl()); pw.println("\tandroid.media.audio.scoManagedByAudio:" diff --git a/services/core/java/com/android/server/display/DisplayControl.java b/services/core/java/com/android/server/display/DisplayControl.java index ddea285d3564..1d70953de6c0 100644 --- a/services/core/java/com/android/server/display/DisplayControl.java +++ b/services/core/java/com/android/server/display/DisplayControl.java @@ -29,7 +29,7 @@ import java.util.Objects; */ public class DisplayControl { private static native IBinder nativeCreateVirtualDisplay(String name, boolean secure, - String uniqueId, float requestedRefreshRate); + boolean optimizeForPower, String uniqueId, float requestedRefreshRate); private static native void nativeDestroyVirtualDisplay(IBinder displayToken); private static native void nativeOverrideHdrTypes(IBinder displayToken, int[] modes); private static native long[] nativeGetPhysicalDisplayIds(); @@ -49,7 +49,7 @@ public class DisplayControl { */ public static IBinder createVirtualDisplay(String name, boolean secure) { Objects.requireNonNull(name, "name must not be null"); - return nativeCreateVirtualDisplay(name, secure, "", 0.0f); + return nativeCreateVirtualDisplay(name, secure, true, "", 0.0f); } /** @@ -57,6 +57,10 @@ public class DisplayControl { * * @param name The name of the virtual display. * @param secure Whether this display is secure. + * @param optimizeForPower Whether SurfaceFlinger should optimize for power (instead of + * performance). Such displays will depend on another display for it to + * be shown and rendered, and that display will optimize for + * performance when it is on. * @param uniqueId The unique ID for the display. * @param requestedRefreshRate The requested refresh rate in frames per second. * For best results, specify a divisor of the physical refresh rate, e.g., 30 or 60 on @@ -66,10 +70,11 @@ public class DisplayControl { * @return The token reference for the display in SurfaceFlinger. */ public static IBinder createVirtualDisplay(String name, boolean secure, - String uniqueId, float requestedRefreshRate) { + boolean optimizeForPower, String uniqueId, float requestedRefreshRate) { Objects.requireNonNull(name, "name must not be null"); Objects.requireNonNull(uniqueId, "uniqueId must not be null"); - return nativeCreateVirtualDisplay(name, secure, uniqueId, requestedRefreshRate); + return nativeCreateVirtualDisplay(name, secure, optimizeForPower, uniqueId, + requestedRefreshRate); } /** diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 7016c11b69e7..a28069bbf050 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -674,9 +674,9 @@ public final class DisplayManagerService extends SystemService { mConfigParameterProvider = new DeviceConfigParameterProvider(DeviceConfigInterface.REAL); mExtraDisplayLoggingPackageName = DisplayProperties.debug_vri_package().orElse(null); mExtraDisplayEventLogging = !TextUtils.isEmpty(mExtraDisplayLoggingPackageName); - + // TODO(b/400384229): stats service needs to react to mirror-extended switch mExternalDisplayStatsService = new ExternalDisplayStatsService(mContext, mHandler, - this::isExtendedDisplayEnabled); + this::isExtendedDisplayAllowed); mDisplayNotificationManager = new DisplayNotificationManager(mFlags, mContext, mExternalDisplayStatsService); mExternalDisplayPolicy = new ExternalDisplayPolicy(new ExternalDisplayPolicyInjector()); @@ -690,7 +690,7 @@ public final class DisplayManagerService extends SystemService { deliverTopologyUpdate(update.first); }; mDisplayTopologyCoordinator = new DisplayTopologyCoordinator( - this::isExtendedDisplayEnabled, topologyChangedCallback, + this::isExtendedDisplayAllowed, topologyChangedCallback, new HandlerExecutor(mHandler), mSyncRoot, backupManager::dataChanged); } else { mDisplayTopologyCoordinator = null; @@ -2411,7 +2411,10 @@ public final class DisplayManagerService extends SystemService { updateLogicalDisplayState(display); } - private boolean isExtendedDisplayEnabled() { + private boolean isExtendedDisplayAllowed() { + if (mFlags.isDisplayContentModeManagementEnabled()) { + return true; + } try { return 0 != Settings.Global.getInt( mContext.getContentResolver(), @@ -6045,7 +6048,13 @@ public final class DisplayManagerService extends SystemService { return; } if (inTopology) { - mDisplayTopologyCoordinator.onDisplayAdded(getDisplayInfo(displayId)); + var info = getDisplayInfo(displayId); + if (info == null) { + Slog.w(TAG, "onDisplayBelongToTopologyChanged: cancelled displayId=" + + displayId + " info=null"); + return; + } + mDisplayTopologyCoordinator.onDisplayAdded(info); } else { mDisplayTopologyCoordinator.onDisplayRemoved(displayId); } diff --git a/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java b/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java index 997fff58b952..b4df1f76dccb 100644 --- a/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java +++ b/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java @@ -69,9 +69,9 @@ class DisplayTopologyCoordinator { private final SparseArray<String> mDisplayIdToUniqueIdMapping = new SparseArray<>(); /** - * Check if extended displays are enabled. If not, a topology is not needed. + * Check if extended displays are allowed. If not, a topology is not needed. */ - private final BooleanSupplier mIsExtendedDisplayEnabled; + private final BooleanSupplier mIsExtendedDisplayAllowed; /** * Callback used to send topology updates. @@ -83,21 +83,21 @@ class DisplayTopologyCoordinator { private final DisplayManagerService.SyncRoot mSyncRoot; private final Runnable mTopologySavedCallback; - DisplayTopologyCoordinator(BooleanSupplier isExtendedDisplayEnabled, + DisplayTopologyCoordinator(BooleanSupplier isExtendedDisplayAllowed, Consumer<Pair<DisplayTopology, DisplayTopologyGraph>> onTopologyChangedCallback, Executor topologyChangeExecutor, DisplayManagerService.SyncRoot syncRoot, Runnable topologySavedCallback) { - this(new Injector(), isExtendedDisplayEnabled, onTopologyChangedCallback, + this(new Injector(), isExtendedDisplayAllowed, onTopologyChangedCallback, topologyChangeExecutor, syncRoot, topologySavedCallback); } @VisibleForTesting - DisplayTopologyCoordinator(Injector injector, BooleanSupplier isExtendedDisplayEnabled, + DisplayTopologyCoordinator(Injector injector, BooleanSupplier isExtendedDisplayAllowed, Consumer<Pair<DisplayTopology, DisplayTopologyGraph>> onTopologyChangedCallback, Executor topologyChangeExecutor, DisplayManagerService.SyncRoot syncRoot, Runnable topologySavedCallback) { mTopology = injector.getTopology(); - mIsExtendedDisplayEnabled = isExtendedDisplayEnabled; + mIsExtendedDisplayAllowed = isExtendedDisplayAllowed; mOnTopologyChangedCallback = onTopologyChangedCallback; mTopologyChangeExecutor = topologyChangeExecutor; mSyncRoot = syncRoot; @@ -262,9 +262,9 @@ class DisplayTopologyCoordinator { return false; } if ((info.type == Display.TYPE_EXTERNAL || info.type == Display.TYPE_OVERLAY) - && !mIsExtendedDisplayEnabled.getAsBoolean()) { + && !mIsExtendedDisplayAllowed.getAsBoolean()) { Slog.d(TAG, "Display " + info.displayId + " not allowed in topology because " - + "type is EXTERNAL or OVERLAY and !mIsExtendedDisplayEnabled"); + + "type is EXTERNAL or OVERLAY and !mIsExtendedDisplayAllowed"); return false; } return true; diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java index ac03a93ca9e1..c2eac8605851 100644 --- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java +++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java @@ -103,10 +103,10 @@ public class VirtualDisplayAdapter extends DisplayAdapter { Context context, Handler handler, Listener listener, DisplayManagerFlags featureFlags) { this(syncRoot, context, handler, listener, new SurfaceControlDisplayFactory() { @Override - public IBinder createDisplay(String name, boolean secure, String uniqueId, - float requestedRefreshRate) { - return DisplayControl.createVirtualDisplay(name, secure, uniqueId, - requestedRefreshRate); + public IBinder createDisplay(String name, boolean secure, boolean optimizeForPower, + String uniqueId, float requestedRefreshRate) { + return DisplayControl.createVirtualDisplay(name, secure, optimizeForPower, uniqueId, + requestedRefreshRate); } @Override @@ -182,9 +182,13 @@ public class VirtualDisplayAdapter extends DisplayAdapter { String name = virtualDisplayConfig.getName(); boolean secure = (flags & VIRTUAL_DISPLAY_FLAG_SECURE) != 0; + boolean neverBlank = isNeverBlank(flags); - IBinder displayToken = mSurfaceControlDisplayFactory.createDisplay(name, secure, uniqueId, - virtualDisplayConfig.getRequestedRefreshRate()); + // Never-blank displays are considered to be dependent on another display to be rendered. + // As a result, such displays should optimize for power instead of performance when it is + // powered on. + IBinder displayToken = mSurfaceControlDisplayFactory.createDisplay(name, secure, neverBlank, + uniqueId, virtualDisplayConfig.getRequestedRefreshRate()); MediaProjectionCallback mediaProjectionCallback = null; if (projection != null) { mediaProjectionCallback = new MediaProjectionCallback(appToken); @@ -318,6 +322,12 @@ public class VirtualDisplayAdapter extends DisplayAdapter { return mVirtualDisplayDevices.remove(appToken); } + private static boolean isNeverBlank(int flags) { + // Private non-mirror displays are never blank and always on. + return (flags & VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) == 0 + && (flags & VIRTUAL_DISPLAY_FLAG_PUBLIC) == 0; + } + private final class VirtualDisplayDevice extends DisplayDevice implements DeathRecipient { private static final int PENDING_SURFACE_CHANGE = 0x01; private static final int PENDING_RESIZE = 0x02; @@ -377,9 +387,7 @@ public class VirtualDisplayAdapter extends DisplayAdapter { mCallback = callback; mProjection = projection; mMediaProjectionCallback = mediaProjectionCallback; - // Private non-mirror displays are never blank and always on. - mNeverBlank = (flags & VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) == 0 - && (flags & VIRTUAL_DISPLAY_FLAG_PUBLIC) == 0; + mNeverBlank = isNeverBlank(flags); if (android.companion.virtualdevice.flags.Flags.correctVirtualDisplayPowerState() && !mNeverBlank) { // The display's power state depends on the power state of the state of its @@ -782,6 +790,10 @@ public class VirtualDisplayAdapter extends DisplayAdapter { * * @param name The name of the display. * @param secure Whether this display is secure. + * @param optimizeForPower Whether SurfaceFlinger should optimize for power (instead of + * performance). Such displays will depend on another display for + * it to be shown and rendered, and that display will optimize for + * performance when it is on. * @param uniqueId The unique ID for the display. * @param requestedRefreshRate * The refresh rate, frames per second, to request on the virtual display. @@ -791,8 +803,8 @@ public class VirtualDisplayAdapter extends DisplayAdapter { * the refresh rate of the leader physical display. * @return The token reference for the display in SurfaceFlinger. */ - IBinder createDisplay(String name, boolean secure, String uniqueId, - float requestedRefreshRate); + IBinder createDisplay(String name, boolean secure, boolean optimizeForPower, + String uniqueId, float requestedRefreshRate); /** * Destroy a display in SurfaceFlinger. diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java index 97f9a7c4f2b0..8f5b831ca0b4 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java @@ -219,7 +219,9 @@ public class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { && reason != HdmiControlService.INITIATED_BY_BOOT_UP; List<HdmiCecMessage> bufferedActiveSource = mDelayedMessageBuffer .getBufferedMessagesWithOpcode(Constants.MESSAGE_ACTIVE_SOURCE); - if (bufferedActiveSource.isEmpty()) { + List<HdmiCecMessage> bufferedActiveSourceFromService = mService.getCecMessageWithOpcode( + Constants.MESSAGE_ACTIVE_SOURCE); + if (bufferedActiveSource.isEmpty() && bufferedActiveSourceFromService.isEmpty()) { addAndStartAction(new RequestActiveSourceAction(this, new IHdmiControlCallback.Stub() { @Override public void onComplete(int result) { diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 6d973ac8d1b5..fdd0ef2f90e1 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -1593,6 +1593,17 @@ public class HdmiControlService extends SystemService { this.mCecMessageBuffer = cecMessageBuffer; } + List<HdmiCecMessage> getCecMessageWithOpcode(int opcode) { + List<HdmiCecMessage> cecMessagesWithOpcode = new ArrayList<>(); + List<HdmiCecMessage> cecMessages = mCecMessageBuffer.getBuffer(); + for (HdmiCecMessage message: cecMessages) { + if (message.getOpcode() == opcode) { + cecMessagesWithOpcode.add(message); + } + } + return cecMessagesWithOpcode; + } + /** * Returns {@link Looper} of main thread. Use this {@link Looper} instance * for tasks that are running on main service thread. diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index c2fecf283a34..d9db178e0dc2 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -568,6 +568,7 @@ public class InputManagerService extends IInputManager.Stub } mWindowManagerCallbacks = callbacks; registerLidSwitchCallbackInternal(mWindowManagerCallbacks); + mKeyGestureController.setWindowManagerCallbacks(callbacks); } public void setWiredAccessoryCallbacks(WiredAccessoryCallbacks callbacks) { @@ -2756,24 +2757,6 @@ public class InputManagerService extends IInputManager.Stub @Nullable IBinder focussedToken) { return InputManagerService.this.handleKeyGestureEvent(event); } - - @Override - public boolean isKeyGestureSupported(int gestureType) { - switch (gestureType) { - case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_UP: - case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN: - case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE: - case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK: - case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_SLOW_KEYS: - case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_BOUNCE_KEYS: - case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MOUSE_KEYS: - case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_STICKY_KEYS: - return true; - default: - return false; - - } - } }); } @@ -3371,6 +3354,11 @@ public class InputManagerService extends IInputManager.Stub */ @Nullable SurfaceControl createSurfaceForGestureMonitor(String name, int displayId); + + /** + * Provide information on whether the keyguard is currently locked or not. + */ + boolean isKeyguardLocked(int displayId); } /** diff --git a/services/core/java/com/android/server/input/KeyGestureController.java b/services/core/java/com/android/server/input/KeyGestureController.java index ef5babf19d83..395c77322c04 100644 --- a/services/core/java/com/android/server/input/KeyGestureController.java +++ b/services/core/java/com/android/server/input/KeyGestureController.java @@ -62,8 +62,10 @@ import android.view.Display; import android.view.InputDevice; import android.view.KeyCharacterMap; import android.view.KeyEvent; +import android.view.ViewConfiguration; import com.android.internal.R; +import com.android.internal.accessibility.AccessibilityShortcutController; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.policy.IShortcutService; @@ -104,6 +106,7 @@ final class KeyGestureController { private static final int MSG_NOTIFY_KEY_GESTURE_EVENT = 1; private static final int MSG_PERSIST_CUSTOM_GESTURES = 2; private static final int MSG_LOAD_CUSTOM_GESTURES = 3; + private static final int MSG_ACCESSIBILITY_SHORTCUT = 4; // must match: config_settingsKeyBehavior in config.xml private static final int SETTINGS_KEY_BEHAVIOR_SETTINGS_ACTIVITY = 0; @@ -122,12 +125,15 @@ final class KeyGestureController { static final int POWER_VOLUME_UP_BEHAVIOR_GLOBAL_ACTIONS = 2; private final Context mContext; + private InputManagerService.WindowManagerCallbacks mWindowManagerCallbacks; private final Handler mHandler; private final Handler mIoHandler; private final int mSystemPid; private final KeyCombinationManager mKeyCombinationManager; private final SettingsObserver mSettingsObserver; private final AppLaunchShortcutManager mAppLaunchShortcutManager; + @VisibleForTesting + final AccessibilityShortcutController mAccessibilityShortcutController; private final InputGestureManager mInputGestureManager; private final DisplayManager mDisplayManager; @GuardedBy("mInputDataStore") @@ -175,8 +181,14 @@ final class KeyGestureController { private final boolean mVisibleBackgroundUsersEnabled = isVisibleBackgroundUsersEnabled(); - KeyGestureController(Context context, Looper looper, Looper ioLooper, + public KeyGestureController(Context context, Looper looper, Looper ioLooper, InputDataStore inputDataStore) { + this(context, looper, ioLooper, inputDataStore, new Injector()); + } + + @VisibleForTesting + KeyGestureController(Context context, Looper looper, Looper ioLooper, + InputDataStore inputDataStore, Injector injector) { mContext = context; mHandler = new Handler(looper, this::handleMessage); mIoHandler = new Handler(ioLooper, this::handleIoMessage); @@ -197,6 +209,8 @@ final class KeyGestureController { mSettingsObserver = new SettingsObserver(mHandler); mAppLaunchShortcutManager = new AppLaunchShortcutManager(mContext); mInputGestureManager = new InputGestureManager(mContext); + mAccessibilityShortcutController = injector.getAccessibilityShortcutController(mContext, + mHandler); mDisplayManager = Objects.requireNonNull(mContext.getSystemService(DisplayManager.class)); mInputDataStore = inputDataStore; mUserManagerInternal = LocalServices.getService(UserManagerInternal.class); @@ -295,8 +309,8 @@ final class KeyGestureController { KeyEvent.KEYCODE_VOLUME_UP) { @Override public boolean preCondition() { - return isKeyGestureSupported( - KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD); + return mAccessibilityShortcutController.isAccessibilityShortcutAvailable( + mWindowManagerCallbacks.isKeyguardLocked(DEFAULT_DISPLAY)); } @Override @@ -376,15 +390,15 @@ final class KeyGestureController { KeyEvent.KEYCODE_DPAD_DOWN) { @Override public boolean preCondition() { - return isKeyGestureSupported( - KeyGestureEvent.KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD); + return mAccessibilityShortcutController + .isAccessibilityShortcutAvailable(false); } @Override public void execute() { handleMultiKeyGesture( new int[]{KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_DPAD_DOWN}, - KeyGestureEvent.KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD, + KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD, KeyGestureEvent.ACTION_GESTURE_START, 0); } @@ -392,7 +406,7 @@ final class KeyGestureController { public void cancel() { handleMultiKeyGesture( new int[]{KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_DPAD_DOWN}, - KeyGestureEvent.KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD, + KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD, KeyGestureEvent.ACTION_GESTURE_COMPLETE, KeyGestureEvent.FLAG_CANCELLED); } @@ -438,6 +452,7 @@ final class KeyGestureController { mSettingsObserver.observe(); mAppLaunchShortcutManager.systemRunning(); mInputGestureManager.systemRunning(); + initKeyGestures(); int userId; synchronized (mUserLock) { @@ -447,6 +462,27 @@ final class KeyGestureController { mIoHandler.obtainMessage(MSG_LOAD_CUSTOM_GESTURES, userId).sendToTarget(); } + @SuppressLint("MissingPermission") + private void initKeyGestures() { + InputManager im = Objects.requireNonNull(mContext.getSystemService(InputManager.class)); + im.registerKeyGestureEventHandler((event, focusedToken) -> { + switch (event.getKeyGestureType()) { + case KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD: + if (event.getAction() == KeyGestureEvent.ACTION_GESTURE_START) { + mHandler.removeMessages(MSG_ACCESSIBILITY_SHORTCUT); + mHandler.sendMessageDelayed( + mHandler.obtainMessage(MSG_ACCESSIBILITY_SHORTCUT), + getAccessibilityShortcutTimeout()); + } else { + mHandler.removeMessages(MSG_ACCESSIBILITY_SHORTCUT); + } + return true; + default: + return false; + } + }); + } + public boolean interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) { if (mVisibleBackgroundUsersEnabled && shouldIgnoreKeyEventForVisibleBackgroundUser(event)) { return false; @@ -971,17 +1007,6 @@ final class KeyGestureController { return false; } - private boolean isKeyGestureSupported(@KeyGestureEvent.KeyGestureType int gestureType) { - synchronized (mKeyGestureHandlerRecords) { - for (KeyGestureHandlerRecord handler : mKeyGestureHandlerRecords.values()) { - if (handler.isKeyGestureSupported(gestureType)) { - return true; - } - } - } - return false; - } - public void notifyKeyGestureCompleted(int deviceId, int[] keycodes, int modifierState, @KeyGestureEvent.KeyGestureType int gestureType) { // TODO(b/358569822): Once we move the gesture detection logic to IMS, we ideally @@ -1019,9 +1044,16 @@ final class KeyGestureController { synchronized (mUserLock) { mCurrentUserId = userId; } + mAccessibilityShortcutController.setCurrentUser(userId); mIoHandler.obtainMessage(MSG_LOAD_CUSTOM_GESTURES, userId).sendToTarget(); } + + public void setWindowManagerCallbacks( + @NonNull InputManagerService.WindowManagerCallbacks callbacks) { + mWindowManagerCallbacks = callbacks; + } + private boolean isDefaultDisplayOn() { Display defaultDisplay = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY); if (defaultDisplay == null) { @@ -1068,6 +1100,9 @@ final class KeyGestureController { AidlKeyGestureEvent event = (AidlKeyGestureEvent) msg.obj; notifyKeyGestureEvent(event); break; + case MSG_ACCESSIBILITY_SHORTCUT: + mAccessibilityShortcutController.performAccessibilityShortcut(); + break; } return true; } @@ -1347,17 +1382,6 @@ final class KeyGestureController { } return false; } - - public boolean isKeyGestureSupported(@KeyGestureEvent.KeyGestureType int gestureType) { - try { - return mKeyGestureHandler.isKeyGestureSupported(gestureType); - } catch (RemoteException ex) { - Slog.w(TAG, "Failed to identify if key gesture type is supported by the " - + "process " + mPid + ", assuming it died.", ex); - binderDied(); - } - return false; - } } private class SettingsObserver extends ContentObserver { @@ -1413,6 +1437,25 @@ final class KeyGestureController { return event; } + private long getAccessibilityShortcutTimeout() { + synchronized (mUserLock) { + final ViewConfiguration config = ViewConfiguration.get(mContext); + final boolean hasDialogShown = Settings.Secure.getIntForUser( + mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0, mCurrentUserId) != 0; + final boolean skipTimeoutRestriction = + Settings.Secure.getIntForUser(mContext.getContentResolver(), + Settings.Secure.SKIP_ACCESSIBILITY_SHORTCUT_DIALOG_TIMEOUT_RESTRICTION, + 0, mCurrentUserId) != 0; + + // If users manually set the volume key shortcut for any accessibility service, the + // system would bypass the timeout restriction of the shortcut dialog. + return hasDialogShown || skipTimeoutRestriction + ? config.getAccessibilityShortcutKeyTimeoutAfterConfirmation() + : config.getAccessibilityShortcutKeyTimeout(); + } + } + public void dump(IndentingPrintWriter ipw) { ipw.println("KeyGestureController:"); ipw.increaseIndent(); @@ -1459,4 +1502,12 @@ final class KeyGestureController { mAppLaunchShortcutManager.dump(ipw); mInputGestureManager.dump(ipw); } + + @VisibleForTesting + static class Injector { + AccessibilityShortcutController getAccessibilityShortcutController(Context context, + Handler handler) { + return new AccessibilityShortcutController(context, handler, UserHandle.USER_SYSTEM); + } + } } diff --git a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java index 600cf7f06981..1a4ead22f658 100644 --- a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java +++ b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java @@ -22,6 +22,7 @@ import static com.android.internal.inputmethod.SoftInputShowHideReason.REMOVE_IM import static com.android.internal.inputmethod.SoftInputShowHideReason.SHOW_IME_SCREENSHOT_FROM_IMMS; import static com.android.server.EventLogTags.IMF_HIDE_IME; import static com.android.server.EventLogTags.IMF_SHOW_IME; +import static com.android.server.inputmethod.ImeProtoLogGroup.IME_VISIBILITY_APPLIER_DEBUG; import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME; import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME_EXPLICIT; import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME_NOT_ALWAYS; @@ -36,7 +37,6 @@ import android.annotation.UserIdInt; import android.os.IBinder; import android.os.ResultReceiver; import android.util.EventLog; -import android.util.Slog; import android.view.MotionEvent; import android.view.inputmethod.Flags; import android.view.inputmethod.ImeTracker; @@ -46,6 +46,7 @@ import android.view.inputmethod.InputMethodManager; import com.android.internal.annotations.GuardedBy; import com.android.internal.inputmethod.InputMethodDebug; import com.android.internal.inputmethod.SoftInputShowHideReason; +import com.android.internal.protolog.ProtoLog; import com.android.server.LocalServices; import com.android.server.wm.ImeTargetVisibilityPolicy; import com.android.server.wm.WindowManagerInternal; @@ -58,9 +59,7 @@ import java.util.Objects; */ final class DefaultImeVisibilityApplier { - private static final String TAG = "DefaultImeVisibilityApplier"; - - private static final boolean DEBUG = InputMethodManagerService.DEBUG; + static final String TAG = "DefaultImeVisibilityApplier"; private InputMethodManagerService mService; @@ -93,11 +92,10 @@ final class DefaultImeVisibilityApplier { final var bindingController = userData.mBindingController; final IInputMethodInvoker curMethod = bindingController.getCurMethod(); if (curMethod != null) { - if (DEBUG) { - Slog.v(TAG, "Calling " + curMethod + ".showSoftInput(" + showInputToken - + ", " + showFlags + ", " + resultReceiver + ") for reason: " - + InputMethodDebug.softInputDisplayReasonToString(reason)); - } + ProtoLog.v(IME_VISIBILITY_APPLIER_DEBUG, + "Calling %s.showSoftInput(%s, %s, %s) for reason: %s", curMethod, + showInputToken, showFlags, resultReceiver, + InputMethodDebug.softInputDisplayReasonToString(reason)); // TODO(b/192412909): Check if we can always call onShowHideSoftInputRequested() or not. if (curMethod.showSoftInput(showInputToken, statsToken, showFlags, resultReceiver)) { if (DEBUG_IME_VISIBILITY) { @@ -136,11 +134,9 @@ final class DefaultImeVisibilityApplier { // delivered to the IME process as an IPC. Hence the inconsistency between // IMMS#mInputShown and IMMS#mImeWindowVis should be resolved spontaneously in // the final state. - if (DEBUG) { - Slog.v(TAG, "Calling " + curMethod + ".hideSoftInput(0, " + hideInputToken - + ", " + resultReceiver + ") for reason: " - + InputMethodDebug.softInputDisplayReasonToString(reason)); - } + ProtoLog.v(IME_VISIBILITY_APPLIER_DEBUG, + "Calling %s.hideSoftInput(0, %s, %s) for reason: %s", curMethod, hideInputToken, + resultReceiver, InputMethodDebug.softInputDisplayReasonToString(reason)); // TODO(b/192412909): Check if we can always call onShowHideSoftInputRequested() or not. if (curMethod.hideSoftInput(hideInputToken, statsToken, 0, resultReceiver)) { if (DEBUG_IME_VISIBILITY) { diff --git a/services/core/java/com/android/server/inputmethod/ImeProtoLogGroup.java b/services/core/java/com/android/server/inputmethod/ImeProtoLogGroup.java index f9a56effc800..ea4e29564cc0 100644 --- a/services/core/java/com/android/server/inputmethod/ImeProtoLogGroup.java +++ b/services/core/java/com/android/server/inputmethod/ImeProtoLogGroup.java @@ -23,7 +23,11 @@ import java.util.UUID; public enum ImeProtoLogGroup implements IProtoLogGroup { // TODO(b/393561240): add info/warn/error log level and replace in IMMS IMMS_DEBUG(Consts.ENABLE_DEBUG, false, false, - InputMethodManagerService.TAG); + InputMethodManagerService.TAG), + IME_VISIBILITY_APPLIER_DEBUG(Consts.ENABLE_DEBUG, false, false, + DefaultImeVisibilityApplier.TAG), + IME_VIS_STATE_COMPUTER_DEBUG(Consts.ENABLE_DEBUG, false, false, + ImeVisibilityStateComputer.TAG); private final boolean mEnabled; private volatile boolean mLogToProto; diff --git a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java index 5fe8318dbb3f..69353becc692 100644 --- a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java +++ b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java @@ -32,6 +32,7 @@ import static android.view.WindowManager.LayoutParams.SoftInputModeFlags; import static com.android.internal.inputmethod.InputMethodDebug.softInputModeToString; import static com.android.internal.inputmethod.SoftInputShowHideReason.REMOVE_IME_SCREENSHOT_FROM_IMMS; import static com.android.internal.inputmethod.SoftInputShowHideReason.SHOW_IME_SCREENSHOT_FROM_IMMS; +import static com.android.server.inputmethod.ImeProtoLogGroup.IME_VIS_STATE_COMPUTER_DEBUG; import static com.android.server.inputmethod.InputMethodManagerService.computeImeDisplayIdForTarget; import android.accessibilityservice.AccessibilityService; @@ -58,6 +59,7 @@ import android.view.inputmethod.InputMethodManager; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.inputmethod.SoftInputShowHideReason; +import com.android.internal.protolog.ProtoLog; import com.android.server.LocalServices; import com.android.server.pm.UserManagerInternal; import com.android.server.wm.WindowManagerInternal; @@ -72,9 +74,7 @@ import java.util.WeakHashMap; */ public final class ImeVisibilityStateComputer { - private static final String TAG = "ImeVisibilityStateComputer"; - - private static final boolean DEBUG = InputMethodManagerService.DEBUG; + static final String TAG = "ImeVisibilityStateComputer"; @UserIdInt private final int mUserId; @@ -292,12 +292,14 @@ public final class ImeVisibilityStateComputer { @InputMethodManager.HideFlags int hideFlags) { if ((hideFlags & InputMethodManager.HIDE_IMPLICIT_ONLY) != 0 && (mRequestedShowExplicitly || mShowForced)) { - if (DEBUG) Slog.v(TAG, "Not hiding: explicit show not cancelled by non-explicit hide"); + ProtoLog.v(IME_VIS_STATE_COMPUTER_DEBUG, + "Not hiding: explicit show not cancelled by non-explicit hide"); ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_SERVER_HIDE_IMPLICIT); return false; } if (mShowForced && (hideFlags & InputMethodManager.HIDE_NOT_ALWAYS) != 0) { - if (DEBUG) Slog.v(TAG, "Not hiding: forced show not cancelled by not-always hide"); + ProtoLog.v(IME_VIS_STATE_COMPUTER_DEBUG, + "Not hiding: forced show not cancelled by not-always hide"); ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_SERVER_HIDE_NOT_ALWAYS); return false; } @@ -417,8 +419,8 @@ public final class ImeVisibilityStateComputer { @GuardedBy("ImfLock.class") private void setWindowStateInner(IBinder windowToken, @NonNull ImeTargetWindowState newState) { - if (DEBUG) Slog.d(TAG, "setWindowStateInner, windowToken=" + windowToken - + ", state=" + newState); + ProtoLog.v(IME_VIS_STATE_COMPUTER_DEBUG, "setWindowStateInner, windowToken=%s, state=%s", + windowToken, newState); mRequestWindowStateMap.put(windowToken, newState); } @@ -466,7 +468,7 @@ public final class ImeVisibilityStateComputer { // Because the app might leverage these flags to hide soft-keyboard with showing their own // UI for input. if (state.hasEditorFocused() && shouldRestoreImeVisibility(state)) { - if (DEBUG) Slog.v(TAG, "Will show input to restore visibility"); + ProtoLog.v(IME_VIS_STATE_COMPUTER_DEBUG, "Will show input to restore visibility"); // Inherit the last requested IME visible state when the target window is still // focused with an editor. state.setRequestedImeVisible(true); @@ -483,7 +485,8 @@ public final class ImeVisibilityStateComputer { // There is no focus view, and this window will // be behind any soft input window, so hide the // soft input window if it is shown. - if (DEBUG) Slog.v(TAG, "Unspecified window will hide input"); + ProtoLog.v(IME_VIS_STATE_COMPUTER_DEBUG, + "Unspecified window will hide input"); return new ImeVisibilityResult(STATE_HIDE_IME_NOT_ALWAYS, SoftInputShowHideReason.HIDE_UNSPECIFIED_WINDOW); } @@ -495,7 +498,7 @@ public final class ImeVisibilityStateComputer { // them good context without input information being obscured // by the IME) or if running on a large screen where there // is more room for the target window + IME. - if (DEBUG) Slog.v(TAG, "Unspecified window will show input"); + ProtoLog.v(IME_VIS_STATE_COMPUTER_DEBUG, "Unspecified window will show input"); return new ImeVisibilityResult(STATE_SHOW_IME_IMPLICIT, SoftInputShowHideReason.SHOW_AUTO_EDITOR_FORWARD_NAV); } @@ -513,7 +516,8 @@ public final class ImeVisibilityStateComputer { // the WindowState, as they're already in the correct state break; } else if (isForwardNavigation) { - if (DEBUG) Slog.v(TAG, "Window asks to hide input going forward"); + ProtoLog.v(IME_VIS_STATE_COMPUTER_DEBUG, + "Window asks to hide input going forward"); return new ImeVisibilityResult(STATE_HIDE_IME_EXPLICIT, SoftInputShowHideReason.HIDE_STATE_HIDDEN_FORWARD_NAV); } @@ -524,7 +528,7 @@ public final class ImeVisibilityStateComputer { // the WindowState, as they're already in the correct state break; } else if (state.hasImeFocusChanged()) { - if (DEBUG) Slog.v(TAG, "Window asks to hide input"); + ProtoLog.v(IME_VIS_STATE_COMPUTER_DEBUG, "Window asks to hide input"); return new ImeVisibilityResult(STATE_HIDE_IME_EXPLICIT, SoftInputShowHideReason.HIDE_ALWAYS_HIDDEN_STATE); } @@ -532,7 +536,8 @@ public final class ImeVisibilityStateComputer { case WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE: if (isForwardNavigation) { if (allowVisible) { - if (DEBUG) Slog.v(TAG, "Window asks to show input going forward"); + ProtoLog.v(IME_VIS_STATE_COMPUTER_DEBUG, + "Window asks to show input going forward"); return new ImeVisibilityResult(STATE_SHOW_IME_IMPLICIT, SoftInputShowHideReason.SHOW_STATE_VISIBLE_FORWARD_NAV); } else { @@ -543,7 +548,7 @@ public final class ImeVisibilityStateComputer { } break; case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE: - if (DEBUG) Slog.v(TAG, "Window asks to always show input"); + ProtoLog.v(IME_VIS_STATE_COMPUTER_DEBUG, "Window asks to always show input"); if (allowVisible) { if (state.hasImeFocusChanged()) { return new ImeVisibilityResult(STATE_SHOW_IME_IMPLICIT, @@ -565,7 +570,8 @@ public final class ImeVisibilityStateComputer { // To maintain compatibility, we are now hiding the IME when we don't have // an editor upon refocusing a window. if (state.isStartInputByGainFocus()) { - if (DEBUG) Slog.v(TAG, "Same window without editor will hide input"); + ProtoLog.v(IME_VIS_STATE_COMPUTER_DEBUG, + "Same window without editor will hide input"); return new ImeVisibilityResult(STATE_HIDE_IME_EXPLICIT, SoftInputShowHideReason.HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR); } @@ -579,7 +585,7 @@ public final class ImeVisibilityStateComputer { // 1) SOFT_INPUT_STATE_UNCHANGED state without an editor // 2) SOFT_INPUT_STATE_VISIBLE state without an editor // 3) SOFT_INPUT_STATE_ALWAYS_VISIBLE state without an editor - if (DEBUG) Slog.v(TAG, "Window without editor will hide input"); + ProtoLog.v(IME_VIS_STATE_COMPUTER_DEBUG, "Window without editor will hide input"); if (Flags.refactorInsetsController()) { state.setRequestedImeVisible(false); } diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index a0e543300ce7..42d0a5c4757a 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -3618,6 +3618,12 @@ public class LockSettingsService extends ILockSettings.Stub { return; } + UserInfo userInfo = mInjector.getUserManagerInternal().getUserInfo(userId); + if (userInfo != null && userInfo.isForTesting()) { + Slog.i(TAG, "Keeping escrow data for test-only user"); + return; + } + // Disable escrow token permanently on all other device/user types. Slogf.i(TAG, "Permanently disabling support for escrow tokens on user %d", userId); mSpManager.destroyEscrowData(userId); diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java index f137de1b3e1d..988924d9f498 100644 --- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java @@ -25,6 +25,7 @@ import static android.media.MediaRouter2.SCANNING_STATE_SCANNING_FULL; import static android.media.MediaRouter2.SCANNING_STATE_WHILE_INTERACTIVE; import static android.media.MediaRouter2Utils.getOriginalId; import static android.media.MediaRouter2Utils.getProviderId; + import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_CREATE_SESSION; import static com.android.server.media.MediaRouterStatsLog.MEDIA_ROUTER_EVENT_REPORTED__EVENT_TYPE__EVENT_TYPE_DESELECT_ROUTE; @@ -63,6 +64,7 @@ import android.media.MediaRouter2Manager; import android.media.RouteDiscoveryPreference; import android.media.RouteListingPreference; import android.media.RoutingSessionInfo; +import android.media.SuggestedDeviceInfo; import android.os.Binder; import android.os.Bundle; import android.os.Handler; @@ -76,18 +78,21 @@ import android.util.ArrayMap; import android.util.Log; import android.util.Slog; import android.util.SparseArray; + import com.android.internal.annotations.GuardedBy; import com.android.internal.util.function.pooled.PooledLambda; import com.android.media.flags.Flags; import com.android.server.LocalServices; import com.android.server.pm.UserManagerInternal; import com.android.server.statusbar.StatusBarManagerInternal; + import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -551,6 +556,36 @@ class MediaRouter2ServiceImpl { } } + public void setDeviceSuggestionsWithRouter2( + @NonNull IMediaRouter2 router, + @Nullable List<SuggestedDeviceInfo> suggestedDeviceInfo) { + Objects.requireNonNull(router, "router must not be null"); + + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + setDeviceSuggestionsWithRouter2Locked(router, suggestedDeviceInfo); + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Nullable + public Map<String, List<SuggestedDeviceInfo>> getDeviceSuggestionsWithRouter2( + @NonNull IMediaRouter2 router) { + Objects.requireNonNull(router, "router must not be null"); + + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + return getDeviceSuggestionsWithRouter2Locked(router); + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + // End of methods that implement MediaRouter2 operations. // Start of methods that implement MediaRouter2Manager operations. @@ -805,6 +840,36 @@ class MediaRouter2ServiceImpl { } } + public void setDeviceSuggestionsWithManager( + @NonNull IMediaRouter2Manager manager, + @Nullable List<SuggestedDeviceInfo> suggestedDeviceInfo) { + Objects.requireNonNull(manager, "manager must not be null"); + + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + setDeviceSuggestionsWithManagerLocked(manager, suggestedDeviceInfo); + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Nullable + public Map<String, List<SuggestedDeviceInfo>> getDeviceSuggestionsWithManager( + @NonNull IMediaRouter2Manager manager) { + Objects.requireNonNull(manager, "manager must not be null"); + + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + return getDeviceSuggestionsWithManagerLocked(manager); + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + @RequiresPermission(Manifest.permission.PACKAGE_USAGE_STATS) public boolean showMediaOutputSwitcherWithProxyRouter( @NonNull IMediaRouter2Manager proxyRouter) { @@ -1582,6 +1647,61 @@ class MediaRouter2ServiceImpl { DUMMY_REQUEST_ID, routerRecord, uniqueSessionId)); } + @GuardedBy("mLock") + private void setDeviceSuggestionsWithRouter2Locked( + @NonNull IMediaRouter2 router, + @Nullable List<SuggestedDeviceInfo> suggestedDeviceInfo) { + final IBinder binder = router.asBinder(); + final RouterRecord routerRecord = mAllRouterRecords.get(binder); + + if (routerRecord == null) { + Slog.w( + TAG, + TextUtils.formatSimple( + "Ignoring set device suggestion for unknown router: %s", router)); + return; + } + + Slog.i( + TAG, + TextUtils.formatSimple( + "setDeviceSuggestions | router: %d suggestion: %d", + routerRecord.mPackageName, suggestedDeviceInfo)); + + routerRecord.mUserRecord.updateDeviceSuggestionsLocked( + routerRecord.mPackageName, routerRecord.mPackageName, suggestedDeviceInfo); + routerRecord.mUserRecord.mHandler.sendMessage( + obtainMessage( + UserHandler::notifyDeviceSuggestionsUpdatedOnHandler, + routerRecord.mUserRecord.mHandler, + routerRecord.mPackageName, + routerRecord.mPackageName, + suggestedDeviceInfo)); + } + + @GuardedBy("mLock") + @Nullable + private Map<String, List<SuggestedDeviceInfo>> getDeviceSuggestionsWithRouter2Locked( + @NonNull IMediaRouter2 router) { + final IBinder binder = router.asBinder(); + final RouterRecord routerRecord = mAllRouterRecords.get(binder); + + if (routerRecord == null) { + Slog.w( + TAG, + TextUtils.formatSimple( + "Attempted to get device suggestion for unknown router: %s", router)); + return null; + } + + Slog.i( + TAG, + TextUtils.formatSimple( + "getDeviceSuggestions | router: %d", routerRecord.mPackageName)); + + return routerRecord.mUserRecord.getDeviceSuggestionsLocked(routerRecord.mPackageName); + } + // End of locked methods that are used by MediaRouter2. // Start of locked methods that are used by MediaRouter2Manager. @@ -1972,6 +2092,68 @@ class MediaRouter2ServiceImpl { uniqueRequestId, routerRecord, uniqueSessionId)); } + @GuardedBy("mLock") + private void setDeviceSuggestionsWithManagerLocked( + @NonNull IMediaRouter2Manager manager, + @Nullable List<SuggestedDeviceInfo> suggestedDeviceInfo) { + final IBinder binder = manager.asBinder(); + ManagerRecord managerRecord = mAllManagerRecords.get(binder); + + if (managerRecord == null || managerRecord.mTargetPackageName == null) { + Slog.w( + TAG, + TextUtils.formatSimple( + "Ignoring set device suggestion for unknown manager: %s", manager)); + return; + } + + Slog.i( + TAG, + TextUtils.formatSimple( + "setDeviceSuggestions | manager: %d, suggestingPackageName: %d suggestion:" + + " %d", + managerRecord.mManagerId, + managerRecord.mOwnerPackageName, + suggestedDeviceInfo)); + + managerRecord.mUserRecord.updateDeviceSuggestionsLocked( + managerRecord.mTargetPackageName, + managerRecord.mOwnerPackageName, + suggestedDeviceInfo); + managerRecord.mUserRecord.mHandler.sendMessage( + obtainMessage( + UserHandler::notifyDeviceSuggestionsUpdatedOnHandler, + managerRecord.mUserRecord.mHandler, + managerRecord.mTargetPackageName, + managerRecord.mOwnerPackageName, + suggestedDeviceInfo)); + } + + @GuardedBy("mLock") + @Nullable + private Map<String, List<SuggestedDeviceInfo>> getDeviceSuggestionsWithManagerLocked( + @NonNull IMediaRouter2Manager manager) { + final IBinder binder = manager.asBinder(); + ManagerRecord managerRecord = mAllManagerRecords.get(binder); + + if (managerRecord == null || managerRecord.mTargetPackageName == null) { + Slog.w( + TAG, + TextUtils.formatSimple( + "Attempted to get device suggestion for unknown manager: %s", manager)); + return null; + } + + Slog.i( + TAG, + TextUtils.formatSimple( + "getDeviceSuggestionsWithManagerLocked | manager: %d", + managerRecord.mManagerId)); + + return managerRecord.mUserRecord.getDeviceSuggestionsLocked( + managerRecord.mTargetPackageName); + } + // End of locked methods that are used by MediaRouter2Manager. // Start of locked methods that are used by both MediaRouter2 and MediaRouter2Manager. @@ -2047,6 +2229,11 @@ class MediaRouter2ServiceImpl { //TODO: make records private for thread-safety final ArrayList<RouterRecord> mRouterRecords = new ArrayList<>(); final ArrayList<ManagerRecord> mManagerRecords = new ArrayList<>(); + + // @GuardedBy("mLock") + private final Map<String, Map<String, List<SuggestedDeviceInfo>>> mDeviceSuggestions = + new HashMap<>(); + RouteDiscoveryPreference mCompositeDiscoveryPreference = RouteDiscoveryPreference.EMPTY; Set<String> mActivelyScanningPackages = Set.of(); final UserHandler mHandler; @@ -2076,6 +2263,25 @@ class MediaRouter2ServiceImpl { return null; } + // @GuardedBy("mLock") + public void updateDeviceSuggestionsLocked( + String packageName, + String suggestingPackageName, + List<SuggestedDeviceInfo> deviceSuggestions) { + mDeviceSuggestions.putIfAbsent( + packageName, new HashMap<String, List<SuggestedDeviceInfo>>()); + Map<String, List<SuggestedDeviceInfo>> suggestions = + mDeviceSuggestions.get(packageName); + suggestions.put(suggestingPackageName, deviceSuggestions); + } + + // @GuardedBy("mLock") + @Nullable + public Map<String, List<SuggestedDeviceInfo>> getDeviceSuggestionsLocked( + String packageName) { + return mDeviceSuggestions.get(packageName); + } + public void dump(@NonNull PrintWriter pw, @NonNull String prefix) { pw.println(prefix + "UserRecord"); @@ -2314,6 +2520,15 @@ class MediaRouter2ServiceImpl { } } + public void notifyDeviceSuggestionsUpdated( + String suggestingPackageName, List<SuggestedDeviceInfo> suggestedDeviceInfo) { + try { + mRouter.notifyDeviceSuggestionsUpdated(suggestingPackageName, suggestedDeviceInfo); + } catch (RemoteException ex) { + logRemoteException("notifyDeviceSuggestionsUpdated", ex); + } + } + /** * Sends the corresponding router a {@link RoutingSessionInfo session} creation request, * with the given {@link MediaRoute2Info} as the initial member. @@ -3556,6 +3771,41 @@ class MediaRouter2ServiceImpl { // need to update routers other than the one making the update. } + private void notifyDeviceSuggestionsUpdatedOnHandler( + String routerPackageName, + String suggestingPackageName, + @Nullable List<SuggestedDeviceInfo> suggestedDeviceInfo) { + MediaRouter2ServiceImpl service = mServiceRef.get(); + if (service == null) { + return; + } + List<IMediaRouter2Manager> managers = new ArrayList<>(); + synchronized (service.mLock) { + for (ManagerRecord managerRecord : mUserRecord.mManagerRecords) { + if (TextUtils.equals(managerRecord.mTargetPackageName, routerPackageName)) { + managers.add(managerRecord.mManager); + } + } + for (IMediaRouter2Manager manager : managers) { + try { + manager.notifyDeviceSuggestionsUpdated( + routerPackageName, suggestingPackageName, suggestedDeviceInfo); + } catch (RemoteException ex) { + Slog.w( + TAG, + "Failed to notify suggesteion changed. Manager probably died.", + ex); + } + } + for (RouterRecord routerRecord : mUserRecord.mRouterRecords) { + if (TextUtils.equals(routerRecord.mPackageName, routerPackageName)) { + routerRecord.notifyDeviceSuggestionsUpdated( + suggestingPackageName, suggestedDeviceInfo); + } + } + } + } + private void updateDiscoveryPreferenceOnHandler() { MediaRouter2ServiceImpl service = mServiceRef.get(); if (service == null) { diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java index 35bb19943a24..11f449e790a8 100644 --- a/services/core/java/com/android/server/media/MediaRouterService.java +++ b/services/core/java/com/android/server/media/MediaRouterService.java @@ -49,6 +49,7 @@ import android.media.RemoteDisplayState.RemoteDisplayInfo; import android.media.RouteDiscoveryPreference; import android.media.RouteListingPreference; import android.media.RoutingSessionInfo; +import android.media.SuggestedDeviceInfo; import android.os.Binder; import android.os.Bundle; import android.os.Handler; @@ -80,6 +81,7 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Objects; /** @@ -526,6 +528,21 @@ public final class MediaRouterService extends IMediaRouterService.Stub // Binder call @Override + public void setDeviceSuggestionsWithRouter2( + IMediaRouter2 router, @Nullable List<SuggestedDeviceInfo> suggestedDeviceInfo) { + mService2.setDeviceSuggestionsWithRouter2(router, suggestedDeviceInfo); + } + + // Binder call + @Override + @Nullable + public Map<String, List<SuggestedDeviceInfo>> getDeviceSuggestionsWithRouter2( + IMediaRouter2 router) { + return mService2.getDeviceSuggestionsWithRouter2(router); + } + + // Binder call + @Override public List<RoutingSessionInfo> getRemoteSessions(IMediaRouter2Manager manager) { return mService2.getRemoteSessions(manager); } @@ -666,6 +683,22 @@ public final class MediaRouterService extends IMediaRouterService.Stub return mService2.showMediaOutputSwitcherWithProxyRouter(proxyRouter); } + // Binder call + @Override + public void setDeviceSuggestionsWithManager( + @NonNull IMediaRouter2Manager manager, + @Nullable List<SuggestedDeviceInfo> suggestedDeviceInfo) { + mService2.setDeviceSuggestionsWithManager(manager, suggestedDeviceInfo); + } + + // Binder call + @Override + @Nullable + public Map<String, List<SuggestedDeviceInfo>> getDeviceSuggestionsWithManager( + IMediaRouter2Manager manager) { + return mService2.getDeviceSuggestionsWithManager(manager); + } + void restoreBluetoothA2dp() { try { boolean a2dpOn; diff --git a/services/core/java/com/android/server/media/quality/MediaQualityService.java b/services/core/java/com/android/server/media/quality/MediaQualityService.java index 91a2843ccaf7..ad108f64ffe3 100644 --- a/services/core/java/com/android/server/media/quality/MediaQualityService.java +++ b/services/core/java/com/android/server/media/quality/MediaQualityService.java @@ -28,6 +28,7 @@ import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; +import android.hardware.audio.effect.DefaultExtension; import android.hardware.tv.mediaquality.AmbientBacklightColorFormat; import android.hardware.tv.mediaquality.IMediaQuality; import android.hardware.tv.mediaquality.IPictureProfileAdjustmentListener; @@ -48,7 +49,6 @@ import android.media.quality.IMediaQualityManager; import android.media.quality.IPictureProfileCallback; import android.media.quality.ISoundProfileCallback; import android.media.quality.MediaQualityContract.BaseParameters; -import android.media.quality.MediaQualityManager; import android.media.quality.ParameterCapability; import android.media.quality.PictureProfile; import android.media.quality.PictureProfileHandle; @@ -58,6 +58,7 @@ import android.os.Binder; import android.os.Bundle; import android.os.Environment; import android.os.IBinder; +import android.os.Parcel; import android.os.PersistableBundle; import android.os.RemoteCallbackList; import android.os.RemoteException; @@ -187,7 +188,7 @@ public class MediaQualityService extends SystemService { @GuardedBy("mPictureProfileLock") @Override - public PictureProfile createPictureProfile(PictureProfile pp, UserHandle user) { + public PictureProfile createPictureProfile(PictureProfile pp, int userId) { if ((pp.getPackageName() != null && !pp.getPackageName().isEmpty() && !incomingPackageEqualsCallingUidPackage(pp.getPackageName())) && !hasGlobalPictureQualityServicePermission()) { @@ -221,7 +222,7 @@ public class MediaQualityService extends SystemService { @GuardedBy("mPictureProfileLock") @Override - public void updatePictureProfile(String id, PictureProfile pp, UserHandle user) { + public void updatePictureProfile(String id, PictureProfile pp, int userId) { Long dbId = mPictureProfileTempIdMap.getKey(id); if (!hasPermissionToUpdatePictureProfile(dbId, pp)) { mMqManagerNotifier.notifyOnPictureProfileError(id, @@ -249,7 +250,7 @@ public class MediaQualityService extends SystemService { @GuardedBy("mPictureProfileLock") @Override - public void removePictureProfile(String id, UserHandle user) { + public void removePictureProfile(String id, int userId) { synchronized (mPictureProfileLock) { Long dbId = mPictureProfileTempIdMap.getKey(id); @@ -290,10 +291,8 @@ public class MediaQualityService extends SystemService { @GuardedBy("mPictureProfileLock") @Override - public PictureProfile getPictureProfile(int type, String name, Bundle options, - UserHandle user) { - boolean includeParams = - options.getBoolean(MediaQualityManager.OPTION_INCLUDE_PARAMETERS, false); + public PictureProfile getPictureProfile(int type, String name, boolean includeParams, + int userId) { String selection = BaseParameters.PARAMETER_TYPE + " = ? AND " + BaseParameters.PARAMETER_NAME + " = ? AND " + BaseParameters.PARAMETER_PACKAGE + " = ?"; @@ -327,7 +326,7 @@ public class MediaQualityService extends SystemService { @GuardedBy("mPictureProfileLock") @Override public List<PictureProfile> getPictureProfilesByPackage( - String packageName, Bundle options, UserHandle user) { + String packageName, boolean includeParams, int userId) { if (!hasGlobalPictureQualityServicePermission()) { mMqManagerNotifier.notifyOnPictureProfileError(null, PictureProfile.ERROR_NO_PERMISSION, @@ -335,8 +334,6 @@ public class MediaQualityService extends SystemService { } synchronized (mPictureProfileLock) { - boolean includeParams = - options.getBoolean(MediaQualityManager.OPTION_INCLUDE_PARAMETERS, false); String selection = BaseParameters.PARAMETER_PACKAGE + " = ?"; String[] selectionArguments = {packageName}; return mMqDatabaseUtils.getPictureProfilesBasedOnConditions(MediaQualityUtils @@ -347,17 +344,17 @@ public class MediaQualityService extends SystemService { @GuardedBy("mPictureProfileLock") @Override - public List<PictureProfile> getAvailablePictureProfiles(Bundle options, UserHandle user) { + public List<PictureProfile> getAvailablePictureProfiles(boolean includeParams, int userId) { String packageName = getPackageOfCallingUid(); if (packageName != null) { - return getPictureProfilesByPackage(packageName, options, user); + return getPictureProfilesByPackage(packageName, includeParams, userId); } return new ArrayList<>(); } @GuardedBy("mPictureProfileLock") @Override - public boolean setDefaultPictureProfile(String profileId, UserHandle user) { + public boolean setDefaultPictureProfile(String profileId, int userId) { if (!hasGlobalPictureQualityServicePermission()) { mMqManagerNotifier.notifyOnPictureProfileError(profileId, PictureProfile.ERROR_NO_PERMISSION, @@ -370,13 +367,21 @@ public class MediaQualityService extends SystemService { try { if (mMediaQuality != null) { + PictureParameters pp = new PictureParameters(); PictureParameter[] pictureParameters = MediaQualityUtils .convertPersistableBundleToPictureParameterList(params); - PictureParameters pp = new PictureParameters(); + PersistableBundle vendorPictureParameters = params + .getPersistableBundle(BaseParameters.VENDOR_PARAMETERS); + Parcel parcel = Parcel.obtain(); + if (vendorPictureParameters != null) { + setVendorPictureParameters(pp, parcel, vendorPictureParameters); + } + pp.pictureParameters = pictureParameters; mMediaQuality.sendDefaultPictureParameters(pp); + parcel.recycle(); return true; } } catch (RemoteException e) { @@ -387,7 +392,7 @@ public class MediaQualityService extends SystemService { @GuardedBy("mPictureProfileLock") @Override - public List<String> getPictureProfilePackageNames(UserHandle user) { + public List<String> getPictureProfilePackageNames(int userId) { if (!hasGlobalPictureQualityServicePermission()) { mMqManagerNotifier.notifyOnPictureProfileError(null, PictureProfile.ERROR_NO_PERMISSION, @@ -406,7 +411,7 @@ public class MediaQualityService extends SystemService { @GuardedBy("mPictureProfileLock") @Override - public List<PictureProfileHandle> getPictureProfileHandle(String[] ids, UserHandle user) { + public List<PictureProfileHandle> getPictureProfileHandle(String[] ids, int userId) { List<PictureProfileHandle> toReturn = new ArrayList<>(); synchronized (mPictureProfileLock) { for (String id : ids) { @@ -423,13 +428,13 @@ public class MediaQualityService extends SystemService { @GuardedBy("mSoundProfileLock") @Override - public List<SoundProfileHandle> getSoundProfileHandle(String[] ids, UserHandle user) { + public List<SoundProfileHandle> getSoundProfileHandle(String[] ids, int userId) { List<SoundProfileHandle> toReturn = new ArrayList<>(); synchronized (mSoundProfileLock) { for (String id : ids) { Long key = mSoundProfileTempIdMap.getKey(id); if (key != null) { - toReturn.add(new SoundProfileHandle(key)); + toReturn.add(MediaQualityUtils.SOUND_PROFILE_HANDLE_NONE); } else { toReturn.add(null); } @@ -440,7 +445,7 @@ public class MediaQualityService extends SystemService { @GuardedBy("mSoundProfileLock") @Override - public SoundProfile createSoundProfile(SoundProfile sp, UserHandle user) { + public SoundProfile createSoundProfile(SoundProfile sp, int userId) { if ((sp.getPackageName() != null && !sp.getPackageName().isEmpty() && !incomingPackageEqualsCallingUidPackage(sp.getPackageName())) && !hasGlobalPictureQualityServicePermission()) { @@ -473,7 +478,7 @@ public class MediaQualityService extends SystemService { @GuardedBy("mSoundProfileLock") @Override - public void updateSoundProfile(String id, SoundProfile sp, UserHandle user) { + public void updateSoundProfile(String id, SoundProfile sp, int userId) { Long dbId = mSoundProfileTempIdMap.getKey(id); if (!hasPermissionToUpdateSoundProfile(dbId, sp)) { mMqManagerNotifier.notifyOnSoundProfileError(id, SoundProfile.ERROR_NO_PERMISSION, @@ -502,7 +507,7 @@ public class MediaQualityService extends SystemService { @GuardedBy("mSoundProfileLock") @Override - public void removeSoundProfile(String id, UserHandle user) { + public void removeSoundProfile(String id, int userId) { synchronized (mSoundProfileLock) { Long dbId = mSoundProfileTempIdMap.getKey(id); SoundProfile toDelete = mMqDatabaseUtils.getSoundProfile(dbId); @@ -542,10 +547,8 @@ public class MediaQualityService extends SystemService { @GuardedBy("mSoundProfileLock") @Override - public SoundProfile getSoundProfile(int type, String name, Bundle options, - UserHandle user) { - boolean includeParams = - options.getBoolean(MediaQualityManager.OPTION_INCLUDE_PARAMETERS, false); + public SoundProfile getSoundProfile(int type, String name, boolean includeParams, + int userId) { String selection = BaseParameters.PARAMETER_TYPE + " = ? AND " + BaseParameters.PARAMETER_NAME + " = ? AND " + BaseParameters.PARAMETER_PACKAGE + " = ?"; @@ -579,15 +582,13 @@ public class MediaQualityService extends SystemService { @GuardedBy("mSoundProfileLock") @Override public List<SoundProfile> getSoundProfilesByPackage( - String packageName, Bundle options, UserHandle user) { + String packageName, boolean includeParams, int userId) { if (!hasGlobalSoundQualityServicePermission()) { mMqManagerNotifier.notifyOnSoundProfileError(null, SoundProfile.ERROR_NO_PERMISSION, Binder.getCallingUid(), Binder.getCallingPid()); } synchronized (mSoundProfileLock) { - boolean includeParams = - options.getBoolean(MediaQualityManager.OPTION_INCLUDE_PARAMETERS, false); String selection = BaseParameters.PARAMETER_PACKAGE + " = ?"; String[] selectionArguments = {packageName}; return mMqDatabaseUtils.getSoundProfilesBasedOnConditions(MediaQualityUtils @@ -598,17 +599,17 @@ public class MediaQualityService extends SystemService { @GuardedBy("mSoundProfileLock") @Override - public List<SoundProfile> getAvailableSoundProfiles(Bundle options, UserHandle user) { + public List<SoundProfile> getAvailableSoundProfiles(boolean includeParams, int userId) { String packageName = getPackageOfCallingUid(); if (packageName != null) { - return getSoundProfilesByPackage(packageName, options, user); + return getSoundProfilesByPackage(packageName, includeParams, userId); } return new ArrayList<>(); } @GuardedBy("mSoundProfileLock") @Override - public boolean setDefaultSoundProfile(String profileId, UserHandle user) { + public boolean setDefaultSoundProfile(String profileId, int userId) { if (!hasGlobalSoundQualityServicePermission()) { mMqManagerNotifier.notifyOnSoundProfileError(profileId, SoundProfile.ERROR_NO_PERMISSION, @@ -638,7 +639,7 @@ public class MediaQualityService extends SystemService { @GuardedBy("mSoundProfileLock") @Override - public List<String> getSoundProfilePackageNames(UserHandle user) { + public List<String> getSoundProfilePackageNames(int userId) { if (!hasGlobalSoundQualityServicePermission()) { mMqManagerNotifier.notifyOnSoundProfileError(null, SoundProfile.ERROR_NO_PERMISSION, Binder.getCallingUid(), Binder.getCallingPid()); @@ -737,7 +738,7 @@ public class MediaQualityService extends SystemService { @GuardedBy("mAmbientBacklightLock") @Override public void setAmbientBacklightSettings( - AmbientBacklightSettings settings, UserHandle user) { + AmbientBacklightSettings settings, int userId) { if (DEBUG) { Slogf.d(TAG, "setAmbientBacklightSettings " + settings); } @@ -775,7 +776,7 @@ public class MediaQualityService extends SystemService { @GuardedBy("mAmbientBacklightLock") @Override - public void setAmbientBacklightEnabled(boolean enabled, UserHandle user) { + public void setAmbientBacklightEnabled(boolean enabled, int userId) { if (DEBUG) { Slogf.d(TAG, "setAmbientBacklightEnabled " + enabled); } @@ -795,7 +796,7 @@ public class MediaQualityService extends SystemService { @Override public List<ParameterCapability> getParameterCapabilities( - List<String> names, UserHandle user) { + List<String> names, int userId) { byte[] byteArray = MediaQualityUtils.convertParameterToByteArray(names); ParamCapability[] caps = new ParamCapability[byteArray.length]; try { @@ -828,7 +829,7 @@ public class MediaQualityService extends SystemService { @GuardedBy("mPictureProfileLock") @Override - public List<String> getPictureProfileAllowList(UserHandle user) { + public List<String> getPictureProfileAllowList(int userId) { if (!hasGlobalPictureQualityServicePermission()) { mMqManagerNotifier.notifyOnPictureProfileError(null, PictureProfile.ERROR_NO_PERMISSION, @@ -844,7 +845,7 @@ public class MediaQualityService extends SystemService { @GuardedBy("mPictureProfileLock") @Override - public void setPictureProfileAllowList(List<String> packages, UserHandle user) { + public void setPictureProfileAllowList(List<String> packages, int userId) { if (!hasGlobalPictureQualityServicePermission()) { mMqManagerNotifier.notifyOnPictureProfileError(null, PictureProfile.ERROR_NO_PERMISSION, @@ -857,7 +858,7 @@ public class MediaQualityService extends SystemService { @GuardedBy("mSoundProfileLock") @Override - public List<String> getSoundProfileAllowList(UserHandle user) { + public List<String> getSoundProfileAllowList(int userId) { if (!hasGlobalSoundQualityServicePermission()) { mMqManagerNotifier.notifyOnSoundProfileError(null, SoundProfile.ERROR_NO_PERMISSION, Binder.getCallingUid(), Binder.getCallingPid()); @@ -872,7 +873,7 @@ public class MediaQualityService extends SystemService { @GuardedBy("mSoundProfileLock") @Override - public void setSoundProfileAllowList(List<String> packages, UserHandle user) { + public void setSoundProfileAllowList(List<String> packages, int userId) { if (!hasGlobalSoundQualityServicePermission()) { mMqManagerNotifier.notifyOnSoundProfileError(null, SoundProfile.ERROR_NO_PERMISSION, Binder.getCallingUid(), Binder.getCallingPid()); @@ -883,13 +884,13 @@ public class MediaQualityService extends SystemService { } @Override - public boolean isSupported(UserHandle user) { + public boolean isSupported(int userId) { return false; } @GuardedBy("mPictureProfileLock") @Override - public void setAutoPictureQualityEnabled(boolean enabled, UserHandle user) { + public void setAutoPictureQualityEnabled(boolean enabled, int userId) { if (!hasGlobalPictureQualityServicePermission()) { mMqManagerNotifier.notifyOnPictureProfileError(null, PictureProfile.ERROR_NO_PERMISSION, @@ -910,7 +911,7 @@ public class MediaQualityService extends SystemService { @GuardedBy("mPictureProfileLock") @Override - public boolean isAutoPictureQualityEnabled(UserHandle user) { + public boolean isAutoPictureQualityEnabled(int userId) { synchronized (mPictureProfileLock) { try { if (mMediaQuality != null) { @@ -927,7 +928,7 @@ public class MediaQualityService extends SystemService { @GuardedBy("mPictureProfileLock") @Override - public void setSuperResolutionEnabled(boolean enabled, UserHandle user) { + public void setSuperResolutionEnabled(boolean enabled, int userId) { if (!hasGlobalPictureQualityServicePermission()) { mMqManagerNotifier.notifyOnPictureProfileError(null, PictureProfile.ERROR_NO_PERMISSION, @@ -948,7 +949,7 @@ public class MediaQualityService extends SystemService { @GuardedBy("mPictureProfileLock") @Override - public boolean isSuperResolutionEnabled(UserHandle user) { + public boolean isSuperResolutionEnabled(int userId) { synchronized (mPictureProfileLock) { try { if (mMediaQuality != null) { @@ -965,7 +966,7 @@ public class MediaQualityService extends SystemService { @GuardedBy("mSoundProfileLock") @Override - public void setAutoSoundQualityEnabled(boolean enabled, UserHandle user) { + public void setAutoSoundQualityEnabled(boolean enabled, int userId) { if (!hasGlobalSoundQualityServicePermission()) { mMqManagerNotifier.notifyOnSoundProfileError(null, SoundProfile.ERROR_NO_PERMISSION, Binder.getCallingUid(), Binder.getCallingPid()); @@ -986,7 +987,7 @@ public class MediaQualityService extends SystemService { @GuardedBy("mSoundProfileLock") @Override - public boolean isAutoSoundQualityEnabled(UserHandle user) { + public boolean isAutoSoundQualityEnabled(int userId) { synchronized (mSoundProfileLock) { try { if (mMediaQuality != null) { @@ -1003,7 +1004,7 @@ public class MediaQualityService extends SystemService { @GuardedBy("mAmbientBacklightLock") @Override - public boolean isAmbientBacklightEnabled(UserHandle user) { + public boolean isAmbientBacklightEnabled(int userId) { return false; } } @@ -1428,11 +1429,19 @@ public class MediaQualityService extends SystemService { MediaQualityUtils.convertPersistableBundleToPictureParameterList( params); + PersistableBundle vendorPictureParameters = params + .getPersistableBundle(BaseParameters.VENDOR_PARAMETERS); + Parcel parcel = Parcel.obtain(); + if (vendorPictureParameters != null) { + setVendorPictureParameters(pictureParameters, parcel, vendorPictureParameters); + } + android.hardware.tv.mediaquality.PictureProfile toReturn = new android.hardware.tv.mediaquality.PictureProfile(); toReturn.pictureProfileId = id; toReturn.parameters = pictureParameters; + parcel.recycle(); return toReturn; } @@ -1738,4 +1747,16 @@ public class MediaQualityService extends SystemService { return android.hardware.tv.mediaquality.IMediaQualityCallback.Stub.VERSION; } } + + private void setVendorPictureParameters( + PictureParameters pictureParameters, + Parcel parcel, + PersistableBundle vendorPictureParameters) { + vendorPictureParameters.writeToParcel(parcel, 0); + byte[] vendorBundleToByteArray = parcel.marshall(); + DefaultExtension defaultExtension = new DefaultExtension(); + defaultExtension.bytes = Arrays.copyOf( + vendorBundleToByteArray, vendorBundleToByteArray.length); + pictureParameters.vendorPictureParameters.setParcelable(defaultExtension); + } } diff --git a/services/core/java/com/android/server/media/quality/MediaQualityUtils.java b/services/core/java/com/android/server/media/quality/MediaQualityUtils.java index 88d3f1ff7c52..303c96750098 100644 --- a/services/core/java/com/android/server/media/quality/MediaQualityUtils.java +++ b/services/core/java/com/android/server/media/quality/MediaQualityUtils.java @@ -60,6 +60,11 @@ public final class MediaQualityUtils { private static final String TAG = "MediaQualityUtils"; public static final String SETTINGS = "settings"; + public static final SoundProfileHandle SOUND_PROFILE_HANDLE_NONE = new SoundProfileHandle(); + static { + SOUND_PROFILE_HANDLE_NONE.id = -10000; + } + /** * Convert PictureParameter List to PersistableBundle. */ @@ -1022,7 +1027,7 @@ public final class MediaQualityUtils { getInputId(cursor), getPackageName(cursor), jsonToPersistableBundle(getSettingsString(cursor)), - SoundProfileHandle.NONE + SOUND_PROFILE_HANDLE_NONE ); } diff --git a/services/core/java/com/android/server/notification/GroupHelper.java b/services/core/java/com/android/server/notification/GroupHelper.java index 6e5308e56aa8..3f4df1dcf4e9 100644 --- a/services/core/java/com/android/server/notification/GroupHelper.java +++ b/services/core/java/com/android/server/notification/GroupHelper.java @@ -1002,8 +1002,7 @@ public class GroupHelper { private FullyQualifiedGroupKey getSectionGroupKeyWithFallback(final NotificationRecord record) { final NotificationSectioner sectioner = getSection(record); if (sectioner != null) { - return new FullyQualifiedGroupKey(record.getUserId(), record.getSbn().getPackageName(), - sectioner); + return FullyQualifiedGroupKey.forRecord(record, sectioner); } else { return getPreviousValidSectionKey(record); } @@ -1105,6 +1104,49 @@ public class GroupHelper { } } + /** + * Called when a group summary is posted. If there are any ungrouped notifications that are + * in that group, remove them as they are no longer candidates for autogrouping. + * + * @param summaryRecord the NotificationRecord for the newly posted group summary + * @param notificationList the full notification list from NotificationManagerService + */ + @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_FORCE_GROUPING) + protected void onGroupSummaryAdded(final NotificationRecord summaryRecord, + final List<NotificationRecord> notificationList) { + String groupKey = summaryRecord.getSbn().getGroup(); + synchronized (mAggregatedNotifications) { + final NotificationSectioner sectioner = getSection(summaryRecord); + if (sectioner == null) { + Slog.w(TAG, "onGroupSummaryAdded " + summaryRecord + ": no valid section found"); + return; + } + + FullyQualifiedGroupKey aggregateGroupKey = FullyQualifiedGroupKey.forRecord( + summaryRecord, sectioner); + ArrayMap<String, NotificationAttributes> ungrouped = + mUngroupedAbuseNotifications.getOrDefault(aggregateGroupKey, + new ArrayMap<>()); + if (ungrouped.isEmpty()) { + // don't bother looking through the notification list if there are no pending + // ungrouped notifications in this section (likely to be the most common case) + return; + } + + // Look through full notification list for any notifications belonging to this group; + // remove from ungrouped map if needed, as the presence of the summary means they will + // now be grouped + for (NotificationRecord r : notificationList) { + if (!r.getNotification().isGroupSummary() + && groupKey.equals(r.getSbn().getGroup()) + && ungrouped.containsKey(r.getKey())) { + ungrouped.remove(r.getKey()); + } + } + mUngroupedAbuseNotifications.put(aggregateGroupKey, ungrouped); + } + } + private record NotificationMoveOp(NotificationRecord record, FullyQualifiedGroupKey oldGroup, FullyQualifiedGroupKey newGroup) { } @@ -1496,8 +1538,8 @@ public class GroupHelper { private boolean isNotificationAggregatedInSection(NotificationRecord record, NotificationSectioner sectioner) { - final FullyQualifiedGroupKey fullAggregateGroupKey = new FullyQualifiedGroupKey( - record.getUserId(), record.getSbn().getPackageName(), sectioner); + final FullyQualifiedGroupKey fullAggregateGroupKey = FullyQualifiedGroupKey.forRecord( + record, sectioner); return record.getGroupKey().equals(fullAggregateGroupKey.toString()); } @@ -1895,6 +1937,12 @@ public class GroupHelper { this(userId, pkg, AGGREGATE_GROUP_KEY + (sectioner != null ? sectioner.mName : "")); } + static FullyQualifiedGroupKey forRecord(NotificationRecord record, + @Nullable NotificationSectioner sectioner) { + return new FullyQualifiedGroupKey(record.getUserId(), record.getSbn().getPackageName(), + sectioner); + } + @Override public String toString() { return userId + "|" + pkg + "|" + "g:" + groupName; diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 0f1d28db8d82..7a544cf1c26c 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -1075,6 +1075,7 @@ public class NotificationManagerService extends SystemService { summary.getSbn().getNotification().getGroupAlertBehavior(); if (notificationForceGrouping()) { + summary.getNotification().flags |= Notification.FLAG_SILENT; if (!summary.getChannel().getId().equals(summaryAttr.channelId)) { NotificationChannel newChannel = mPreferencesHelper.getNotificationChannel(pkg, summary.getUid(), summaryAttr.channelId, false); @@ -7450,6 +7451,7 @@ public class NotificationManagerService extends SystemService { // Override group key early for forced grouped notifications r.setOverrideGroupKey(groupName); } + r.getNotification().flags |= Notification.FLAG_SILENT; } addAutoGroupAdjustment(r, groupName); @@ -10196,6 +10198,12 @@ public class NotificationManagerService extends SystemService { } if (isSummary) { mSummaryByGroupKey.put(group, r); + + if (notificationForceGrouping()) { + // If any formerly-ungrouped notifications will be grouped by this summary, update + // accordingly. + mGroupHelper.onGroupSummaryAdded(r, mNotificationList); + } } FlagChecker childrenFlagChecker = (flags) -> { diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java index ee29849465e2..737d943f084d 100644 --- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java +++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java @@ -308,7 +308,9 @@ final class OverlayManagerServiceImpl { Slog.d(TAG, "onPackageRemoved pkgName=" + pkgName + " userId=" + userId); } // Update the state of all overlays that target this package. - final Set<UserPackage> targets = updateOverlaysForTarget(pkgName, userId, 0 /* flags */); + Set<UserPackage> targets = Collections.emptySet(); + targets = CollectionUtils.addAll(targets, + updateOverlaysForTarget(pkgName, userId, 0 /* flags */)); // Remove all the overlays this package declares. return CollectionUtils.addAll(targets, diff --git a/services/core/java/com/android/server/os/instrumentation/OWNERS b/services/core/java/com/android/server/os/instrumentation/OWNERS new file mode 100644 index 000000000000..2522426d93f8 --- /dev/null +++ b/services/core/java/com/android/server/os/instrumentation/OWNERS @@ -0,0 +1 @@ +include platform/packages/modules/UprobeStats:/OWNERS
\ No newline at end of file diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java index 734920435e26..3361dbc2df07 100644 --- a/services/core/java/com/android/server/pm/InstallRequest.java +++ b/services/core/java/com/android/server/pm/InstallRequest.java @@ -644,20 +644,6 @@ final class InstallRequest { return mScanResult.mPkgSetting; } - @Nullable - public PackageSetting getRealPackageSetting() { - // TODO: Fix this to have 1 mutable PackageSetting for scan/install. If the previous - // setting needs to be passed to have a comparison, hide it behind an immutable - // interface. There's no good reason to have 3 different ways to access the real - // PackageSetting object, only one of which is actually correct. - PackageSetting realPkgSetting = isExistingSettingCopied() - ? getScanRequestPackageSetting() : getScannedPackageSetting(); - if (realPkgSetting == null) { - realPkgSetting = getScannedPackageSetting(); - } - return realPkgSetting; - } - public boolean isExistingSettingCopied() { assertScanResultExists(); return mScanResult.mExistingSettingCopied; diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 93837b34f7b3..092ec8ef4a8a 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -7490,6 +7490,7 @@ public class UserManagerService extends IUserManager.Stub { final long now = System.currentTimeMillis(); final long nowRealtime = SystemClock.elapsedRealtime(); final StringBuilder sb = new StringBuilder(); + final Resources resources = Resources.getSystem(); if (args != null && args.length > 0) { switch (args[0]) { @@ -7570,10 +7571,15 @@ public class UserManagerService extends IUserManager.Stub { // Dump some capabilities pw.println(); - pw.print(" Max users: " + UserManager.getMaxSupportedUsers()); + int effectiveMaxSupportedUsers = UserManager.getMaxSupportedUsers(); + pw.print(" Max users: " + effectiveMaxSupportedUsers); + int defaultMaxSupportedUsers = resources.getInteger(R.integer.config_multiuserMaximumUsers); + if (effectiveMaxSupportedUsers != defaultMaxSupportedUsers) { + pw.print(" (built-in value: " + defaultMaxSupportedUsers + ")"); + } pw.println(" (limit reached: " + isUserLimitReached() + ")"); pw.println(" Supports switchable users: " + UserManager.supportsMultipleUsers()); - pw.println(" All guests ephemeral: " + Resources.getSystem().getBoolean( + pw.println(" All guests ephemeral: " + resources.getBoolean( com.android.internal.R.bool.config_guestUserEphemeral)); pw.println(" Force ephemeral users: " + mForceEphemeralUsers); final boolean isHeadlessSystemUserMode = isHeadlessSystemUserMode(); @@ -7588,7 +7594,7 @@ public class UserManagerService extends IUserManager.Stub { } } if (isHeadlessSystemUserMode) { - pw.println(" Can switch to headless system user: " + Resources.getSystem() + pw.println(" Can switch to headless system user: " + resources .getBoolean(com.android.internal.R.bool.config_canSwitchToHeadlessSystemUser)); } pw.println(" User version: " + mUserVersion); diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java index 58c5b1c90a66..5798aa919d96 100644 --- a/services/core/java/com/android/server/pm/UserTypeFactory.java +++ b/services/core/java/com/android/server/pm/UserTypeFactory.java @@ -36,6 +36,7 @@ import static android.os.UserManager.USER_TYPE_PROFILE_CLONE; import static android.os.UserManager.USER_TYPE_PROFILE_COMMUNAL; import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED; import static android.os.UserManager.USER_TYPE_PROFILE_PRIVATE; +import static android.os.UserManager.USER_TYPE_PROFILE_SUPERVISING; import static android.os.UserManager.USER_TYPE_PROFILE_TEST; import static android.os.UserManager.USER_TYPE_SYSTEM_HEADLESS; @@ -111,6 +112,7 @@ public final class UserTypeFactory { builders.put(USER_TYPE_PROFILE_CLONE, getDefaultTypeProfileClone()); builders.put(USER_TYPE_PROFILE_COMMUNAL, getDefaultTypeProfileCommunal()); builders.put(USER_TYPE_PROFILE_PRIVATE, getDefaultTypeProfilePrivate()); + builders.put(USER_TYPE_PROFILE_SUPERVISING, getDefaultTypeProfileSupervising()); if (Build.IS_DEBUGGABLE) { builders.put(USER_TYPE_PROFILE_TEST, getDefaultTypeProfileTest()); } @@ -343,6 +345,29 @@ public final class UserTypeFactory { } /** + * Returns the Builder for the default {@link UserManager#USER_TYPE_PROFILE_SUPERVISING} + * configuration. + */ + private static UserTypeDetails.Builder getDefaultTypeProfileSupervising() { + return new UserTypeDetails.Builder() + .setName(USER_TYPE_PROFILE_SUPERVISING) + .setBaseType(FLAG_PROFILE) + .setMaxAllowed(1) + .setProfileParentRequired(false) + .setEnabled(android.multiuser.Flags.allowSupervisingProfile() ? 1 : 0) + .setLabels(R.string.profile_label_supervising) + .setDefaultRestrictions(getDefaultSupervisingProfileRestrictions()) + .setDefaultSecureSettings(getDefaultNonManagedProfileSecureSettings()) + .setDefaultUserProperties(new UserProperties.Builder() + .setStartWithParent(false) + .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_NO) + .setShowInSettings(UserProperties.SHOW_IN_SETTINGS_NO) + .setShowInQuietMode(UserProperties.SHOW_IN_QUIET_MODE_HIDDEN) + .setCredentialShareableWithParent(false) + .setAlwaysVisible(true)); + } + + /** * Returns the Builder for the default {@link UserManager#USER_TYPE_FULL_SECONDARY} * configuration. */ @@ -449,6 +474,12 @@ public final class UserTypeFactory { return restrictions; } + private static Bundle getDefaultSupervisingProfileRestrictions() { + final Bundle restrictions = getDefaultProfileRestrictions(); + restrictions.putBoolean(UserManager.DISALLOW_INSTALL_APPS, true); + return restrictions; + } + private static Bundle getDefaultManagedProfileSecureSettings() { // Only add String values to the bundle, settings are written as Strings eventually final Bundle settings = new Bundle(); diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 46dc75817a36..3230e891db55 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -4240,66 +4240,14 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (!useKeyGestureEventHandler()) { return; } - mInputManager.registerKeyGestureEventHandler(new InputManager.KeyGestureEventHandler() { - @Override - public boolean handleKeyGestureEvent(@NonNull KeyGestureEvent event, - @Nullable IBinder focusedToken) { - boolean handled = PhoneWindowManager.this.handleKeyGestureEvent(event, - focusedToken); - if (handled && !event.isCancelled() && Arrays.stream(event.getKeycodes()).anyMatch( - (keycode) -> keycode == KeyEvent.KEYCODE_POWER)) { - mPowerKeyHandled = true; - } - return handled; - } - - @Override - public boolean isKeyGestureSupported(int gestureType) { - switch (gestureType) { - case KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS: - case KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH: - case KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT: - case KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT: - case KeyGestureEvent.KEY_GESTURE_TYPE_HOME: - case KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS: - case KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN: - case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL: - case KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT: - case KeyGestureEvent.KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT: - case KeyGestureEvent.KEY_GESTURE_TYPE_BACK: - case KeyGestureEvent.KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION: - case KeyGestureEvent.KEY_GESTURE_TYPE_DESKTOP_MODE: - case KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT: - case KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT: - case KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER: - case KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_UP: - case KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_DOWN: - case KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER: - case KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS: - case KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS: - case KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SEARCH: - case KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH: - case KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT: - case KeyGestureEvent.KEY_GESTURE_TYPE_CLOSE_ALL_DIALOGS: - case KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION: - case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_DO_NOT_DISTURB: - case KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD: - case KeyGestureEvent.KEY_GESTURE_TYPE_RINGER_TOGGLE_CHORD: - case KeyGestureEvent.KEY_GESTURE_TYPE_GLOBAL_ACTIONS: - case KeyGestureEvent.KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT: - case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_TALKBACK: - case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS: - return true; - case KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD: - return mAccessibilityShortcutController.isAccessibilityShortcutAvailable( - isKeyguardLocked()); - case KeyGestureEvent.KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD: - return mAccessibilityShortcutController.isAccessibilityShortcutAvailable( - false); - default: - return false; - } + mInputManager.registerKeyGestureEventHandler((event, focusedToken) -> { + boolean handled = PhoneWindowManager.this.handleKeyGestureEvent(event, + focusedToken); + if (handled && !event.isCancelled() && Arrays.stream(event.getKeycodes()).anyMatch( + (keycode) -> keycode == KeyEvent.KEYCODE_POWER)) { + mPowerKeyHandled = true; } + return handled; }); } @@ -4457,13 +4405,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { cancelPendingScreenshotChordAction(); } return true; - case KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD: - if (start) { - interceptAccessibilityShortcutChord(); - } else { - cancelPendingAccessibilityShortcutAction(); - } - return true; case KeyGestureEvent.KEY_GESTURE_TYPE_RINGER_TOGGLE_CHORD: if (start) { interceptRingerToggleChord(); @@ -4481,14 +4422,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { cancelGlobalActionsAction(); } return true; - // TODO (b/358569822): Consolidate TV and non-TV gestures into same KeyGestureEvent - case KeyGestureEvent.KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD: - if (start) { - interceptAccessibilityGestureTv(); - } else { - cancelAccessibilityGestureTv(); - } - return true; case KeyGestureEvent.KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT: if (start) { interceptBugreportGestureTv(); diff --git a/services/core/java/com/android/server/power/ScreenUndimDetector.java b/services/core/java/com/android/server/power/ScreenUndimDetector.java index c4929c210e2c..b376417061db 100644 --- a/services/core/java/com/android/server/power/ScreenUndimDetector.java +++ b/services/core/java/com/android/server/power/ScreenUndimDetector.java @@ -30,11 +30,11 @@ import android.provider.DeviceConfig; import android.util.Slog; import android.view.Display; +import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.FrameworkStatsLog; import java.util.Set; -import java.util.concurrent.TimeUnit; /** * Detects when user manually undims the screen (x times) and acquires a wakelock to keep the screen @@ -48,7 +48,6 @@ public class ScreenUndimDetector { /** DeviceConfig flag: is keep screen on feature enabled. */ static final String KEY_KEEP_SCREEN_ON_ENABLED = "keep_screen_on_enabled"; - private static final boolean DEFAULT_KEEP_SCREEN_ON_ENABLED = true; private static final int OUTCOME_POWER_BUTTON = FrameworkStatsLog.TIMEOUT_AUTO_EXTENDED_REPORTED__OUTCOME__POWER_BUTTON; private static final int OUTCOME_TIMEOUT = @@ -58,15 +57,11 @@ public class ScreenUndimDetector { /** DeviceConfig flag: how long should we keep the screen on. */ @VisibleForTesting static final String KEY_KEEP_SCREEN_ON_FOR_MILLIS = "keep_screen_on_for_millis"; - @VisibleForTesting - static final long DEFAULT_KEEP_SCREEN_ON_FOR_MILLIS = TimeUnit.MINUTES.toMillis(10); private long mKeepScreenOnForMillis; /** DeviceConfig flag: how many user undims required to trigger keeping the screen on. */ @VisibleForTesting static final String KEY_UNDIMS_REQUIRED = "undims_required"; - @VisibleForTesting - static final int DEFAULT_UNDIMS_REQUIRED = 2; private int mUndimsRequired; /** @@ -76,8 +71,6 @@ public class ScreenUndimDetector { @VisibleForTesting static final String KEY_MAX_DURATION_BETWEEN_UNDIMS_MILLIS = "max_duration_between_undims_millis"; - @VisibleForTesting - static final long DEFAULT_MAX_DURATION_BETWEEN_UNDIMS_MILLIS = TimeUnit.MINUTES.toMillis(5); private long mMaxDurationBetweenUndimsMillis; @VisibleForTesting @@ -92,6 +85,7 @@ public class ScreenUndimDetector { private long mUndimOccurredTime = -1; private long mInteractionAfterUndimTime = -1; private InternalClock mClock; + private Context mContext; public ScreenUndimDetector() { mClock = new InternalClock(); @@ -109,12 +103,13 @@ public class ScreenUndimDetector { /** Should be called in parent's systemReady() */ public void systemReady(Context context) { + mContext = context; readValuesFromDeviceConfig(); DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_ATTENTION_MANAGER_SERVICE, - context.getMainExecutor(), + mContext.getMainExecutor(), (properties) -> onDeviceConfigChange(properties.getKeyset())); - final PowerManager powerManager = context.getSystemService(PowerManager.class); + final PowerManager powerManager = mContext.getSystemService(PowerManager.class); mWakeLock = powerManager.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, UNDIM_DETECTOR_WAKE_LOCK); @@ -203,36 +198,44 @@ public class ScreenUndimDetector { } } - private boolean readKeepScreenOnNotificationEnabled() { + private boolean readKeepScreenOnEnabled() { + boolean defaultKeepScreenOnEnabled = mContext.getResources().getBoolean( + R.bool.config_defaultPreventScreenTimeoutEnabled); return DeviceConfig.getBoolean(NAMESPACE_ATTENTION_MANAGER_SERVICE, KEY_KEEP_SCREEN_ON_ENABLED, - DEFAULT_KEEP_SCREEN_ON_ENABLED); + defaultKeepScreenOnEnabled); } private long readKeepScreenOnForMillis() { + long defaultKeepScreenOnDuration = mContext.getResources().getInteger( + R.integer.config_defaultPreventScreenTimeoutForMillis); return DeviceConfig.getLong(NAMESPACE_ATTENTION_MANAGER_SERVICE, KEY_KEEP_SCREEN_ON_FOR_MILLIS, - DEFAULT_KEEP_SCREEN_ON_FOR_MILLIS); + defaultKeepScreenOnDuration); } private int readUndimsRequired() { + int defaultUndimsRequired = mContext.getResources().getInteger( + R.integer.config_defaultUndimsRequired); int undimsRequired = DeviceConfig.getInt(NAMESPACE_ATTENTION_MANAGER_SERVICE, KEY_UNDIMS_REQUIRED, - DEFAULT_UNDIMS_REQUIRED); + defaultUndimsRequired); if (undimsRequired < 1 || undimsRequired > 5) { Slog.e(TAG, "Provided undimsRequired=" + undimsRequired - + " is not allowed [1, 5]; using the default=" + DEFAULT_UNDIMS_REQUIRED); - return DEFAULT_UNDIMS_REQUIRED; + + " is not allowed [1, 5]; using the default=" + defaultUndimsRequired); + return defaultUndimsRequired; } return undimsRequired; } private long readMaxDurationBetweenUndimsMillis() { + long defaultMaxDurationBetweenUndimsMillis = mContext.getResources().getInteger( + R.integer.config_defaultMaxDurationBetweenUndimsMillis); return DeviceConfig.getLong(NAMESPACE_ATTENTION_MANAGER_SERVICE, KEY_MAX_DURATION_BETWEEN_UNDIMS_MILLIS, - DEFAULT_MAX_DURATION_BETWEEN_UNDIMS_MILLIS); + defaultMaxDurationBetweenUndimsMillis); } private void onDeviceConfigChange(@NonNull Set<String> keys) { @@ -253,15 +256,16 @@ public class ScreenUndimDetector { @VisibleForTesting void readValuesFromDeviceConfig() { - mKeepScreenOnEnabled = readKeepScreenOnNotificationEnabled(); + mKeepScreenOnEnabled = readKeepScreenOnEnabled(); mKeepScreenOnForMillis = readKeepScreenOnForMillis(); mUndimsRequired = readUndimsRequired(); mMaxDurationBetweenUndimsMillis = readMaxDurationBetweenUndimsMillis(); Slog.i(TAG, "readValuesFromDeviceConfig():" + "\nmKeepScreenOnForMillis=" + mKeepScreenOnForMillis - + "\nmKeepScreenOnNotificationEnabled=" + mKeepScreenOnEnabled - + "\nmUndimsRequired=" + mUndimsRequired); + + "\nmKeepScreenOnEnabled=" + mKeepScreenOnEnabled + + "\nmUndimsRequired=" + mUndimsRequired + + "\nmMaxDurationBetweenUndimsMillis=" + mMaxDurationBetweenUndimsMillis); } diff --git a/services/core/java/com/android/server/power/stats/BatteryHistoryDirectory.java b/services/core/java/com/android/server/power/stats/BatteryHistoryDirectory.java index 5563f98e8842..7cd9bdbc662c 100644 --- a/services/core/java/com/android/server/power/stats/BatteryHistoryDirectory.java +++ b/services/core/java/com/android/server/power/stats/BatteryHistoryDirectory.java @@ -374,6 +374,10 @@ public class BatteryHistoryDirectory implements BatteryStatsHistory.BatteryHisto @SuppressWarnings("unchecked") @Override public List<BatteryHistoryFragment> getFragments() { + if (!mLock.isHeldByCurrentThread()) { + throw new IllegalStateException("Reading battery history without a lock"); + } + ensureInitialized(); return (List<BatteryHistoryFragment>) (List<? extends BatteryHistoryFragment>) mHistoryFiles; @@ -443,44 +447,6 @@ public class BatteryHistoryDirectory implements BatteryStatsHistory.BatteryHisto } @Override - public BatteryHistoryFragment getNextFragment(BatteryHistoryFragment current, long startTimeMs, - long endTimeMs) { - ensureInitialized(); - - if (!mLock.isHeldByCurrentThread()) { - throw new IllegalStateException("Iterating battery history without a lock"); - } - - int nextFileIndex = 0; - int firstFileIndex = 0; - // skip the last file because its data is in history buffer. - int lastFileIndex = mHistoryFiles.size() - 2; - for (int i = lastFileIndex; i >= 0; i--) { - BatteryHistoryFragment fragment = mHistoryFiles.get(i); - if (current != null && fragment.monotonicTimeMs == current.monotonicTimeMs) { - nextFileIndex = i + 1; - } - if (fragment.monotonicTimeMs > endTimeMs) { - lastFileIndex = i - 1; - } - if (fragment.monotonicTimeMs <= startTimeMs) { - firstFileIndex = i; - break; - } - } - - if (nextFileIndex < firstFileIndex) { - nextFileIndex = firstFileIndex; - } - - if (nextFileIndex <= lastFileIndex) { - return mHistoryFiles.get(nextFileIndex); - } - - return null; - } - - @Override public boolean hasCompletedFragments() { ensureInitialized(); diff --git a/services/core/java/com/android/server/selinux/SelinuxAuditLogsCollector.java b/services/core/java/com/android/server/selinux/SelinuxAuditLogsCollector.java index 54365ff03db0..c5a43a57da82 100644 --- a/services/core/java/com/android/server/selinux/SelinuxAuditLogsCollector.java +++ b/services/core/java/com/android/server/selinux/SelinuxAuditLogsCollector.java @@ -72,14 +72,20 @@ class SelinuxAuditLogsCollector { } SelinuxAuditLogsCollector(RateLimiter rateLimiter, QuotaLimiter quotaLimiter) { - this( - () -> - DeviceConfig.getString( - DeviceConfig.NAMESPACE_ADSERVICES, - CONFIG_SELINUX_AUDIT_DOMAIN, - DEFAULT_SELINUX_AUDIT_DOMAIN), - rateLimiter, - quotaLimiter); + this(new DefaultDomainSupplier(), rateLimiter, quotaLimiter); + } + + private static class DefaultDomainSupplier implements Supplier<String> { + @Override + public String get() { + if (SelinuxAuditLogsService.enabledForAllDomains()) { + return "\\w+"; + } + return DeviceConfig.getString( + DeviceConfig.NAMESPACE_ADSERVICES, + CONFIG_SELINUX_AUDIT_DOMAIN, + DEFAULT_SELINUX_AUDIT_DOMAIN); + } } public void setStopRequested(boolean stopRequested) { diff --git a/services/core/java/com/android/server/selinux/SelinuxAuditLogsService.java b/services/core/java/com/android/server/selinux/SelinuxAuditLogsService.java index d46e8916d9e9..9dc457c5d63b 100644 --- a/services/core/java/com/android/server/selinux/SelinuxAuditLogsService.java +++ b/services/core/java/com/android/server/selinux/SelinuxAuditLogsService.java @@ -16,6 +16,7 @@ package com.android.server.selinux; import static com.android.sdksandbox.flags.Flags.selinuxSdkSandboxAudit; +import static com.android.server.selinux.flags.Flags.selinuxLogsCollect; import android.app.job.JobInfo; import android.app.job.JobParameters; @@ -49,6 +50,9 @@ public class SelinuxAuditLogsService extends JobService { "selinux_audit_job_frequency_hours"; private static final String CONFIG_SELINUX_ENABLE_AUDIT_JOB = "selinux_enable_audit_job"; private static final String CONFIG_SELINUX_AUDIT_CAP = "selinux_audit_cap"; + private static final String DEVICE_CONFIG_SECURITY_NAMESPACE = "security"; + private static final String CONFIG_SECURITY_SELINUX_AUDIT_JOB_ENABLED = + "selinux_audit_job_enabled"; private static final int MAX_PERMITS_CAP_DEFAULT = 50000; private static final int SELINUX_AUDIT_JOB_ID = 25327386; @@ -76,7 +80,7 @@ public class SelinuxAuditLogsService extends JobService { /** Schedule jobs with the {@link JobScheduler}. */ public static void schedule(Context context) { - if (!selinuxSdkSandboxAudit()) { + if (!selinuxSdkSandboxAudit() && !enabledForAllDomains()) { Slog.d(TAG, "SelinuxAuditLogsService not enabled"); return; } @@ -86,13 +90,20 @@ public class SelinuxAuditLogsService extends JobService { return; } - LogsCollectorJobScheduler propertiesListener = + LogsCollectorJobScheduler scheduler = new LogsCollectorJobScheduler( context.getSystemService(JobScheduler.class) .forNamespace(SELINUX_AUDIT_NAMESPACE)); - propertiesListener.schedule(); + scheduler.schedule(); + + AdServicesPropertyMonitor adServicesProperties = new AdServicesPropertyMonitor(scheduler); + DeviceConfig.addOnPropertiesChangedListener( + DeviceConfig.NAMESPACE_ADSERVICES, context.getMainExecutor(), adServicesProperties); + + SecurityPropertyMonitor securityProperties = new SecurityPropertyMonitor(scheduler); DeviceConfig.addOnPropertiesChangedListener( - DeviceConfig.NAMESPACE_ADSERVICES, context.getMainExecutor(), propertiesListener); + DEVICE_CONFIG_SECURITY_NAMESPACE, context.getMainExecutor(), securityProperties); + } @Override @@ -101,7 +112,7 @@ public class SelinuxAuditLogsService extends JobService { Slog.e(TAG, "The job id does not match the expected selinux job id."); return false; } - if (!selinuxSdkSandboxAudit()) { + if (!selinuxSdkSandboxAudit() && !enabledForAllDomains()) { Slog.i(TAG, "Selinux audit job disabled."); return false; } @@ -123,17 +134,33 @@ public class SelinuxAuditLogsService extends JobService { return false; } - /** - * This class is in charge of scheduling the job service, and keeping the scheduling up to date - * when the parameters change. - */ - private static final class LogsCollectorJobScheduler + /** Checks if the service is enabled for all domains */ + public static final boolean enabledForAllDomains() { + if (selinuxLogsCollect()) { + return DeviceConfig.getBoolean( + DEVICE_CONFIG_SECURITY_NAMESPACE, + CONFIG_SECURITY_SELINUX_AUDIT_JOB_ENABLED, + false); + } + return false; + } + + /** Checks if the service is enabled for SDK Sandbox */ + public static final boolean enabledForSdkSandbox() { + if (selinuxSdkSandboxAudit()) { + return DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_ADSERVICES, CONFIG_SELINUX_ENABLE_AUDIT_JOB, false); + } + return false; + } + + private static final class AdServicesPropertyMonitor implements DeviceConfig.OnPropertiesChangedListener { - private final JobScheduler mJobScheduler; + private final LogsCollectorJobScheduler mScheduler; - private LogsCollectorJobScheduler(JobScheduler jobScheduler) { - mJobScheduler = jobScheduler; + private AdServicesPropertyMonitor(LogsCollectorJobScheduler scheduler) { + mScheduler = scheduler; } @Override @@ -149,19 +176,65 @@ public class SelinuxAuditLogsService extends JobService { if (keyset.contains(CONFIG_SELINUX_ENABLE_AUDIT_JOB)) { boolean enabled = changedProperties.getBoolean( - CONFIG_SELINUX_ENABLE_AUDIT_JOB, /* defaultValue= */ false); + CONFIG_SELINUX_ENABLE_AUDIT_JOB, /* defaultValue= */ false) + || enabledForAllDomains(); if (enabled) { - schedule(); + mScheduler.schedule(); } else { - mJobScheduler.cancel(SELINUX_AUDIT_JOB_ID); + mScheduler.cancel(); } } else if (keyset.contains(CONFIG_SELINUX_AUDIT_JOB_FREQUENCY_HOURS)) { // The job frequency changed, reschedule. - schedule(); + mScheduler.schedule(); } } + } + + private static final class SecurityPropertyMonitor + implements DeviceConfig.OnPropertiesChangedListener { + + private final LogsCollectorJobScheduler mScheduler; + + private SecurityPropertyMonitor(LogsCollectorJobScheduler scheduler) { + mScheduler = scheduler; + } + + @Override + public void onPropertiesChanged(Properties changedProperties) { + Set<String> keyset = changedProperties.getKeyset(); + + if (keyset.contains(CONFIG_SECURITY_SELINUX_AUDIT_JOB_ENABLED)) { + boolean enabled = + changedProperties.getBoolean( + CONFIG_SECURITY_SELINUX_AUDIT_JOB_ENABLED, + /* defaultValue= */ false) + || enabledForSdkSandbox(); + if (enabled) { + mScheduler.schedule(); + } else { + mScheduler.cancel(); + } + } + } + } + + /** + * This class is in charge of scheduling the job service, and keeping the scheduling up to date + * when the parameters change. + */ + private static final class LogsCollectorJobScheduler { + + private final JobScheduler mJobScheduler; + + private LogsCollectorJobScheduler(JobScheduler jobScheduler) { + mJobScheduler = jobScheduler; + } + + public void cancel() { + mJobScheduler.cancel(SELINUX_AUDIT_JOB_ID); + } - private void schedule() { + public void schedule() { long frequencyMillis = TimeUnit.HOURS.toMillis( DeviceConfig.getInt( diff --git a/services/core/java/com/android/server/selinux/flags.aconfig b/services/core/java/com/android/server/selinux/flags.aconfig new file mode 100644 index 000000000000..3bb5a6bda1de --- /dev/null +++ b/services/core/java/com/android/server/selinux/flags.aconfig @@ -0,0 +1,9 @@ +package: "com.android.server.selinux.flags" +container: "system" + +flag { + name: "selinux_logs_collect" + namespace: "network_security" + description: "Enable collection of SELinux denials based on selinux_audit_job_enabled" + bug: "372950125" +} diff --git a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java index 31348cd9156f..17980c02502f 100644 --- a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java +++ b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java @@ -177,6 +177,7 @@ public final class TextClassificationManagerService extends ITextClassifierServi private final String mDefaultTextClassifierPackage; @Nullable private final String mSystemTextClassifierPackage; + private final MyPackageMonitor mPackageMonitor; private TextClassificationManagerService(Context context) { mContext = Objects.requireNonNull(context); @@ -187,50 +188,50 @@ public final class TextClassificationManagerService extends ITextClassifierServi mDefaultTextClassifierPackage = packageManager.getDefaultTextClassifierPackageName(); mSystemTextClassifierPackage = packageManager.getSystemTextClassifierPackageName(); mSessionCache = new SessionCache(mLock); + mPackageMonitor = new MyPackageMonitor(); } private void startListenSettings() { mSettingsListener.registerObserver(); } - void startTrackingPackageChanges() { - final PackageMonitor monitor = new PackageMonitor() { - - @Override - public void onPackageAdded(String packageName, int uid) { - notifyPackageInstallStatusChange(packageName, /* installed*/ true); - } + private class MyPackageMonitor extends PackageMonitor { + @Override + public void onPackageAdded(String packageName, int uid) { + notifyPackageInstallStatusChange(packageName, /* installed*/ true); + } - @Override - public void onPackageRemoved(String packageName, int uid) { - notifyPackageInstallStatusChange(packageName, /* installed= */ false); - } + @Override + public void onPackageRemoved(String packageName, int uid) { + notifyPackageInstallStatusChange(packageName, /* installed= */ false); + } - @Override - public void onPackageModified(String packageName) { - final int userId = getChangingUserId(); - synchronized (mLock) { - final UserState userState = getUserStateLocked(userId); - final ServiceState serviceState = userState.getServiceStateLocked(packageName); - if (serviceState != null) { - serviceState.onPackageModifiedLocked(); - } + @Override + public void onPackageModified(String packageName) { + final int userId = getChangingUserId(); + synchronized (mLock) { + final UserState userState = getUserStateLocked(userId); + final ServiceState serviceState = userState.getServiceStateLocked(packageName); + if (serviceState != null) { + serviceState.onPackageModifiedLocked(); } } + } - private void notifyPackageInstallStatusChange(String packageName, boolean installed) { - final int userId = getChangingUserId(); - synchronized (mLock) { - final UserState userState = getUserStateLocked(userId); - final ServiceState serviceState = userState.getServiceStateLocked(packageName); - if (serviceState != null) { - serviceState.onPackageInstallStatusChangeLocked(installed); - } + private void notifyPackageInstallStatusChange(String packageName, boolean installed) { + final int userId = getChangingUserId(); + synchronized (mLock) { + final UserState userState = getUserStateLocked(userId); + final ServiceState serviceState = userState.getServiceStateLocked(packageName); + if (serviceState != null) { + serviceState.onPackageInstallStatusChangeLocked(installed); } } - }; + } + } - monitor.register(mContext, null, UserHandle.ALL, true); + void startTrackingPackageChanges() { + mPackageMonitor.register(mContext, null, UserHandle.ALL, true); } @Override diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java index 8bcf1a9be031..47d6879129ee 100644 --- a/services/core/java/com/android/server/tv/TvInputManagerService.java +++ b/services/core/java/com/android/server/tv/TvInputManagerService.java @@ -183,7 +183,7 @@ public final class TvInputManagerService extends SystemService { private final Map<String, SessionState> mSessionIdToSessionStateMap = new HashMap<>(); private final MessageHandler mMessageHandler; - + private final MyPackageMonitor mPackageMonitor; private final ActivityManager mActivityManager; private boolean mExternalInputLoggingDisplayNameFilterEnabled = false; @@ -200,6 +200,7 @@ public final class TvInputManagerService extends SystemService { mMessageHandler = new MessageHandler(mContext.getContentResolver(), IoThread.get().getLooper()); mTvInputHardwareManager = new TvInputHardwareManager(context, new HardwareListener()); + mPackageMonitor = new MyPackageMonitor(/* supportsPackageRestartQuery */ true); mActivityManager = (ActivityManager) getContext().getSystemService(Context.ACTIVITY_SERVICE); @@ -298,74 +299,79 @@ public final class TvInputManagerService extends SystemService { mExternalInputLoggingDeviceBrandNames.addAll(Arrays.asList(deviceBrandNames)); } - private void registerBroadcastReceivers() { - PackageMonitor monitor = new PackageMonitor(/* supportsPackageRestartQuery */ true) { - private void buildTvInputList(String[] packages) { - int userId = getChangingUserId(); - synchronized (mLock) { - if (mCurrentUserId == userId || mRunningProfiles.contains(userId)) { - buildTvInputListLocked(userId, packages); - buildTvContentRatingSystemListLocked(userId); - } + private class MyPackageMonitor extends PackageMonitor { + MyPackageMonitor(boolean supportsPackageRestartQuery) { + super(supportsPackageRestartQuery); + } + + private void buildTvInputList(String[] packages) { + int userId = getChangingUserId(); + synchronized (mLock) { + if (mCurrentUserId == userId || mRunningProfiles.contains(userId)) { + buildTvInputListLocked(userId, packages); + buildTvContentRatingSystemListLocked(userId); } } + } - @Override - public void onPackageUpdateFinished(String packageName, int uid) { - if (DEBUG) Slog.d(TAG, "onPackageUpdateFinished(packageName=" + packageName + ")"); - // This callback is invoked when the TV input is reinstalled. - // In this case, isReplacing() always returns true. - buildTvInputList(new String[] { packageName }); - } + @Override + public void onPackageUpdateFinished(String packageName, int uid) { + if (DEBUG) Slog.d(TAG, "onPackageUpdateFinished(packageName=" + packageName + ")"); + // This callback is invoked when the TV input is reinstalled. + // In this case, isReplacing() always returns true. + buildTvInputList(new String[] { packageName }); + } - @Override - public void onPackagesAvailable(String[] packages) { - if (DEBUG) { - Slog.d(TAG, "onPackagesAvailable(packages=" + Arrays.toString(packages) + ")"); - } - // This callback is invoked when the media on which some packages exist become - // available. - if (isReplacing()) { - buildTvInputList(packages); - } + @Override + public void onPackagesAvailable(String[] packages) { + if (DEBUG) { + Slog.d(TAG, "onPackagesAvailable(packages=" + Arrays.toString(packages) + ")"); } + // This callback is invoked when the media on which some packages exist become + // available. + if (isReplacing()) { + buildTvInputList(packages); + } + } - @Override - public void onPackagesUnavailable(String[] packages) { - // This callback is invoked when the media on which some packages exist become - // unavailable. - if (DEBUG) { - Slog.d(TAG, "onPackagesUnavailable(packages=" + Arrays.toString(packages) - + ")"); - } - if (isReplacing()) { - buildTvInputList(packages); - } + @Override + public void onPackagesUnavailable(String[] packages) { + // This callback is invoked when the media on which some packages exist become + // unavailable. + if (DEBUG) { + Slog.d(TAG, "onPackagesUnavailable(packages=" + Arrays.toString(packages) + + ")"); } + if (isReplacing()) { + buildTvInputList(packages); + } + } - @Override - public void onSomePackagesChanged() { - // TODO: Use finer-grained methods(e.g. onPackageAdded, onPackageRemoved) to manage - // the TV inputs. - if (DEBUG) Slog.d(TAG, "onSomePackagesChanged()"); - if (isReplacing()) { - if (DEBUG) Slog.d(TAG, "Skipped building TV input list due to replacing"); - // When the package is updated, buildTvInputListLocked is called in other - // methods instead. - return; - } - buildTvInputList(null); + @Override + public void onSomePackagesChanged() { + // TODO: Use finer-grained methods(e.g. onPackageAdded, onPackageRemoved) to manage + // the TV inputs. + if (DEBUG) Slog.d(TAG, "onSomePackagesChanged()"); + if (isReplacing()) { + if (DEBUG) Slog.d(TAG, "Skipped building TV input list due to replacing"); + // When the package is updated, buildTvInputListLocked is called in other + // methods instead. + return; } + buildTvInputList(null); + } - @Override - public boolean onPackageChanged(String packageName, int uid, String[] components) { - // The input list needs to be updated in any cases, regardless of whether - // it happened to the whole package or a specific component. Returning true so that - // the update can be handled in {@link #onSomePackagesChanged}. - return true; - } - }; - monitor.register(mContext, null, UserHandle.ALL, true); + @Override + public boolean onPackageChanged(String packageName, int uid, String[] components) { + // The input list needs to be updated in any cases, regardless of whether + // it happened to the whole package or a specific component. Returning true so that + // the update can be handled in {@link #onSomePackagesChanged}. + return true; + } + } + + private void registerBroadcastReceivers() { + mPackageMonitor.register(mContext, null, UserHandle.ALL, true); IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(Intent.ACTION_USER_SWITCHED); diff --git a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java index 6a7fc6dcf7cd..42013fab7a14 100644 --- a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java +++ b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java @@ -105,6 +105,7 @@ public class TvInteractiveAppManagerService extends SystemService { // A global lock. private final Object mLock = new Object(); private final Context mContext; + private final MyPackageMonitor mPackageMonitor; // ID of the current user. @GuardedBy("mLock") private int mCurrentUserId = UserHandle.USER_SYSTEM; @@ -138,6 +139,7 @@ public class TvInteractiveAppManagerService extends SystemService { super(context); mContext = context; mUserManager = (UserManager) getContext().getSystemService(Context.USER_SERVICE); + mPackageMonitor = new MyPackageMonitor(/* supportsPackageRestartQuery */ true); } @GuardedBy("mLock") @@ -518,86 +520,91 @@ public class TvInteractiveAppManagerService extends SystemService { } } - private void registerBroadcastReceivers() { - PackageMonitor monitor = new PackageMonitor(/* supportsPackageRestartQuery */ true) { - private void buildTvInteractiveAppServiceList(String[] packages) { - int userId = getChangingUserId(); - synchronized (mLock) { - if (mCurrentUserId == userId || mRunningProfiles.contains(userId)) { - buildTvInteractiveAppServiceListLocked(userId, packages); - buildAppLinkInfoLocked(userId); - } + private class MyPackageMonitor extends PackageMonitor { + MyPackageMonitor(boolean supportsPackageRestartQuery) { + super(supportsPackageRestartQuery); + } + + private void buildTvInteractiveAppServiceList(String[] packages) { + int userId = getChangingUserId(); + synchronized (mLock) { + if (mCurrentUserId == userId || mRunningProfiles.contains(userId)) { + buildTvInteractiveAppServiceListLocked(userId, packages); + buildAppLinkInfoLocked(userId); } } - private void buildTvAdServiceList(String[] packages) { - int userId = getChangingUserId(); - synchronized (mLock) { - if (mCurrentUserId == userId || mRunningProfiles.contains(userId)) { - buildTvAdServiceListLocked(userId, packages); - } + } + private void buildTvAdServiceList(String[] packages) { + int userId = getChangingUserId(); + synchronized (mLock) { + if (mCurrentUserId == userId || mRunningProfiles.contains(userId)) { + buildTvAdServiceListLocked(userId, packages); } } + } - @Override - public void onPackageUpdateFinished(String packageName, int uid) { - if (DEBUG) Slogf.d(TAG, "onPackageUpdateFinished(packageName=" + packageName + ")"); - // This callback is invoked when the TV interactive App service is reinstalled. - // In this case, isReplacing() always returns true. - buildTvInteractiveAppServiceList(new String[] { packageName }); - buildTvAdServiceList(new String[] { packageName }); - } + @Override + public void onPackageUpdateFinished(String packageName, int uid) { + if (DEBUG) Slogf.d(TAG, "onPackageUpdateFinished(packageName=" + packageName + ")"); + // This callback is invoked when the TV interactive App service is reinstalled. + // In this case, isReplacing() always returns true. + buildTvInteractiveAppServiceList(new String[] { packageName }); + buildTvAdServiceList(new String[] { packageName }); + } - @Override - public void onPackagesAvailable(String[] packages) { - if (DEBUG) { - Slogf.d(TAG, "onPackagesAvailable(packages=" + Arrays.toString(packages) + ")"); - } - // This callback is invoked when the media on which some packages exist become - // available. - if (isReplacing()) { - buildTvInteractiveAppServiceList(packages); - buildTvAdServiceList(packages); - } + @Override + public void onPackagesAvailable(String[] packages) { + if (DEBUG) { + Slogf.d(TAG, "onPackagesAvailable(packages=" + Arrays.toString(packages) + ")"); } + // This callback is invoked when the media on which some packages exist become + // available. + if (isReplacing()) { + buildTvInteractiveAppServiceList(packages); + buildTvAdServiceList(packages); + } + } - @Override - public void onPackagesUnavailable(String[] packages) { - // This callback is invoked when the media on which some packages exist become - // unavailable. - if (DEBUG) { - Slogf.d(TAG, "onPackagesUnavailable(packages=" + Arrays.toString(packages) - + ")"); - } - if (isReplacing()) { - buildTvInteractiveAppServiceList(packages); - buildTvAdServiceList(packages); - } + @Override + public void onPackagesUnavailable(String[] packages) { + // This callback is invoked when the media on which some packages exist become + // unavailable. + if (DEBUG) { + Slogf.d(TAG, "onPackagesUnavailable(packages=" + Arrays.toString(packages) + + ")"); } + if (isReplacing()) { + buildTvInteractiveAppServiceList(packages); + buildTvAdServiceList(packages); + } + } - @Override - public void onSomePackagesChanged() { - if (DEBUG) Slogf.d(TAG, "onSomePackagesChanged()"); - if (isReplacing()) { - if (DEBUG) { - Slogf.d(TAG, "Skipped building TV interactive App list due to replacing"); - } - // When the package is updated, buildTvInteractiveAppServiceListLocked is called - // in other methods instead. - return; + @Override + public void onSomePackagesChanged() { + if (DEBUG) Slogf.d(TAG, "onSomePackagesChanged()"); + if (isReplacing()) { + if (DEBUG) { + Slogf.d(TAG, "Skipped building TV interactive App list due to replacing"); } - buildTvInteractiveAppServiceList(null); - buildTvAdServiceList(null); + // When the package is updated, buildTvInteractiveAppServiceListLocked is called + // in other methods instead. + return; } + buildTvInteractiveAppServiceList(null); + buildTvAdServiceList(null); + } - @Override - public boolean onPackageChanged(String packageName, int uid, String[] components) { - // The interactive App list needs to be updated in any cases, regardless of whether - // it happened to the whole package or a specific component. Returning true so that - // the update can be handled in {@link #onSomePackagesChanged}. - return true; - } - }; - monitor.register(mContext, null, UserHandle.ALL, true); + @Override + public boolean onPackageChanged(String packageName, int uid, String[] components) { + // The interactive App list needs to be updated in any cases, regardless of whether + // it happened to the whole package or a specific component. Returning true so that + // the update can be handled in {@link #onSomePackagesChanged}. + return true; + } + } + + private void registerBroadcastReceivers() { + mPackageMonitor.register(mContext, null, UserHandle.ALL, true); IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(Intent.ACTION_USER_SWITCHED); diff --git a/services/core/java/com/android/server/updates/CertPinInstallReceiver.java b/services/core/java/com/android/server/updates/CertPinInstallReceiver.java index c8e7a8dea5c3..250e99b47b1a 100644 --- a/services/core/java/com/android/server/updates/CertPinInstallReceiver.java +++ b/services/core/java/com/android/server/updates/CertPinInstallReceiver.java @@ -19,10 +19,7 @@ package com.android.server.updates; import android.content.Context; import android.content.Intent; -import java.io.File; - public class CertPinInstallReceiver extends ConfigUpdateInstallReceiver { - private static final String KEYCHAIN_DIR = "/data/misc/keychain/"; public CertPinInstallReceiver() { super("/data/misc/keychain/", "pins", "metadata/", "version"); @@ -30,22 +27,7 @@ public class CertPinInstallReceiver extends ConfigUpdateInstallReceiver { @Override public void onReceive(final Context context, final Intent intent) { - if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) { - if (com.android.server.flags.Flags.certpininstallerRemoval()) { - File pins = new File(KEYCHAIN_DIR + "pins"); - if (pins.exists()) { - pins.delete(); - } - File version = new File(KEYCHAIN_DIR + "metadata/version"); - if (version.exists()) { - version.delete(); - } - File metadata = new File(KEYCHAIN_DIR + "metadata"); - if (metadata.exists()) { - metadata.delete(); - } - } - } else if (!com.android.server.flags.Flags.certpininstallerRemoval()) { + if (!com.android.server.flags.Flags.certpininstallerRemoval()) { super.onReceive(context, intent); } } diff --git a/services/core/java/com/android/server/vr/EnabledComponentsObserver.java b/services/core/java/com/android/server/vr/EnabledComponentsObserver.java index 7c2ce6467122..458cb023cc4e 100644 --- a/services/core/java/com/android/server/vr/EnabledComponentsObserver.java +++ b/services/core/java/com/android/server/vr/EnabledComponentsObserver.java @@ -58,6 +58,7 @@ public class EnabledComponentsObserver implements SettingChangeListener { private final Object mLock; private final Context mContext; + private final PackageMonitor mPackageMonitor; private final String mSettingName; private final String mServiceName; private final String mServicePermission; @@ -78,13 +79,39 @@ public class EnabledComponentsObserver implements SettingChangeListener { private EnabledComponentsObserver(@NonNull Context context, @NonNull String settingName, @NonNull String servicePermission, @NonNull String serviceName, @NonNull Object lock, - @NonNull Collection<EnabledComponentChangeListener> listeners) { + @NonNull Collection<EnabledComponentChangeListener> listeners, + @NonNull Looper looper) { mLock = lock; mContext = context; mSettingName = settingName; mServiceName = serviceName; mServicePermission = servicePermission; mEnabledComponentListeners.addAll(listeners); + mPackageMonitor = new PackageMonitor(true) { + @Override + public void onSomePackagesChanged() { + onPackagesChanged(); + } + + @Override + public void onPackageDisappeared(String packageName, int reason) { + onPackagesChanged(); + } + + @Override + public void onPackageModified(String packageName) { + onPackagesChanged(); + } + + @Override + public boolean onHandleForceStop(Intent intent, String[] packages, int uid, + boolean doit) { + onPackagesChanged(); + return super.onHandleForceStop(intent, packages, uid, doit); + } + }; + + mPackageMonitor.register(context, looper, UserHandle.ALL, true);; } /** @@ -108,38 +135,7 @@ public class EnabledComponentsObserver implements SettingChangeListener { SettingsObserver s = SettingsObserver.build(context, handler, settingName); final EnabledComponentsObserver o = new EnabledComponentsObserver(context, settingName, - servicePermission, serviceName, lock, listeners); - - PackageMonitor packageMonitor = new PackageMonitor(true) { - @Override - public void onSomePackagesChanged() { - o.onPackagesChanged(); - - } - - @Override - public void onPackageDisappeared(String packageName, int reason) { - o.onPackagesChanged(); - - } - - @Override - public void onPackageModified(String packageName) { - o.onPackagesChanged(); - - } - - @Override - public boolean onHandleForceStop(Intent intent, String[] packages, int uid, - boolean doit) { - o.onPackagesChanged(); - - return super.onHandleForceStop(intent, packages, uid, doit); - } - }; - - packageMonitor.register(context, looper, UserHandle.ALL, true); - + servicePermission, serviceName, lock, listeners, looper); s.addListener(o); return o; diff --git a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java index 8e8455ad5288..6e640d890fb8 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java @@ -17,7 +17,6 @@ package com.android.server.wallpaper; import static android.app.WallpaperManager.ORIENTATION_LANDSCAPE; -import static android.app.WallpaperManager.ORIENTATION_SQUARE_LANDSCAPE; import static android.app.WallpaperManager.ORIENTATION_UNKNOWN; import static android.app.WallpaperManager.getOrientation; import static android.app.WallpaperManager.getRotatedOrientation; @@ -85,20 +84,11 @@ public class WallpaperCropper { private final WallpaperDisplayHelper mWallpaperDisplayHelper; - /** - * Helpers exposed to the window manager part (WallpaperController) - */ - public interface WallpaperCropUtils { - - /** - * Equivalent to {@link WallpaperCropper#getCrop(Point, Point, SparseArray, boolean)} - */ - Rect getCrop(Point displaySize, Point bitmapSize, - SparseArray<Rect> suggestedCrops, boolean rtl); - } + private final WallpaperDefaultDisplayInfo mDefaultDisplayInfo; WallpaperCropper(WallpaperDisplayHelper wallpaperDisplayHelper) { mWallpaperDisplayHelper = wallpaperDisplayHelper; + mDefaultDisplayInfo = mWallpaperDisplayHelper.getDefaultDisplayInfo(); } /** @@ -116,16 +106,16 @@ public class WallpaperCropper { * {@link #getAdjustedCrop}. * </ul> * - * @param displaySize The dimensions of the surface where we want to render the wallpaper - * @param bitmapSize The dimensions of the wallpaper bitmap - * @param rtl Whether the device is right-to-left - * @param suggestedCrops An optional list of user-defined crops for some orientations. - * If there is a suggested crop for + * @param displaySize The dimensions of the surface where we want to render the wallpaper + * @param defaultDisplayInfo The default display info + * @param bitmapSize The dimensions of the wallpaper bitmap + * @param rtl Whether the device is right-to-left + * @param suggestedCrops An optional list of user-defined crops for some orientations. * * @return A Rect indicating how to crop the bitmap for the current display. */ - public Rect getCrop(Point displaySize, Point bitmapSize, - SparseArray<Rect> suggestedCrops, boolean rtl) { + public static Rect getCrop(Point displaySize, WallpaperDefaultDisplayInfo defaultDisplayInfo, + Point bitmapSize, SparseArray<Rect> suggestedCrops, boolean rtl) { int orientation = getOrientation(displaySize); @@ -135,23 +125,24 @@ public class WallpaperCropper { // The first exception is if the device is a foldable and we're on the folded screen. // In that case, show the center of what's on the unfolded screen. - int unfoldedOrientation = mWallpaperDisplayHelper.getUnfoldedOrientation(orientation); + int unfoldedOrientation = defaultDisplayInfo.getUnfoldedOrientation(orientation); if (unfoldedOrientation != ORIENTATION_UNKNOWN) { // Let the system know that we're showing the full image on the unfolded screen SparseArray<Rect> newSuggestedCrops = new SparseArray<>(); newSuggestedCrops.put(unfoldedOrientation, crop); // This will fall into "Case 4" of this function and center the folded screen - return getCrop(displaySize, bitmapSize, newSuggestedCrops, rtl); + return getCrop(displaySize, defaultDisplayInfo, bitmapSize, newSuggestedCrops, + rtl); } // The second exception is if we're on tablet and we're on portrait mode. // In that case, center the wallpaper relatively to landscape and put some parallax. - boolean isTablet = mWallpaperDisplayHelper.isLargeScreen() - && !mWallpaperDisplayHelper.isFoldable(); + boolean isTablet = defaultDisplayInfo.isLargeScreen && !defaultDisplayInfo.isFoldable; if (isTablet && displaySize.x < displaySize.y) { Point rotatedDisplaySize = new Point(displaySize.y, displaySize.x); // compute the crop on landscape (without parallax) - Rect landscapeCrop = getCrop(rotatedDisplaySize, bitmapSize, suggestedCrops, rtl); + Rect landscapeCrop = getCrop(rotatedDisplaySize, defaultDisplayInfo, bitmapSize, + suggestedCrops, rtl); landscapeCrop = noParallax(landscapeCrop, rotatedDisplaySize, bitmapSize, rtl); // compute the crop on portrait at the center of the landscape crop crop = getAdjustedCrop(landscapeCrop, bitmapSize, displaySize, false, rtl, ADD); @@ -173,7 +164,8 @@ public class WallpaperCropper { if (testCrop == null || testCrop.left < 0 || testCrop.top < 0 || testCrop.right > bitmapSize.x || testCrop.bottom > bitmapSize.y) { Slog.w(TAG, "invalid crop: " + testCrop + " for bitmap size: " + bitmapSize); - return getCrop(displaySize, bitmapSize, new SparseArray<>(), rtl); + return getCrop(displaySize, defaultDisplayInfo, bitmapSize, new SparseArray<>(), + rtl); } } @@ -185,10 +177,9 @@ public class WallpaperCropper { // Case 3: if we have the 90° rotated orientation in the suggested crops, reuse it and // trying to preserve the zoom level and the center of the image - SparseArray<Point> defaultDisplaySizes = mWallpaperDisplayHelper.getDefaultDisplaySizes(); int rotatedOrientation = getRotatedOrientation(orientation); suggestedCrop = suggestedCrops.get(rotatedOrientation); - Point suggestedDisplaySize = defaultDisplaySizes.get(rotatedOrientation); + Point suggestedDisplaySize = defaultDisplayInfo.defaultDisplaySizes.get(rotatedOrientation); if (suggestedCrop != null) { // only keep the visible part (without parallax) Rect adjustedCrop = noParallax(suggestedCrop, suggestedDisplaySize, bitmapSize, rtl); @@ -197,9 +188,9 @@ public class WallpaperCropper { // Case 4: if the device is a foldable, if we're looking for a folded orientation and have // the suggested crop of the relative unfolded orientation, reuse it by removing content. - int unfoldedOrientation = mWallpaperDisplayHelper.getUnfoldedOrientation(orientation); + int unfoldedOrientation = defaultDisplayInfo.getUnfoldedOrientation(orientation); suggestedCrop = suggestedCrops.get(unfoldedOrientation); - suggestedDisplaySize = defaultDisplaySizes.get(unfoldedOrientation); + suggestedDisplaySize = defaultDisplayInfo.defaultDisplaySizes.get(unfoldedOrientation); if (suggestedCrop != null) { // compute the visible part (without parallax) of the unfolded screen Rect adjustedCrop = noParallax(suggestedCrop, suggestedDisplaySize, bitmapSize, rtl); @@ -207,8 +198,11 @@ public class WallpaperCropper { Rect res = getAdjustedCrop(adjustedCrop, bitmapSize, displaySize, false, rtl, REMOVE); // if we removed some width, add it back to add a parallax effect if (res.width() < adjustedCrop.width()) { - if (rtl) res.left = Math.min(res.left, adjustedCrop.left); - else res.right = Math.max(res.right, adjustedCrop.right); + if (rtl) { + res.left = Math.min(res.left, adjustedCrop.left); + } else { + res.right = Math.max(res.right, adjustedCrop.right); + } // use getAdjustedCrop(parallax=true) to make sure we don't exceed MAX_PARALLAX res = getAdjustedCrop(res, bitmapSize, displaySize, true, rtl, ADD); } @@ -218,9 +212,9 @@ public class WallpaperCropper { // Case 5: if the device is a foldable, if we're looking for an unfolded orientation and // have the suggested crop of the relative folded orientation, reuse it by adding content. - int foldedOrientation = mWallpaperDisplayHelper.getFoldedOrientation(orientation); + int foldedOrientation = defaultDisplayInfo.getFoldedOrientation(orientation); suggestedCrop = suggestedCrops.get(foldedOrientation); - suggestedDisplaySize = defaultDisplaySizes.get(foldedOrientation); + suggestedDisplaySize = defaultDisplayInfo.defaultDisplaySizes.get(foldedOrientation); if (suggestedCrop != null) { // only keep the visible part (without parallax) Rect adjustedCrop = noParallax(suggestedCrop, suggestedDisplaySize, bitmapSize, rtl); @@ -229,17 +223,19 @@ public class WallpaperCropper { // Case 6: for a foldable device, try to combine case 3 + case 4 or 5: // rotate, then fold or unfold - Point rotatedDisplaySize = defaultDisplaySizes.get(rotatedOrientation); + Point rotatedDisplaySize = defaultDisplayInfo.defaultDisplaySizes.get(rotatedOrientation); if (rotatedDisplaySize != null) { - int rotatedFolded = mWallpaperDisplayHelper.getFoldedOrientation(rotatedOrientation); - int rotateUnfolded = mWallpaperDisplayHelper.getUnfoldedOrientation(rotatedOrientation); + int rotatedFolded = defaultDisplayInfo.getFoldedOrientation(rotatedOrientation); + int rotateUnfolded = defaultDisplayInfo.getUnfoldedOrientation(rotatedOrientation); for (int suggestedOrientation : new int[]{rotatedFolded, rotateUnfolded}) { suggestedCrop = suggestedCrops.get(suggestedOrientation); if (suggestedCrop != null) { - Rect rotatedCrop = getCrop(rotatedDisplaySize, bitmapSize, suggestedCrops, rtl); + Rect rotatedCrop = getCrop(rotatedDisplaySize, defaultDisplayInfo, bitmapSize, + suggestedCrops, rtl); SparseArray<Rect> rotatedCropMap = new SparseArray<>(); rotatedCropMap.put(rotatedOrientation, rotatedCrop); - return getCrop(displaySize, bitmapSize, rotatedCropMap, rtl); + return getCrop(displaySize, defaultDisplayInfo, bitmapSize, rotatedCropMap, + rtl); } } } @@ -248,8 +244,8 @@ public class WallpaperCropper { Slog.w(TAG, "Could not find a proper default crop for display: " + displaySize + ", bitmap size: " + bitmapSize + ", suggested crops: " + suggestedCrops + ", orientation: " + orientation + ", rtl: " + rtl - + ", defaultDisplaySizes: " + defaultDisplaySizes); - return getCrop(displaySize, bitmapSize, new SparseArray<>(), rtl); + + ", defaultDisplaySizes: " + defaultDisplayInfo.defaultDisplaySizes); + return getCrop(displaySize, defaultDisplayInfo, bitmapSize, new SparseArray<>(), rtl); } /** @@ -445,7 +441,7 @@ public class WallpaperCropper { Rect suggestedCrop = suggestedCrops.get(orientation); if (suggestedCrop != null) { adjustedSuggestedCrops.put(orientation, - getCrop(displaySize, bitmapSize, suggestedCrops, rtl)); + getCrop(displaySize, mDefaultDisplayInfo, bitmapSize, suggestedCrops, rtl)); } } @@ -455,7 +451,8 @@ public class WallpaperCropper { int orientation = defaultDisplaySizes.keyAt(i); if (result.contains(orientation)) continue; Point displaySize = defaultDisplaySizes.valueAt(i); - Rect newCrop = getCrop(displaySize, bitmapSize, adjustedSuggestedCrops, rtl); + Rect newCrop = getCrop(displaySize, mDefaultDisplayInfo, bitmapSize, + adjustedSuggestedCrops, rtl); result.put(orientation, newCrop); } return result; diff --git a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java index ba0262a8bd19..69f0ef7c430e 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java @@ -542,9 +542,11 @@ public class WallpaperDataParser { // to support back compatibility in B&R, save the crops for one orientation in the // legacy "cropLeft", "cropTop", "cropRight", "cropBottom" entries int orientationToPutInLegacyCrop = wallpaper.mOrientationWhenSet; - if (mWallpaperDisplayHelper.isFoldable()) { - int unfoldedOrientation = mWallpaperDisplayHelper - .getUnfoldedOrientation(orientationToPutInLegacyCrop); + WallpaperDefaultDisplayInfo defaultDisplayInfo = + mWallpaperDisplayHelper.getDefaultDisplayInfo(); + if (defaultDisplayInfo.isFoldable) { + int unfoldedOrientation = defaultDisplayInfo.getUnfoldedOrientation( + orientationToPutInLegacyCrop); if (unfoldedOrientation != ORIENTATION_UNKNOWN) { orientationToPutInLegacyCrop = unfoldedOrientation; } diff --git a/services/core/java/com/android/server/wallpaper/WallpaperDefaultDisplayInfo.java b/services/core/java/com/android/server/wallpaper/WallpaperDefaultDisplayInfo.java new file mode 100644 index 000000000000..dabe91968338 --- /dev/null +++ b/services/core/java/com/android/server/wallpaper/WallpaperDefaultDisplayInfo.java @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wallpaper; + +import static android.app.WallpaperManager.ORIENTATION_UNKNOWN; +import static android.app.WallpaperManager.getRotatedOrientation; +import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP; + +import android.app.WallpaperManager; +import android.content.res.Resources; +import android.graphics.Point; +import android.graphics.Rect; +import android.util.SparseArray; +import android.view.WindowManager; +import android.view.WindowMetrics; + +import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Set; + + +/** A data class for the default display attributes used in wallpaper related operations. */ +public final class WallpaperDefaultDisplayInfo { + /** + * A data class representing the screen orientations for a foldable device in the folded and + * unfolded states. + */ + @VisibleForTesting + static final class FoldableOrientations { + @WallpaperManager.ScreenOrientation + public final int foldedOrientation; + @WallpaperManager.ScreenOrientation + public final int unfoldedOrientation; + + FoldableOrientations(int foldedOrientation, int unfoldedOrientation) { + this.foldedOrientation = foldedOrientation; + this.unfoldedOrientation = unfoldedOrientation; + } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + if (!(other instanceof FoldableOrientations that)) return false; + return foldedOrientation == that.foldedOrientation + && unfoldedOrientation == that.unfoldedOrientation; + } + + @Override + public int hashCode() { + return Objects.hash(foldedOrientation, unfoldedOrientation); + } + } + + public final SparseArray<Point> defaultDisplaySizes; + public final boolean isLargeScreen; + public final boolean isFoldable; + @VisibleForTesting + final List<FoldableOrientations> foldableOrientations; + + public WallpaperDefaultDisplayInfo() { + this.defaultDisplaySizes = new SparseArray<>(); + this.isLargeScreen = false; + this.isFoldable = false; + this.foldableOrientations = Collections.emptyList(); + } + + public WallpaperDefaultDisplayInfo(WindowManager windowManager, Resources resources) { + Set<WindowMetrics> metrics = windowManager.getPossibleMaximumWindowMetrics(DEFAULT_DISPLAY); + boolean isFoldable = resources.getIntArray(R.array.config_foldedDeviceStates).length > 0; + if (isFoldable) { + this.foldableOrientations = getFoldableOrientations(metrics); + } else { + this.foldableOrientations = Collections.emptyList(); + } + this.defaultDisplaySizes = getDisplaySizes(metrics); + this.isLargeScreen = isLargeScreen(metrics); + this.isFoldable = isFoldable; + } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + if (!(other instanceof WallpaperDefaultDisplayInfo that)) return false; + return isLargeScreen == that.isLargeScreen && isFoldable == that.isFoldable + && defaultDisplaySizes.contentEquals(that.defaultDisplaySizes) + && Objects.equals(foldableOrientations, that.foldableOrientations); + } + + @Override + public int hashCode() { + return 31 * Objects.hash(isLargeScreen, isFoldable, foldableOrientations) + + defaultDisplaySizes.contentHashCode(); + } + + /** + * Returns the folded orientation corresponds to the {@code unfoldedOrientation} found in + * {@link #foldableOrientations}. If not found, returns + * {@link WallpaperManager.ORIENTATION_UNKNOWN}. + */ + public int getFoldedOrientation(int unfoldedOrientation) { + for (FoldableOrientations orientations : foldableOrientations) { + if (orientations.unfoldedOrientation == unfoldedOrientation) { + return orientations.foldedOrientation; + } + } + return ORIENTATION_UNKNOWN; + } + + /** + * Returns the unfolded orientation corresponds to the {@code foldedOrientation} found in + * {@link #foldableOrientations}. If not found, returns + * {@link WallpaperManager.ORIENTATION_UNKNOWN}. + */ + public int getUnfoldedOrientation(int foldedOrientation) { + for (FoldableOrientations orientations : foldableOrientations) { + if (orientations.foldedOrientation == foldedOrientation) { + return orientations.unfoldedOrientation; + } + } + return ORIENTATION_UNKNOWN; + } + + private static SparseArray<Point> getDisplaySizes(Set<WindowMetrics> displayMetrics) { + SparseArray<Point> displaySizes = new SparseArray<>(); + for (WindowMetrics metric : displayMetrics) { + Rect bounds = metric.getBounds(); + Point displaySize = new Point(bounds.width(), bounds.height()); + Point reversedDisplaySize = new Point(displaySize.y, displaySize.x); + for (Point point : List.of(displaySize, reversedDisplaySize)) { + int orientation = WallpaperManager.getOrientation(point); + // don't add an entry if there is already a larger display of the same orientation + Point display = displaySizes.get(orientation); + if (display == null || display.x * display.y < point.x * point.y) { + displaySizes.put(orientation, point); + } + } + } + return displaySizes; + } + + private static boolean isLargeScreen(Set<WindowMetrics> displayMetrics) { + float smallestWidth = Float.MAX_VALUE; + for (WindowMetrics metric : displayMetrics) { + Rect bounds = metric.getBounds(); + smallestWidth = Math.min(smallestWidth, bounds.width() / metric.getDensity()); + } + return smallestWidth >= LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP; + } + + /** + * Determines all potential foldable orientations, populating {@code + * outFoldableOrientationPairs} with pairs of (folded orientation, unfolded orientation). If + * {@code defaultDisplayMetrics} isn't for foldable, {@code outFoldableOrientationPairs} will + * not be populated. + */ + private static List<FoldableOrientations> getFoldableOrientations( + Set<WindowMetrics> defaultDisplayMetrics) { + if (defaultDisplayMetrics.size() != 2) { + return Collections.emptyList(); + } + List<FoldableOrientations> foldableOrientations = new ArrayList<>(); + float surface = 0; + int firstOrientation = -1; + for (WindowMetrics metric : defaultDisplayMetrics) { + Rect bounds = metric.getBounds(); + Point displaySize = new Point(bounds.width(), bounds.height()); + + int orientation = WallpaperManager.getOrientation(displaySize); + float newSurface = displaySize.x * displaySize.y + / (metric.getDensity() * metric.getDensity()); + if (surface <= 0) { + surface = newSurface; + firstOrientation = orientation; + } else { + FoldableOrientations orientations = (newSurface > surface) + ? new FoldableOrientations(firstOrientation, orientation) + : new FoldableOrientations(orientation, firstOrientation); + FoldableOrientations rotatedOrientations = new FoldableOrientations( + getRotatedOrientation(orientations.foldedOrientation), + getRotatedOrientation(orientations.unfoldedOrientation)); + foldableOrientations.add(orientations); + foldableOrientations.add(rotatedOrientations); + } + } + return Collections.unmodifiableList(foldableOrientations); + } +} diff --git a/services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java b/services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java index 3636f5aa8f27..bff5fc9c49f3 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java @@ -16,31 +16,25 @@ package com.android.server.wallpaper; -import static android.app.WallpaperManager.ORIENTATION_UNKNOWN; -import static android.app.WallpaperManager.getRotatedOrientation; import static android.view.Display.DEFAULT_DISPLAY; import static com.android.window.flags.Flags.multiCrop; import android.app.WallpaperManager; +import android.content.res.Resources; import android.graphics.Point; import android.graphics.Rect; import android.hardware.display.DisplayManager; import android.os.Binder; import android.os.Debug; -import android.util.Pair; import android.util.Slog; import android.util.SparseArray; import android.view.Display; import android.view.DisplayInfo; import android.view.WindowManager; -import android.view.WindowMetrics; import com.android.server.wm.WindowManagerInternal; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; import java.util.function.Consumer; /** @@ -59,65 +53,25 @@ class WallpaperDisplayHelper { } private static final String TAG = WallpaperDisplayHelper.class.getSimpleName(); - private static final float LARGE_SCREEN_MIN_DP = 600f; private final SparseArray<DisplayData> mDisplayDatas = new SparseArray<>(); private final DisplayManager mDisplayManager; private final WindowManagerInternal mWindowManagerInternal; - private final SparseArray<Point> mDefaultDisplaySizes = new SparseArray<>(); - // related orientations pairs for foldable (folded orientation, unfolded orientation) - private final List<Pair<Integer, Integer>> mFoldableOrientationPairs = new ArrayList<>(); - - private final boolean mIsFoldable; - private boolean mIsLargeScreen = false; + private final WallpaperDefaultDisplayInfo mDefaultDisplayInfo; WallpaperDisplayHelper( DisplayManager displayManager, WindowManager windowManager, WindowManagerInternal windowManagerInternal, - boolean isFoldable) { + Resources resources) { mDisplayManager = displayManager; mWindowManagerInternal = windowManagerInternal; - mIsFoldable = isFoldable; - if (!multiCrop()) return; - Set<WindowMetrics> metrics = windowManager.getPossibleMaximumWindowMetrics(DEFAULT_DISPLAY); - boolean populateOrientationPairs = isFoldable && metrics.size() == 2; - float surface = 0; - int firstOrientation = -1; - for (WindowMetrics metric: metrics) { - Rect bounds = metric.getBounds(); - Point displaySize = new Point(bounds.width(), bounds.height()); - Point reversedDisplaySize = new Point(displaySize.y, displaySize.x); - for (Point point : List.of(displaySize, reversedDisplaySize)) { - int orientation = WallpaperManager.getOrientation(point); - // don't add an entry if there is already a larger display of the same orientation - Point display = mDefaultDisplaySizes.get(orientation); - if (display == null || display.x * display.y < point.x * point.y) { - mDefaultDisplaySizes.put(orientation, point); - } - } - - mIsLargeScreen |= (displaySize.x / metric.getDensity() >= LARGE_SCREEN_MIN_DP); - - if (populateOrientationPairs) { - int orientation = WallpaperManager.getOrientation(displaySize); - float newSurface = displaySize.x * displaySize.y - / (metric.getDensity() * metric.getDensity()); - if (surface <= 0) { - surface = newSurface; - firstOrientation = orientation; - } else { - Pair<Integer, Integer> pair = (newSurface > surface) - ? new Pair<>(firstOrientation, orientation) - : new Pair<>(orientation, firstOrientation); - Pair<Integer, Integer> rotatedPair = new Pair<>( - getRotatedOrientation(pair.first), getRotatedOrientation(pair.second)); - mFoldableOrientationPairs.add(pair); - mFoldableOrientationPairs.add(rotatedPair); - } - } + if (!multiCrop()) { + mDefaultDisplayInfo = new WallpaperDefaultDisplayInfo(); + return; } + mDefaultDisplayInfo = new WallpaperDefaultDisplayInfo(windowManager, resources); } DisplayData getDisplayDataOrCreate(int displayId) { @@ -203,51 +157,21 @@ class WallpaperDisplayHelper { } SparseArray<Point> getDefaultDisplaySizes() { - return mDefaultDisplaySizes; + return mDefaultDisplayInfo.defaultDisplaySizes; } /** Return the number of pixel of the largest dimension of the default display */ int getDefaultDisplayLargestDimension() { + SparseArray<Point> defaultDisplaySizes = mDefaultDisplayInfo.defaultDisplaySizes; int result = -1; - for (int i = 0; i < mDefaultDisplaySizes.size(); i++) { - Point size = mDefaultDisplaySizes.valueAt(i); + for (int i = 0; i < defaultDisplaySizes.size(); i++) { + Point size = defaultDisplaySizes.valueAt(i); result = Math.max(result, Math.max(size.x, size.y)); } return result; } - boolean isFoldable() { - return mIsFoldable; - } - - /** - * Return true if any of the screens of the default display is considered large (DP >= 600) - */ - boolean isLargeScreen() { - return mIsLargeScreen; - } - - /** - * If a given orientation corresponds to an unfolded orientation on foldable, return the - * corresponding folded orientation. Otherwise, return UNKNOWN. Always return UNKNOWN if the - * device is not a foldable. - */ - int getFoldedOrientation(int orientation) { - for (Pair<Integer, Integer> pair : mFoldableOrientationPairs) { - if (pair.second.equals(orientation)) return pair.first; - } - return ORIENTATION_UNKNOWN; - } - - /** - * If a given orientation corresponds to a folded orientation on foldable, return the - * corresponding unfolded orientation. Otherwise, return UNKNOWN. Always return UNKNOWN if the - * device is not a foldable. - */ - int getUnfoldedOrientation(int orientation) { - for (Pair<Integer, Integer> pair : mFoldableOrientationPairs) { - if (pair.first.equals(orientation)) return pair.second; - } - return ORIENTATION_UNKNOWN; + public WallpaperDefaultDisplayInfo getDefaultDisplayInfo() { + return mDefaultDisplayInfo; } } diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index bac732637d8d..e7da33d50b27 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -1666,12 +1666,9 @@ public class WallpaperManagerService extends IWallpaperManager.Stub DisplayManager displayManager = mContext.getSystemService(DisplayManager.class); displayManager.registerDisplayListener(mDisplayListener, null /* handler */); WindowManager windowManager = mContext.getSystemService(WindowManager.class); - boolean isFoldable = mContext.getResources() - .getIntArray(R.array.config_foldedDeviceStates).length > 0; mWallpaperDisplayHelper = new WallpaperDisplayHelper( - displayManager, windowManager, mWindowManagerInternal, isFoldable); + displayManager, windowManager, mWindowManagerInternal, mContext.getResources()); mWallpaperCropper = new WallpaperCropper(mWallpaperDisplayHelper); - mWindowManagerInternal.setWallpaperCropUtils(mWallpaperCropper::getCrop); mActivityManager = mContext.getSystemService(ActivityManager.class); if (mContext.getResources().getBoolean( @@ -2510,9 +2507,11 @@ public class WallpaperManagerService extends IWallpaperManager.Stub List<Rect> result = new ArrayList<>(); boolean rtl = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()) == View.LAYOUT_DIRECTION_RTL; + WallpaperDefaultDisplayInfo defaultDisplayInfo = + mWallpaperDisplayHelper.getDefaultDisplayInfo(); for (Point displaySize : displaySizes) { - result.add(mWallpaperCropper.getCrop( - displaySize, croppedBitmapSize, adjustedRelativeSuggestedCrops, rtl)); + result.add(WallpaperCropper.getCrop(displaySize, defaultDisplayInfo, + croppedBitmapSize, adjustedRelativeSuggestedCrops, rtl)); } if (originalBitmap) result = WallpaperCropper.getOriginalCropHints(wallpaper, result); return result; @@ -2548,8 +2547,11 @@ public class WallpaperManagerService extends IWallpaperManager.Stub List<Rect> result = new ArrayList<>(); boolean rtl = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()) == View.LAYOUT_DIRECTION_RTL; + WallpaperDefaultDisplayInfo defaultDisplayInfo = + mWallpaperDisplayHelper.getDefaultDisplayInfo(); for (Point displaySize : displaySizes) { - result.add(mWallpaperCropper.getCrop(displaySize, bitmapSize, defaultCrops, rtl)); + result.add(WallpaperCropper.getCrop(displaySize, defaultDisplayInfo, bitmapSize, + defaultCrops, rtl)); } return result; } diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java index a94183849bc5..e2b47b92f232 100644 --- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java +++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java @@ -112,7 +112,6 @@ import com.android.server.FgThread; import com.android.server.LocalServices; import com.android.server.apphibernation.AppHibernationManagerInternal; import com.android.server.apphibernation.AppHibernationService; -import com.android.window.flags.Flags; import java.util.ArrayList; import java.util.concurrent.TimeUnit; @@ -807,14 +806,8 @@ class ActivityMetricsLogger { } final Task otherTask = otherInfo.mLastLaunchedActivity.getTask(); // The adjacent task is the split root in which activities are started - final boolean isDescendantOfAdjacent; - if (Flags.allowMultipleAdjacentTaskFragments()) { - isDescendantOfAdjacent = launchedSplitRootTask.forOtherAdjacentTasks( - otherTask::isDescendantOf); - } else { - isDescendantOfAdjacent = otherTask.isDescendantOf( - launchedSplitRootTask.getAdjacentTask()); - } + final boolean isDescendantOfAdjacent = launchedSplitRootTask.forOtherAdjacentTasks( + otherTask::isDescendantOf); if (isDescendantOfAdjacent) { if (DEBUG_METRICS) { Slog.i(TAG, "Found adjacent tasks t1=" + launchedActivityTask.mTaskId diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 3cd4db7d8dfc..e91d88901751 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -1716,6 +1716,7 @@ final class ActivityRecord extends WindowToken { } mAppCompatController.getLetterboxPolicy().onMovedToDisplay(mDisplayContent.getDisplayId()); + mAppCompatController.getDisplayCompatModePolicy().onMovedToDisplay(); } void layoutLetterboxIfNeeded(WindowState winHint) { @@ -3801,19 +3802,10 @@ final class ActivityRecord extends WindowToken { final TaskFragment taskFragment = getTaskFragment(); if (next != null && taskFragment != null && taskFragment.isEmbedded()) { final TaskFragment organized = taskFragment.getOrganizedTaskFragment(); - if (Flags.allowMultipleAdjacentTaskFragments()) { - delayRemoval = organized != null - && organized.topRunningActivity() == null - && organized.isDelayLastActivityRemoval() - && organized.forOtherAdjacentTaskFragments(next::isDescendantOf); - } else { - final TaskFragment adjacent = - organized != null ? organized.getAdjacentTaskFragment() : null; - if (adjacent != null && next.isDescendantOf(adjacent) - && organized.topRunningActivity() == null) { - delayRemoval = organized.isDelayLastActivityRemoval(); - } - } + delayRemoval = organized != null + && organized.topRunningActivity() == null + && organized.isDelayLastActivityRemoval() + && organized.forOtherAdjacentTaskFragments(next::isDescendantOf); } // isNextNotYetVisible is to check if the next activity is invisible, or it has been @@ -4787,11 +4779,6 @@ final class ActivityRecord extends WindowToken { } // Make sure the embedded adjacent can also be shown. - if (!Flags.allowMultipleAdjacentTaskFragments()) { - final ActivityRecord adjacentActivity = taskFragment.getAdjacentTaskFragment() - .getTopNonFinishingActivity(); - return canShowWhenLocked(adjacentActivity); - } final boolean hasAdjacentNotAllowToShow = taskFragment.forOtherAdjacentTaskFragments( adjacentTF -> !canShowWhenLocked(adjacentTF.getTopNonFinishingActivity())); return !hasAdjacentNotAllowToShow; @@ -8980,6 +8967,7 @@ final class ActivityRecord extends WindowToken { // Reset the existing override configuration so it can be updated according to the latest // configuration. mAppCompatController.getSizeCompatModePolicy().clearSizeCompatMode(); + mAppCompatController.getDisplayCompatModePolicy().onProcessRestarted(); if (!attachedToProcess()) { return; diff --git a/services/core/java/com/android/server/wm/ActivitySnapshotController.java b/services/core/java/com/android/server/wm/ActivitySnapshotController.java index 21628341ea62..0f1939bfbb49 100644 --- a/services/core/java/com/android/server/wm/ActivitySnapshotController.java +++ b/services/core/java/com/android/server/wm/ActivitySnapshotController.java @@ -34,7 +34,6 @@ import android.window.TaskSnapshot; import com.android.internal.annotations.VisibleForTesting; import com.android.server.wm.BaseAppSnapshotPersister.PersistInfoProvider; -import com.android.window.flags.Flags; import java.io.File; import java.io.PrintWriter; @@ -107,8 +106,7 @@ class ActivitySnapshotController extends AbsAppSnapshotController<ActivityRecord && !ActivityManager.isLowRamDeviceStatic(); // Don't support Android Go setSnapshotEnabled(snapshotEnabled); mSnapshotPersistQueue = persistQueue; - mPersistInfoProvider = createPersistInfoProvider(service, - Environment::getDataSystemCeDirectory); + mPersistInfoProvider = createPersistInfoProvider(service); mPersister = new TaskSnapshotPersister( persistQueue, mPersistInfoProvider, @@ -117,6 +115,11 @@ class ActivitySnapshotController extends AbsAppSnapshotController<ActivityRecord initialize(new ActivitySnapshotCache()); } + @VisibleForTesting + PersistInfoProvider createPersistInfoProvider(WindowManagerService service) { + return createPersistInfoProvider(service, Environment::getDataSystemCeDirectory); + } + @Override protected float initSnapshotScale() { final float config = mService.mContext.getResources().getFloat( @@ -528,26 +531,6 @@ class ActivitySnapshotController extends AbsAppSnapshotController<ActivityRecord final int currentIndex = currTF.asTask() != null ? currentTask.mChildren.indexOf(currentActivity) : currentTask.mChildren.indexOf(currTF); - if (!Flags.allowMultipleAdjacentTaskFragments()) { - final int prevAdjacentIndex = currentTask.mChildren.indexOf( - prevTF.getAdjacentTaskFragment()); - if (prevAdjacentIndex > currentIndex) { - // PrevAdjacentTF already above currentActivity - return; - } - // Add both the one below, and its adjacent. - if (!inTransition || isInParticipant(initPrev, mTmpTransitionParticipants)) { - result.add(initPrev); - } - final ActivityRecord prevAdjacentActivity = prevTF.getAdjacentTaskFragment() - .getTopMostActivity(); - if (prevAdjacentActivity != null && (!inTransition - || isInParticipant(prevAdjacentActivity, mTmpTransitionParticipants))) { - result.add(prevAdjacentActivity); - } - return; - } - final boolean hasAdjacentAboveCurrent = prevTF.forOtherAdjacentTaskFragments( prevAdjacentTF -> { final int prevAdjacentIndex = currentTask.mChildren.indexOf(prevAdjacentTF); diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java index a7f2153993bb..b0563128870a 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java @@ -164,7 +164,6 @@ import com.android.server.companion.virtual.VirtualDeviceManagerInternal; import com.android.server.pm.SaferIntentUtils; import com.android.server.utils.Slogf; import com.android.server.wm.ActivityMetricsLogger.LaunchingState; -import com.android.window.flags.Flags; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -2991,17 +2990,9 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { if (child.asTaskFragment() != null && child.asTaskFragment().hasAdjacentTaskFragment()) { - final boolean isAnyTranslucent; - if (Flags.allowMultipleAdjacentTaskFragments()) { - final TaskFragment.AdjacentSet set = - child.asTaskFragment().getAdjacentTaskFragments(); - isAnyTranslucent = set.forAllTaskFragments( - tf -> !isOpaque(tf), null); - } else { - final TaskFragment adjacent = child.asTaskFragment() - .getAdjacentTaskFragment(); - isAnyTranslucent = !isOpaque(child) || !isOpaque(adjacent); - } + final boolean isAnyTranslucent = !isOpaque(child) + || child.asTaskFragment().forOtherAdjacentTaskFragments( + tf -> !isOpaque(tf)); if (!isAnyTranslucent) { // This task fragment and all its adjacent task fragments are opaque, // consider it opaque even if it doesn't fill its parent. diff --git a/services/core/java/com/android/server/wm/AppCompatController.java b/services/core/java/com/android/server/wm/AppCompatController.java index 48f08e945a59..c479591a5e0d 100644 --- a/services/core/java/com/android/server/wm/AppCompatController.java +++ b/services/core/java/com/android/server/wm/AppCompatController.java @@ -46,6 +46,8 @@ class AppCompatController { private final AppCompatSizeCompatModePolicy mSizeCompatModePolicy; @NonNull private final AppCompatSandboxingPolicy mSandboxingPolicy; + @NonNull + private final AppCompatDisplayCompatModePolicy mDisplayCompatModePolicy; AppCompatController(@NonNull WindowManagerService wmService, @NonNull ActivityRecord activityRecord) { @@ -69,6 +71,7 @@ class AppCompatController { mSizeCompatModePolicy = new AppCompatSizeCompatModePolicy(activityRecord, mAppCompatOverrides); mSandboxingPolicy = new AppCompatSandboxingPolicy(activityRecord); + mDisplayCompatModePolicy = new AppCompatDisplayCompatModePolicy(); } @NonNull @@ -151,6 +154,11 @@ class AppCompatController { return mSandboxingPolicy; } + @NonNull + AppCompatDisplayCompatModePolicy getDisplayCompatModePolicy() { + return mDisplayCompatModePolicy; + } + void dump(@NonNull PrintWriter pw, @NonNull String prefix) { getTransparentPolicy().dump(pw, prefix); getLetterboxPolicy().dump(pw, prefix); diff --git a/services/core/java/com/android/server/wm/AppCompatDisplayCompatModePolicy.java b/services/core/java/com/android/server/wm/AppCompatDisplayCompatModePolicy.java new file mode 100644 index 000000000000..acf51707c894 --- /dev/null +++ b/services/core/java/com/android/server/wm/AppCompatDisplayCompatModePolicy.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import com.android.window.flags.Flags; + +/** + * Encapsulate app-compat logic for multi-display environments. + */ +class AppCompatDisplayCompatModePolicy { + + private boolean mIsRestartMenuEnabledForDisplayMove; + + boolean isRestartMenuEnabledForDisplayMove() { + return Flags.enableRestartMenuForConnectedDisplays() && mIsRestartMenuEnabledForDisplayMove; + } + + void onMovedToDisplay() { + mIsRestartMenuEnabledForDisplayMove = true; + } + + void onProcessRestarted() { + mIsRestartMenuEnabledForDisplayMove = false; + } +} diff --git a/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java b/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java index b03aa5263927..0f1e36d70db2 100644 --- a/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java +++ b/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java @@ -361,7 +361,10 @@ class AppCompatSizeCompatModePolicy { if (enableSizeCompatModeImprovementsForConnectedDisplays()) { overrideConfig.touchscreen = fullConfig.touchscreen; overrideConfig.navigation = fullConfig.navigation; - overrideConfig.fontScale = fullConfig.fontScale; + overrideConfig.keyboard = fullConfig.keyboard; + overrideConfig.keyboardHidden = fullConfig.keyboardHidden; + overrideConfig.hardKeyboardHidden = fullConfig.hardKeyboardHidden; + overrideConfig.navigationHidden = fullConfig.navigationHidden; } // The smallest screen width is the short side of screen bounds. Because the bounds // and density won't be changed, smallestScreenWidthDp is also fixed. diff --git a/services/core/java/com/android/server/wm/AppCompatUtils.java b/services/core/java/com/android/server/wm/AppCompatUtils.java index 146044008b3f..b91a12598e01 100644 --- a/services/core/java/com/android/server/wm/AppCompatUtils.java +++ b/services/core/java/com/android/server/wm/AppCompatUtils.java @@ -161,6 +161,9 @@ final class AppCompatUtils { top.mAppCompatController.getLetterboxOverrides() .isLetterboxEducationEnabled()); + appCompatTaskInfo.setRestartMenuEnabledForDisplayMove(top.mAppCompatController + .getDisplayCompatModePolicy().isRestartMenuEnabledForDisplayMove()); + final AppCompatAspectRatioOverrides aspectRatioOverrides = top.mAppCompatController.getAspectRatioOverrides(); appCompatTaskInfo.setUserFullscreenOverrideEnabled( diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java index e9b7649e8cbd..dfe323c43abb 100644 --- a/services/core/java/com/android/server/wm/BackNavigationController.java +++ b/services/core/java/com/android/server/wm/BackNavigationController.java @@ -479,21 +479,16 @@ class BackNavigationController { } } else { // If adjacent TF has companion to current TF, those two TF will be closed together. - final TaskFragment adjacentTF; - if (Flags.allowMultipleAdjacentTaskFragments()) { - if (currTF.getAdjacentTaskFragments().size() > 2) { - throw new IllegalStateException( - "Not yet support 3+ adjacent for non-Task TFs"); - } - final TaskFragment[] tmpAdjacent = new TaskFragment[1]; - currTF.forOtherAdjacentTaskFragments(tf -> { - tmpAdjacent[0] = tf; - return true; - }); - adjacentTF = tmpAdjacent[0]; - } else { - adjacentTF = currTF.getAdjacentTaskFragment(); + if (currTF.getAdjacentTaskFragments().size() > 2) { + throw new IllegalStateException( + "Not yet support 3+ adjacent for non-Task TFs"); } + final TaskFragment[] tmpAdjacent = new TaskFragment[1]; + currTF.forOtherAdjacentTaskFragments(tf -> { + tmpAdjacent[0] = tf; + return true; + }); + final TaskFragment adjacentTF = tmpAdjacent[0]; if (isSecondCompanionToFirst(currTF, adjacentTF)) { // The two TFs are adjacent (visually displayed side-by-side), search if any // activity below the lowest one. @@ -553,15 +548,6 @@ class BackNavigationController { if (!prevTF.hasAdjacentTaskFragment()) { return; } - if (!Flags.allowMultipleAdjacentTaskFragments()) { - final TaskFragment prevTFAdjacent = prevTF.getAdjacentTaskFragment(); - final ActivityRecord prevActivityAdjacent = - prevTFAdjacent.getTopNonFinishingActivity(); - if (prevActivityAdjacent != null) { - outPrevActivities.add(prevActivityAdjacent); - } - return; - } prevTF.forOtherAdjacentTaskFragments(prevTFAdjacent -> { final ActivityRecord prevActivityAdjacent = prevTFAdjacent.getTopNonFinishingActivity(); @@ -577,14 +563,6 @@ class BackNavigationController { if (mainTF == null || !mainTF.hasAdjacentTaskFragment()) { return; } - if (!Flags.allowMultipleAdjacentTaskFragments()) { - final TaskFragment adjacentTF = mainTF.getAdjacentTaskFragment(); - final ActivityRecord topActivity = adjacentTF.getTopNonFinishingActivity(); - if (topActivity != null) { - outList.add(topActivity); - } - return; - } mainTF.forOtherAdjacentTaskFragments(adjacentTF -> { final ActivityRecord topActivity = adjacentTF.getTopNonFinishingActivity(); if (topActivity != null) { diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java index 2287a687700c..f50a68cc5389 100644 --- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java +++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java @@ -49,8 +49,8 @@ import static com.android.window.flags.Flags.balDontBringExistingBackgroundTaskS import static com.android.window.flags.Flags.balImprovedMetrics; import static com.android.window.flags.Flags.balRequireOptInByPendingIntentCreator; import static com.android.window.flags.Flags.balShowToastsBlocked; -import static com.android.window.flags.Flags.balStrictModeRo; import static com.android.window.flags.Flags.balStrictModeGracePeriod; +import static com.android.window.flags.Flags.balStrictModeRo; import static java.lang.annotation.RetentionPolicy.SOURCE; import static java.util.Objects.requireNonNull; @@ -91,7 +91,6 @@ import com.android.internal.util.Preconditions; import com.android.server.UiThread; import com.android.server.am.PendingIntentRecord; import com.android.server.wm.BackgroundLaunchProcessController.BalCheckConfiguration; -import com.android.window.flags.Flags; import java.lang.annotation.Retention; import java.util.ArrayList; @@ -1687,14 +1686,6 @@ public class BackgroundActivityStartController { } // Check the adjacent fragment. - if (!Flags.allowMultipleAdjacentTaskFragments()) { - TaskFragment adjacentTaskFragment = taskFragment.getAdjacentTaskFragment(); - topActivity = adjacentTaskFragment.getActivity(topOfStackPredicate); - if (topActivity == null) { - return bas; - } - return checkCrossUidActivitySwitchFromBelow(topActivity, uid, bas); - } final BlockActivityStart[] out = { bas }; taskFragment.forOtherAdjacentTaskFragments(adjacentTaskFragment -> { final ActivityRecord top = adjacentTaskFragment.getActivity(topOfStackPredicate); diff --git a/services/core/java/com/android/server/wm/BaseAppSnapshotPersister.java b/services/core/java/com/android/server/wm/BaseAppSnapshotPersister.java index 5db02dff8351..aaa5a0074506 100644 --- a/services/core/java/com/android/server/wm/BaseAppSnapshotPersister.java +++ b/services/core/java/com/android/server/wm/BaseAppSnapshotPersister.java @@ -16,10 +16,20 @@ package com.android.server.wm; +import static com.android.server.wm.AbsAppSnapshotController.TAG; + import android.annotation.NonNull; +import android.annotation.Nullable; +import android.util.Slog; +import android.util.SparseArray; +import android.util.SparseBooleanArray; import android.window.TaskSnapshot; +import com.android.internal.annotations.VisibleForTesting; +import com.android.window.flags.Flags; + import java.io.File; +import java.util.UUID; class BaseAppSnapshotPersister { static final String LOW_RES_FILE_POSTFIX = "_reduced"; @@ -29,6 +39,7 @@ class BaseAppSnapshotPersister { // Shared with SnapshotPersistQueue protected final Object mLock; protected final SnapshotPersistQueue mSnapshotPersistQueue; + @VisibleForTesting protected final PersistInfoProvider mPersistInfoProvider; BaseAppSnapshotPersister(SnapshotPersistQueue persistQueue, @@ -79,6 +90,8 @@ class BaseAppSnapshotPersister { private final boolean mEnableLowResSnapshots; private final float mLowResScaleFactor; private final boolean mUse16BitFormat; + private final SparseBooleanArray mInitializedUsers = new SparseBooleanArray(); + private final SparseArray<File> mScrambleDirectories = new SparseArray<>(); PersistInfoProvider(DirectoryResolver directoryResolver, String dirName, boolean enableLowResSnapshots, float lowResScaleFactor, boolean use16BitFormat) { @@ -91,9 +104,80 @@ class BaseAppSnapshotPersister { @NonNull File getDirectory(int userId) { + if (Flags.scrambleSnapshotFileName()) { + final File directory = getOrInitScrambleDirectory(userId); + if (directory != null) { + return directory; + } + } + return getBaseDirectory(userId); + } + + @NonNull + private File getBaseDirectory(int userId) { return new File(mDirectoryResolver.getSystemDirectoryForUser(userId), mDirName); } + @Nullable + private File getOrInitScrambleDirectory(int userId) { + synchronized (mScrambleDirectories) { + if (mInitializedUsers.get(userId)) { + return mScrambleDirectories.get(userId); + } + mInitializedUsers.put(userId, true); + final File scrambledDirectory = getScrambleDirectory(userId); + final File baseDir = getBaseDirectory(userId); + String newName = null; + // If directory exists, rename + if (scrambledDirectory.exists()) { + newName = UUID.randomUUID().toString(); + final File scrambleTo = new File(baseDir, newName); + if (!scrambledDirectory.renameTo(scrambleTo)) { + Slog.w(TAG, "SnapshotPersister rename scramble folder fail."); + return null; + } + } else { + // If directory not exists, mkDir. + if (!baseDir.exists() && !baseDir.mkdir()) { + Slog.w(TAG, "SnapshotPersister make base folder fail."); + return null; + } + if (!scrambledDirectory.mkdir()) { + Slog.e(TAG, "SnapshotPersister make scramble folder fail"); + return null; + } + // Move any existing files to this folder. + final String[] files = baseDir.list(); + if (files != null) { + for (String file : files) { + final File original = new File(baseDir, file); + if (original.isDirectory()) { + newName = file; + } else { + File to = new File(scrambledDirectory, file); + original.renameTo(to); + } + } + } + } + final File newFolder = new File(baseDir, newName); + mScrambleDirectories.put(userId, newFolder); + return newFolder; + } + } + + @NonNull + private File getScrambleDirectory(int userId) { + final File dir = getBaseDirectory(userId); + final String[] directories = dir.list( + (current, name) -> new File(current, name).isDirectory()); + if (directories != null && directories.length > 0) { + return new File(dir, directories[0]); + } else { + return new File(dir, UUID.randomUUID().toString()); + } + } + /** * Return if task snapshots are stored in 16 bit pixel format. * diff --git a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java index f473b7b7e4fb..fcc697242ff6 100644 --- a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java +++ b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java @@ -18,7 +18,7 @@ package com.android.server.wm; import static android.view.WindowManager.TRANSIT_CHANGE; -import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS; +import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS_MIN; import static com.android.server.wm.ActivityTaskManagerService.POWER_MODE_REASON_CHANGE_DISPLAY; import static com.android.server.wm.utils.DisplayInfoOverrides.WM_OVERRIDE_FIELDS; import static com.android.server.wm.utils.DisplayInfoOverrides.copyDisplayInfoFields; @@ -140,8 +140,9 @@ class DeferredDisplayUpdater { if (displayInfoDiff == DIFF_EVERYTHING || !mDisplayContent.getLastHasContent() || !mDisplayContent.mTransitionController.isShellTransitionsEnabled()) { - ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS, - "DeferredDisplayUpdater: applying DisplayInfo immediately"); + ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS_MIN, + "DeferredDisplayUpdater: applying DisplayInfo(%d x %d) immediately", + displayInfo.logicalWidth, displayInfo.logicalHeight); mLastWmDisplayInfo = displayInfo; applyLatestDisplayInfo(); @@ -151,17 +152,23 @@ class DeferredDisplayUpdater { // If there are non WM-specific display info changes, apply only these fields immediately if ((displayInfoDiff & DIFF_NOT_WM_DEFERRABLE) > 0) { - ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS, - "DeferredDisplayUpdater: partially applying DisplayInfo immediately"); + ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS_MIN, + "DeferredDisplayUpdater: partially applying DisplayInfo(%d x %d) immediately", + displayInfo.logicalWidth, displayInfo.logicalHeight); applyLatestDisplayInfo(); } // If there are WM-specific display info changes, apply them through a Shell transition if ((displayInfoDiff & DIFF_WM_DEFERRABLE) > 0) { - ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS, - "DeferredDisplayUpdater: deferring DisplayInfo update"); + ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS_MIN, + "DeferredDisplayUpdater: deferring DisplayInfo(%d x %d) update", + displayInfo.logicalWidth, displayInfo.logicalHeight); requestDisplayChangeTransition(physicalDisplayUpdated, () -> { + ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS_MIN, + "DeferredDisplayUpdater: applying DisplayInfo(%d x %d) after deferring", + displayInfo.logicalWidth, displayInfo.logicalHeight); + // Apply deferrable fields to DisplayContent only when the transition // starts collecting, non-deferrable fields are ignored in mLastWmDisplayInfo mLastWmDisplayInfo = displayInfo; @@ -199,7 +206,7 @@ class DeferredDisplayUpdater { mDisplayContent.getDisplayPolicy().getNotificationShade(); if (notificationShade != null && notificationShade.isVisible() && mDisplayContent.mAtmService.mKeyguardController.isKeyguardOrAodShowing( - mDisplayContent.mDisplayId)) { + mDisplayContent.mDisplayId)) { Slog.i(TAG, notificationShade + " uses blast for display switch"); notificationShade.mSyncMethodOverride = BLASTSyncEngine.METHOD_BLAST; } @@ -209,9 +216,6 @@ class DeferredDisplayUpdater { try { onStartCollect.run(); - ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS, - "DeferredDisplayUpdater: applied DisplayInfo after deferring"); - if (physicalDisplayUpdated) { onDisplayUpdated(transition, fromRotation, startBounds); } else { diff --git a/services/core/java/com/android/server/wm/DesktopModeHelper.java b/services/core/java/com/android/server/wm/DesktopModeHelper.java index dc42b32967e2..d91fca9e2816 100644 --- a/services/core/java/com/android/server/wm/DesktopModeHelper.java +++ b/services/core/java/com/android/server/wm/DesktopModeHelper.java @@ -17,6 +17,7 @@ package com.android.server.wm; import static android.app.Flags.enableConnectedDisplaysWallpaper; +import static android.window.DesktopExperienceFlags.ENABLE_PROJECTED_DISPLAY_DESKTOP_MODE; import android.annotation.NonNull; import android.content.Context; @@ -66,7 +67,7 @@ public final class DesktopModeHelper { * Return {@code true} if the current device can hosts desktop sessions on its internal display. */ @VisibleForTesting - static boolean canInternalDisplayHostDesktops(@NonNull Context context) { + private static boolean canInternalDisplayHostDesktops(@NonNull Context context) { return context.getResources().getBoolean(R.bool.config_canInternalDisplayHostDesktops); } @@ -83,8 +84,11 @@ public final class DesktopModeHelper { if (!shouldEnforceDeviceRestrictions()) { return true; } - final boolean desktopModeSupported = isDesktopModeSupported(context) - && canInternalDisplayHostDesktops(context); + // If projected display is enabled, #canInternalDisplayHostDesktops is no longer a + // requirement. + final boolean desktopModeSupported = ENABLE_PROJECTED_DISPLAY_DESKTOP_MODE.isTrue() + ? isDesktopModeSupported(context) : (isDesktopModeSupported(context) + && canInternalDisplayHostDesktops(context)); final boolean desktopModeSupportedByDevOptions = Flags.enableDesktopModeThroughDevOption() && isDesktopModeDevOptionsSupported(context); diff --git a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java index d466a646c7dd..ddcb5eccb1d8 100644 --- a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java +++ b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java @@ -19,6 +19,12 @@ package com.android.server.wm; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK; +import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; +import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; +import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE; +import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE_PER_TASK; +import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK; import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM; import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME; @@ -131,6 +137,18 @@ class DesktopModeLaunchParamsModifier implements LaunchParamsModifier { return RESULT_SKIP; } + if (DesktopModeFlags.INHERIT_TASK_BOUNDS_FOR_TRAMPOLINE_TASK_LAUNCHES.isTrue()) { + ActivityRecord topVisibleFreeformActivity = + task.getDisplayContent().getTopMostVisibleFreeformActivity(); + if (shouldInheritExistingTaskBounds(topVisibleFreeformActivity, activity, task)) { + appendLog("inheriting bounds from existing closing instance"); + outParams.mBounds.set(topVisibleFreeformActivity.getBounds()); + appendLog("final desktop mode task bounds set to %s", outParams.mBounds); + // Return result done to prevent other modifiers from changing or cascading bounds. + return RESULT_DONE; + } + } + DesktopModeBoundsCalculator.updateInitialBounds(task, layout, activity, options, outParams.mBounds, this::appendLog); appendLog("final desktop mode task bounds set to %s", outParams.mBounds); @@ -159,7 +177,7 @@ class DesktopModeLaunchParamsModifier implements LaunchParamsModifier { // activity will also enter desktop mode. On this same relationship, we can also assume // if there are not visible freeform tasks but a freeform activity is now launching, it // will force the device into desktop mode. - return (task.getDisplayContent().getTopMostVisibleFreeformActivity() != null + return (task.getDisplayContent().getTopMostFreeformActivity() != null && checkSourceWindowModesCompatible(task, options, currentParams)) || isRequestingFreeformWindowMode(task, options, currentParams); } @@ -201,6 +219,40 @@ class DesktopModeLaunchParamsModifier implements LaunchParamsModifier { }; } + /** + * Whether the launching task should inherit the task bounds of an existing closing instance. + */ + private boolean shouldInheritExistingTaskBounds( + @Nullable ActivityRecord existingTaskActivity, + @Nullable ActivityRecord launchingActivity, + @NonNull Task launchingTask) { + if (existingTaskActivity == null || launchingActivity == null) return false; + return (existingTaskActivity.packageName == launchingActivity.packageName) + && isLaunchingNewTask(launchingActivity.launchMode, + launchingTask.getBaseIntent().getFlags()) + && isClosingExitingInstance(launchingTask.getBaseIntent().getFlags()); + } + + /** + * Returns true if the launch mode or intent will result in a new task being created for the + * activity. + */ + private boolean isLaunchingNewTask(int launchMode, int intentFlags) { + return launchMode == LAUNCH_SINGLE_TASK + || launchMode == LAUNCH_SINGLE_INSTANCE + || launchMode == LAUNCH_SINGLE_INSTANCE_PER_TASK + || (intentFlags & FLAG_ACTIVITY_NEW_TASK) != 0; + } + + /** + * Returns true if the intent will result in an existing task instance being closed if a new + * one appears. + */ + private boolean isClosingExitingInstance(int intentFlags) { + return (intentFlags & FLAG_ACTIVITY_CLEAR_TASK) != 0 + || (intentFlags & FLAG_ACTIVITY_MULTIPLE_TASK) == 0; + } + private void initLogBuilder(Task task, ActivityRecord activity) { if (DEBUG) { mLogBuilder = new StringBuilder( diff --git a/services/core/java/com/android/server/wm/DisplayWindowListenerController.java b/services/core/java/com/android/server/wm/DisplayWindowListenerController.java index fa7a99d55896..d90fff229cd9 100644 --- a/services/core/java/com/android/server/wm/DisplayWindowListenerController.java +++ b/services/core/java/com/android/server/wm/DisplayWindowListenerController.java @@ -40,14 +40,14 @@ class DisplayWindowListenerController { } int[] registerListener(IDisplayWindowListener listener) { + mDisplayListeners.register(listener); + final IntArray displayIds = new IntArray(); synchronized (mService.mGlobalLock) { - mDisplayListeners.register(listener); - final IntArray displayIds = new IntArray(); mService.mAtmService.mRootWindowContainer.forAllDisplays((displayContent) -> { displayIds.add(displayContent.mDisplayId); }); - return displayIds.toArray(); } + return displayIds.toArray(); } void unregisterListener(IDisplayWindowListener listener) { diff --git a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java index a017a1173d97..e508a6d23178 100644 --- a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java +++ b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java @@ -23,8 +23,6 @@ import static com.android.server.wm.Task.TAG_VISIBILITY; import android.annotation.Nullable; import android.util.Slog; -import com.android.window.flags.Flags; - import java.util.ArrayList; /** Helper class to ensure activities are in the right visible state for a container. */ @@ -112,18 +110,11 @@ class EnsureActivitiesVisibleHelper { if (adjacentTaskFragments != null && adjacentTaskFragments.contains( childTaskFragment)) { - final boolean isTranslucent; - if (Flags.allowMultipleAdjacentTaskFragments()) { - isTranslucent = childTaskFragment.isTranslucent(starting) - || childTaskFragment.forOtherAdjacentTaskFragments( - adjacentTaskFragment -> { - return adjacentTaskFragment.isTranslucent(starting); - }); - } else { - isTranslucent = childTaskFragment.isTranslucent(starting) - || childTaskFragment.getAdjacentTaskFragment() - .isTranslucent(starting); - } + final boolean isTranslucent = childTaskFragment.isTranslucent(starting) + || childTaskFragment.forOtherAdjacentTaskFragments( + adjacentTaskFragment -> { + return adjacentTaskFragment.isTranslucent(starting); + }); if (!isTranslucent) { // Everything behind two adjacent TaskFragments are occluded. mBehindFullyOccludedContainer = true; @@ -135,14 +126,10 @@ class EnsureActivitiesVisibleHelper { if (adjacentTaskFragments == null) { adjacentTaskFragments = new ArrayList<>(); } - if (Flags.allowMultipleAdjacentTaskFragments()) { - final ArrayList<TaskFragment> adjacentTfs = adjacentTaskFragments; - childTaskFragment.forOtherAdjacentTaskFragments(adjacentTf -> { - adjacentTfs.add(adjacentTf); - }); - } else { - adjacentTaskFragments.add(childTaskFragment.getAdjacentTaskFragment()); - } + final ArrayList<TaskFragment> adjacentTfs = adjacentTaskFragments; + childTaskFragment.forOtherAdjacentTaskFragments(adjacentTf -> { + adjacentTfs.add(adjacentTf); + }); } } else if (child.asActivityRecord() != null) { setActivityVisibilityState(child.asActivityRecord(), starting, resumeTopActivity); diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java index 2cac63c1e5e9..a937691e7998 100644 --- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java @@ -263,8 +263,8 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { boolean oldVisibility = mSource.isVisible(); super.updateVisibility(); if (Flags.refactorInsetsController()) { - if (mSource.isVisible() && !oldVisibility && mImeRequester != null) { - reportImeDrawnForOrganizerIfNeeded(mImeRequester); + if (mSource.isVisible() && !oldVisibility && mControlTarget != null) { + reportImeDrawnForOrganizerIfNeeded(mControlTarget); } } onSourceChanged(); diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java index 7751ac3f9fc6..a4bc5cbcb5d3 100644 --- a/services/core/java/com/android/server/wm/InputManagerCallback.java +++ b/services/core/java/com/android/server/wm/InputManagerCallback.java @@ -343,6 +343,13 @@ final class InputManagerCallback implements InputManagerService.WindowManagerCal } } + @Override + public boolean isKeyguardLocked(int displayId) { + synchronized (mService.mGlobalLock) { + return mService.mAtmService.mKeyguardController.isKeyguardLocked(displayId); + } + } + /** Waits until the built-in input devices have been configured. */ public boolean waitForInputDevicesReady(long timeoutMillis) { synchronized (mInputDevicesReadyMonitor) { diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index cf201c9f34f0..609302ce3f56 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -1732,26 +1732,14 @@ class RootWindowContainer extends WindowContainer<DisplayContent> activityAssistInfos.clear(); activityAssistInfos.add(new ActivityAssistInfo(top)); // Check if the activity on the split screen. - if (Flags.allowMultipleAdjacentTaskFragments()) { - top.getTask().forOtherAdjacentTasks(task -> { - final ActivityRecord adjacentActivityRecord = - task.getTopNonFinishingActivity(); - if (adjacentActivityRecord != null) { - activityAssistInfos.add( - new ActivityAssistInfo(adjacentActivityRecord)); - } - }); - } else { - final Task adjacentTask = top.getTask().getAdjacentTask(); - if (adjacentTask != null) { - final ActivityRecord adjacentActivityRecord = - adjacentTask.getTopNonFinishingActivity(); - if (adjacentActivityRecord != null) { - activityAssistInfos.add( - new ActivityAssistInfo(adjacentActivityRecord)); - } + top.getTask().forOtherAdjacentTasks(task -> { + final ActivityRecord adjacentActivityRecord = + task.getTopNonFinishingActivity(); + if (adjacentActivityRecord != null) { + activityAssistInfos.add( + new ActivityAssistInfo(adjacentActivityRecord)); } - } + }); if (rootTask == topFocusedRootTask) { topVisibleActivities.addAll(0, activityAssistInfos); } else { diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index d16c301cec40..e98b2b749af8 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -2461,21 +2461,6 @@ class Task extends TaskFragment { return parentTask == null ? null : parentTask.getCreatedByOrganizerTask(); } - /** @deprecated b/373709676 replace with {@link #forOtherAdjacentTasks(Consumer)} ()}. */ - @Deprecated - @Nullable - Task getAdjacentTask() { - if (Flags.allowMultipleAdjacentTaskFragments()) { - throw new IllegalStateException("allowMultipleAdjacentTaskFragments is enabled. " - + "Use #forOtherAdjacentTasks instead"); - } - final Task taskWithAdjacent = getTaskWithAdjacent(); - if (taskWithAdjacent == null) { - return null; - } - return taskWithAdjacent.getAdjacentTaskFragment().asTask(); - } - /** Finds the first Task parent (or itself) that has adjacent. */ @Nullable Task getTaskWithAdjacent() { @@ -2499,11 +2484,6 @@ class Task extends TaskFragment { * Tasks. The invoke order is not guaranteed. */ void forOtherAdjacentTasks(@NonNull Consumer<Task> callback) { - if (!Flags.allowMultipleAdjacentTaskFragments()) { - throw new IllegalStateException("allowMultipleAdjacentTaskFragments is not enabled. " - + "Use #getAdjacentTask instead"); - } - final Task taskWithAdjacent = getTaskWithAdjacent(); if (taskWithAdjacent == null) { return; @@ -2521,10 +2501,6 @@ class Task extends TaskFragment { * guaranteed. */ boolean forOtherAdjacentTasks(@NonNull Predicate<Task> callback) { - if (!Flags.allowMultipleAdjacentTaskFragments()) { - throw new IllegalStateException("allowMultipleAdjacentTaskFragments is not enabled. " - + "Use getAdjacentTask instead"); - } final Task taskWithAdjacent = getTaskWithAdjacent(); if (taskWithAdjacent == null) { return false; @@ -3651,20 +3627,13 @@ class Task extends TaskFragment { final TaskFragment taskFragment = wc.asTaskFragment(); if (taskFragment != null && taskFragment.isEmbedded() && taskFragment.hasAdjacentTaskFragment()) { - if (Flags.allowMultipleAdjacentTaskFragments()) { - final int[] nextLayer = { layer }; - taskFragment.forOtherAdjacentTaskFragments(adjacentTf -> { - if (adjacentTf.shouldBoostDimmer()) { - adjacentTf.assignLayer(t, nextLayer[0]++); - } - }); - layer = nextLayer[0]; - } else { - final TaskFragment adjacentTf = taskFragment.getAdjacentTaskFragment(); + final int[] nextLayer = { layer }; + taskFragment.forOtherAdjacentTaskFragments(adjacentTf -> { if (adjacentTf.shouldBoostDimmer()) { - adjacentTf.assignLayer(t, layer++); + adjacentTf.assignLayer(t, nextLayer[0]++); } - } + }); + layer = nextLayer[0]; } // Place the decor surface just above the owner TaskFragment. diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java index 1966ecf57c73..fb7bab4b3e26 100644 --- a/services/core/java/com/android/server/wm/TaskDisplayArea.java +++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java @@ -60,7 +60,6 @@ import com.android.internal.util.function.pooled.PooledLambda; import com.android.internal.util.function.pooled.PooledPredicate; import com.android.server.pm.UserManagerInternal; import com.android.server.wm.LaunchParamsController.LaunchParams; -import com.android.window.flags.Flags; import java.io.PrintWriter; import java.util.ArrayList; @@ -1089,19 +1088,14 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { // Use launch-adjacent-flag-root if launching with launch-adjacent flag. if ((launchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0 && mLaunchAdjacentFlagRootTask != null) { - final Task launchAdjacentRootAdjacentTask; - if (Flags.allowMultipleAdjacentTaskFragments()) { - final Task[] tmpTask = new Task[1]; - mLaunchAdjacentFlagRootTask.forOtherAdjacentTasks(task -> { - // TODO(b/382208145): enable FLAG_ACTIVITY_LAUNCH_ADJACENT for 3+. - // Find the first adjacent for now. - tmpTask[0] = task; - return true; - }); - launchAdjacentRootAdjacentTask = tmpTask[0]; - } else { - launchAdjacentRootAdjacentTask = mLaunchAdjacentFlagRootTask.getAdjacentTask(); - } + final Task[] tmpTask = new Task[1]; + mLaunchAdjacentFlagRootTask.forOtherAdjacentTasks(task -> { + // TODO(b/382208145): enable FLAG_ACTIVITY_LAUNCH_ADJACENT for 3+. + // Find the first adjacent for now. + tmpTask[0] = task; + return true; + }); + final Task launchAdjacentRootAdjacentTask = tmpTask[0]; if (sourceTask != null && (sourceTask == candidateTask || sourceTask.topRunningActivity() == null)) { // Do nothing when task that is getting opened is same as the source or when @@ -1129,14 +1123,6 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { if (launchRootTask == null || sourceTask == null) { return launchRootTask; } - if (!Flags.allowMultipleAdjacentTaskFragments()) { - final Task adjacentRootTask = launchRootTask.getAdjacentTask(); - if (adjacentRootTask != null && (sourceTask == adjacentRootTask - || sourceTask.isDescendantOf(adjacentRootTask))) { - return adjacentRootTask; - } - return launchRootTask; - } final Task[] adjacentRootTask = new Task[1]; launchRootTask.forOtherAdjacentTasks(task -> { if (sourceTask == task || sourceTask.isDescendantOf(task)) { @@ -1163,24 +1149,16 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { return sourceTask.getCreatedByOrganizerTask(); } // Check if the candidate is already positioned in the adjacent Task. - if (Flags.allowMultipleAdjacentTaskFragments()) { - final Task[] adjacentRootTask = new Task[1]; - sourceTask.forOtherAdjacentTasks(task -> { - if (candidateTask == task || candidateTask.isDescendantOf(task)) { - adjacentRootTask[0] = task; - return true; - } - return false; - }); - if (adjacentRootTask[0] != null) { - return adjacentRootTask[0]; - } - } else { - final Task adjacentTarget = taskWithAdjacent.getAdjacentTask(); - if (candidateTask == adjacentTarget - || candidateTask.isDescendantOf(adjacentTarget)) { - return adjacentTarget; + final Task[] adjacentRootTask = new Task[1]; + sourceTask.forOtherAdjacentTasks(task -> { + if (candidateTask == task || candidateTask.isDescendantOf(task)) { + adjacentRootTask[0] = task; + return true; } + return false; + }); + if (adjacentRootTask[0] != null) { + return adjacentRootTask[0]; } return sourceTask.getCreatedByOrganizerTask(); } diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 5183c6b57f15..2dabb253744a 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -235,11 +235,6 @@ class TaskFragment extends WindowContainer<WindowContainer> { /** This task fragment will be removed when the cleanup of its children are done. */ private boolean mIsRemovalRequested; - /** @deprecated b/373709676 replace with {@link #mAdjacentTaskFragments} */ - @Deprecated - @Nullable - private TaskFragment mAdjacentTaskFragment; - /** * The TaskFragments that are adjacent to each other, including this TaskFragment. * All TaskFragments in this set share the same set instance. @@ -455,22 +450,6 @@ class TaskFragment extends WindowContainer<WindowContainer> { return service.mWindowOrganizerController.getTaskFragment(token); } - /** @deprecated b/373709676 replace with {@link #setAdjacentTaskFragments}. */ - @Deprecated - void setAdjacentTaskFragment(@NonNull TaskFragment taskFragment) { - if (!Flags.allowMultipleAdjacentTaskFragments()) { - if (mAdjacentTaskFragment == taskFragment) { - return; - } - resetAdjacentTaskFragment(); - mAdjacentTaskFragment = taskFragment; - taskFragment.setAdjacentTaskFragment(this); - return; - } - - setAdjacentTaskFragments(new AdjacentSet(this, taskFragment)); - } - void setAdjacentTaskFragments(@NonNull AdjacentSet adjacentTaskFragments) { adjacentTaskFragments.setAsAdjacent(); } @@ -483,56 +462,18 @@ class TaskFragment extends WindowContainer<WindowContainer> { return mCompanionTaskFragment; } - /** @deprecated b/373709676 replace with {@link #clearAdjacentTaskFragments()}. */ - @Deprecated - private void resetAdjacentTaskFragment() { - if (Flags.allowMultipleAdjacentTaskFragments()) { - throw new IllegalStateException("resetAdjacentTaskFragment shouldn't be called when" - + " allowMultipleAdjacentTaskFragments is enabled. Use either" - + " #clearAdjacentTaskFragments or #removeFromAdjacentTaskFragments"); - } - // Reset the adjacent TaskFragment if its adjacent TaskFragment is also this TaskFragment. - if (mAdjacentTaskFragment != null && mAdjacentTaskFragment.mAdjacentTaskFragment == this) { - mAdjacentTaskFragment.mAdjacentTaskFragment = null; - mAdjacentTaskFragment.mDelayLastActivityRemoval = false; - } - mAdjacentTaskFragment = null; - mDelayLastActivityRemoval = false; - } - void clearAdjacentTaskFragments() { - if (!Flags.allowMultipleAdjacentTaskFragments()) { - resetAdjacentTaskFragment(); - return; - } - if (mAdjacentTaskFragments != null) { mAdjacentTaskFragments.clear(); } } void removeFromAdjacentTaskFragments() { - if (!Flags.allowMultipleAdjacentTaskFragments()) { - resetAdjacentTaskFragment(); - return; - } - if (mAdjacentTaskFragments != null) { mAdjacentTaskFragments.remove(this); } } - /** @deprecated b/373709676 replace with {@link #getAdjacentTaskFragments()}. */ - @Deprecated - @Nullable - TaskFragment getAdjacentTaskFragment() { - if (Flags.allowMultipleAdjacentTaskFragments()) { - throw new IllegalStateException("allowMultipleAdjacentTaskFragments is enabled. " - + "Use #getAdjacentTaskFragments instead"); - } - return mAdjacentTaskFragment; - } - @Nullable AdjacentSet getAdjacentTaskFragments() { return mAdjacentTaskFragments; @@ -561,16 +502,10 @@ class TaskFragment extends WindowContainer<WindowContainer> { } boolean hasAdjacentTaskFragment() { - if (!Flags.allowMultipleAdjacentTaskFragments()) { - return mAdjacentTaskFragment != null; - } return mAdjacentTaskFragments != null; } boolean isAdjacentTo(@NonNull TaskFragment other) { - if (!Flags.allowMultipleAdjacentTaskFragments()) { - return mAdjacentTaskFragment == other; - } return other != this && mAdjacentTaskFragments != null && mAdjacentTaskFragments.contains(other); @@ -1377,21 +1312,12 @@ class TaskFragment extends WindowContainer<WindowContainer> { if (taskFragment.isAdjacentTo(this)) { continue; } - if (Flags.allowMultipleAdjacentTaskFragments()) { - final boolean isOccluding = mTmpRect.intersect(taskFragment.getBounds()) - || taskFragment.forOtherAdjacentTaskFragments(adjacentTf -> { - return mTmpRect.intersect(adjacentTf.getBounds()); - }); - if (isOccluding) { - return TASK_FRAGMENT_VISIBILITY_INVISIBLE; - } - } else { - final TaskFragment adjacentTaskFragment = - taskFragment.mAdjacentTaskFragment; - if (mTmpRect.intersect(taskFragment.getBounds()) - || mTmpRect.intersect(adjacentTaskFragment.getBounds())) { - return TASK_FRAGMENT_VISIBILITY_INVISIBLE; - } + final boolean isOccluding = mTmpRect.intersect(taskFragment.getBounds()) + || taskFragment.forOtherAdjacentTaskFragments(adjacentTf -> { + return mTmpRect.intersect(adjacentTf.getBounds()); + }); + if (isOccluding) { + return TASK_FRAGMENT_VISIBILITY_INVISIBLE; } } } @@ -1427,37 +1353,22 @@ class TaskFragment extends WindowContainer<WindowContainer> { // 2. Adjacent TaskFragments do not overlap, so that if this TaskFragment is behind // any translucent TaskFragment in the adjacent set, then this TaskFragment is // visible behind translucent. - if (Flags.allowMultipleAdjacentTaskFragments()) { - final boolean hasTraversedAdj = otherTaskFrag.forOtherAdjacentTaskFragments( - adjacentTaskFragments::contains); - if (hasTraversedAdj) { - final boolean isTranslucent = - isBehindTransparentTaskFragment(otherTaskFrag, starting) - || otherTaskFrag.forOtherAdjacentTaskFragments( - (Predicate<TaskFragment>) tf -> - isBehindTransparentTaskFragment(tf, starting)); - if (isTranslucent) { - // Can be visible behind a translucent adjacent TaskFragments. - gotTranslucentFullscreen = true; - gotTranslucentAdjacent = true; - continue; - } - // Can not be visible behind adjacent TaskFragments. - return TASK_FRAGMENT_VISIBILITY_INVISIBLE; - } - } else { - if (adjacentTaskFragments.contains(otherTaskFrag.mAdjacentTaskFragment)) { - if (isBehindTransparentTaskFragment(otherTaskFrag, starting) - || isBehindTransparentTaskFragment( - otherTaskFrag.mAdjacentTaskFragment, starting)) { - // Can be visible behind a translucent adjacent TaskFragments. - gotTranslucentFullscreen = true; - gotTranslucentAdjacent = true; - continue; - } - // Can not be visible behind adjacent TaskFragments. - return TASK_FRAGMENT_VISIBILITY_INVISIBLE; + final boolean hasTraversedAdj = otherTaskFrag.forOtherAdjacentTaskFragments( + adjacentTaskFragments::contains); + if (hasTraversedAdj) { + final boolean isTranslucent = + isBehindTransparentTaskFragment(otherTaskFrag, starting) + || otherTaskFrag.forOtherAdjacentTaskFragments( + (Predicate<TaskFragment>) tf -> + isBehindTransparentTaskFragment(tf, starting)); + if (isTranslucent) { + // Can be visible behind a translucent adjacent TaskFragments. + gotTranslucentFullscreen = true; + gotTranslucentAdjacent = true; + continue; } + // Can not be visible behind adjacent TaskFragments. + return TASK_FRAGMENT_VISIBILITY_INVISIBLE; } adjacentTaskFragments.add(otherTaskFrag); } @@ -3299,40 +3210,23 @@ class TaskFragment extends WindowContainer<WindowContainer> { final ArrayList<WindowContainer> siblings = getParent().mChildren; final int zOrder = siblings.indexOf(this); - - if (!Flags.allowMultipleAdjacentTaskFragments()) { - if (siblings.indexOf(getAdjacentTaskFragment()) < zOrder) { - // early return if this TF already has higher z-ordering. - return false; - } - } else { - final boolean hasAdjacentOnTop = forOtherAdjacentTaskFragments( - tf -> siblings.indexOf(tf) > zOrder); - if (!hasAdjacentOnTop) { - // early return if this TF already has higher z-ordering. - return false; - } + final boolean hasAdjacentOnTop = forOtherAdjacentTaskFragments( + tf -> siblings.indexOf(tf) > zOrder); + if (!hasAdjacentOnTop) { + // early return if this TF already has higher z-ordering. + return false; } final ToBooleanFunction<WindowState> getDimBehindWindow = (w) -> (w.mAttrs.flags & FLAG_DIM_BEHIND) != 0 && w.mActivityRecord != null && w.mActivityRecord.isEmbedded() && (w.mActivityRecord.isVisibleRequested() || w.mActivityRecord.isVisible()); - - if (!Flags.allowMultipleAdjacentTaskFragments()) { - final TaskFragment adjacentTf = getAdjacentTaskFragment(); - if (adjacentTf.forAllWindows(getDimBehindWindow, true)) { - // early return if the adjacent Tf has a dimming window. - return false; - } - } else { - final boolean adjacentHasDimmingWindow = forOtherAdjacentTaskFragments(tf -> { - return tf.forAllWindows(getDimBehindWindow, true); - }); - if (adjacentHasDimmingWindow) { - // early return if the adjacent Tf has a dimming window. - return false; - } + final boolean adjacentHasDimmingWindow = forOtherAdjacentTaskFragments(tf -> { + return tf.forAllWindows(getDimBehindWindow, true); + }); + if (adjacentHasDimmingWindow) { + // early return if the adjacent Tf has a dimming window. + return false; } // boost if there's an Activity window that has FLAG_DIM_BEHIND flag. @@ -3456,16 +3350,9 @@ class TaskFragment extends WindowContainer<WindowContainer> { sb.append(" organizerProc="); sb.append(mTaskFragmentOrganizerProcessName); } - if (Flags.allowMultipleAdjacentTaskFragments()) { - if (mAdjacentTaskFragments != null) { - sb.append(" adjacent="); - sb.append(mAdjacentTaskFragments); - } - } else { - if (mAdjacentTaskFragment != null) { - sb.append(" adjacent="); - sb.append(mAdjacentTaskFragment); - } + if (mAdjacentTaskFragments != null) { + sb.append(" adjacent="); + sb.append(mAdjacentTaskFragments); } sb.append('}'); return sb.toString(); @@ -3591,10 +3478,6 @@ class TaskFragment extends WindowContainer<WindowContainer> { } AdjacentSet(@NonNull ArraySet<TaskFragment> taskFragments) { - if (!Flags.allowMultipleAdjacentTaskFragments()) { - throw new IllegalStateException("allowMultipleAdjacentTaskFragments must be" - + " enabled to set more than two TaskFragments adjacent to each other."); - } final int size = taskFragments.size(); if (size < 2) { throw new IllegalArgumentException("Adjacent TaskFragments must contain at least" diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index c78cdaa10df2..803c21ccab6e 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -2589,9 +2589,6 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { } // When the TaskFragment has an adjacent TaskFragment, sibling behind them should be // hidden unless any of them are translucent. - if (!Flags.allowMultipleAdjacentTaskFragments()) { - return taskFragment.getAdjacentTaskFragment().isTranslucentForTransition(); - } return taskFragment.forOtherAdjacentTaskFragments(TaskFragment::isTranslucentForTransition); } diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java index 3a4d9d27f65a..e1553cd37d03 100644 --- a/services/core/java/com/android/server/wm/WallpaperController.java +++ b/services/core/java/com/android/server/wm/WallpaperController.java @@ -57,7 +57,8 @@ import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.ProtoLog; import com.android.internal.util.ToBooleanFunction; -import com.android.server.wallpaper.WallpaperCropper.WallpaperCropUtils; +import com.android.server.wallpaper.WallpaperCropper; +import com.android.server.wallpaper.WallpaperDefaultDisplayInfo; import java.io.PrintWriter; import java.util.ArrayList; @@ -71,7 +72,6 @@ import java.util.function.Consumer; class WallpaperController { private static final String TAG = TAG_WITH_CLASS_NAME ? "WallpaperController" : TAG_WM; private WindowManagerService mService; - private WallpaperCropUtils mWallpaperCropUtils = null; private DisplayContent mDisplayContent; // Larger index has higher z-order. @@ -116,6 +116,10 @@ class WallpaperController { private boolean mShouldOffsetWallpaperCenter; + // This is for WallpaperCropper, which has cropping logic for the default display only. + // TODO(b/400685784) make the WallpaperCropper operate on every display independently + private final WallpaperDefaultDisplayInfo mDefaultDisplayInfo; + private final ToBooleanFunction<WindowState> mFindWallpaperTargetFunction = w -> { final ActivityRecord ar = w.mActivityRecord; // The animating window can still be visible on screen if it is in transition, so we @@ -198,12 +202,14 @@ class WallpaperController { WallpaperController(WindowManagerService service, DisplayContent displayContent) { mService = service; mDisplayContent = displayContent; + WindowManager windowManager = service.mContext.getSystemService(WindowManager.class); Resources resources = service.mContext.getResources(); mMinWallpaperScale = resources.getFloat(com.android.internal.R.dimen.config_wallpaperMinScale); mMaxWallpaperScale = resources.getFloat(R.dimen.config_wallpaperMaxScale); mShouldOffsetWallpaperCenter = resources.getBoolean( com.android.internal.R.bool.config_offsetWallpaperToCenterOfLargestDisplay); + mDefaultDisplayInfo = new WallpaperDefaultDisplayInfo(windowManager, resources); } void resetLargestDisplay(Display display) { @@ -246,10 +252,6 @@ class WallpaperController { return largestDisplaySize; } - void setWallpaperCropUtils(WallpaperCropUtils wallpaperCropUtils) { - mWallpaperCropUtils = wallpaperCropUtils; - } - WindowState getWallpaperTarget() { return mWallpaperTarget; } @@ -352,16 +354,12 @@ class WallpaperController { int offsetY; if (multiCrop()) { - if (mWallpaperCropUtils == null) { - Slog.e(TAG, "Update wallpaper offsets before the system is ready. Aborting"); - return false; - } Point bitmapSize = new Point( wallpaperWin.mRequestedWidth, wallpaperWin.mRequestedHeight); SparseArray<Rect> cropHints = token.getCropHints(); wallpaperFrame = bitmapSize.x <= 0 || bitmapSize.y <= 0 ? wallpaperWin.getFrame() - : mWallpaperCropUtils.getCrop(screenSize, bitmapSize, cropHints, - wallpaperWin.isRtl()); + : WallpaperCropper.getCrop(screenSize, mDefaultDisplayInfo, bitmapSize, + cropHints, wallpaperWin.isRtl()); int frameWidth = wallpaperFrame.width(); int frameHeight = wallpaperFrame.height(); float frameRatio = (float) frameWidth / frameHeight; diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 466ed7863c84..772a7fdfc684 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -2006,11 +2006,16 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< return getActivity(r -> !r.finishing, true /* traverseTopToBottom */); } - ActivityRecord getTopMostVisibleFreeformActivity() { + ActivityRecord getTopMostFreeformActivity() { return getActivity(r -> r.isVisibleRequested() && r.inFreeformWindowingMode(), true /* traverseTopToBottom */); } + ActivityRecord getTopMostVisibleFreeformActivity() { + return getActivity(r -> r.isVisible() && r.inFreeformWindowingMode(), + true /* traverseTopToBottom */); + } + ActivityRecord getTopActivity(boolean includeFinishing, boolean includeOverlays) { // Break down into 4 calls to avoid object creation due to capturing input params. if (includeFinishing) { diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java index 4b5a3a031931..5f2a2ad7f0eb 100644 --- a/services/core/java/com/android/server/wm/WindowManagerInternal.java +++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java @@ -54,7 +54,6 @@ import android.window.ScreenCapture.ScreenshotHardwareBuffer; import com.android.internal.policy.KeyInterceptionInfo; import com.android.server.input.InputManagerService; import com.android.server.policy.WindowManagerPolicy; -import com.android.server.wallpaper.WallpaperCropper.WallpaperCropUtils; import com.android.server.wm.SensitiveContentPackages.PackageInfo; import java.lang.annotation.Retention; @@ -772,12 +771,6 @@ public abstract class WindowManagerInternal { public abstract void setWallpaperCropHints(IBinder windowToken, SparseArray<Rect> cropHints); /** - * Transmits the {@link WallpaperCropUtils} instance to {@link WallpaperController}. - * {@link WallpaperCropUtils} contains the helpers to properly position the wallpaper. - */ - public abstract void setWallpaperCropUtils(WallpaperCropUtils wallpaperCropUtils); - - /** * Returns {@code true} if a Window owned by {@code uid} has focus. */ public abstract boolean isUidFocused(int uid); diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 9fc0339c52a2..c078d67b6cc6 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -356,7 +356,6 @@ import com.android.server.policy.WindowManagerPolicy; import com.android.server.policy.WindowManagerPolicy.ScreenOffListener; import com.android.server.power.ShutdownThread; import com.android.server.utils.PriorityDump; -import com.android.server.wallpaper.WallpaperCropper.WallpaperCropUtils; import com.android.window.flags.Flags; import dalvik.annotation.optimization.NeverCompile; @@ -8100,12 +8099,6 @@ public class WindowManagerService extends IWindowManager.Stub } @Override - public void setWallpaperCropUtils(WallpaperCropUtils wallpaperCropUtils) { - mRoot.getDisplayContent(DEFAULT_DISPLAY).mWallpaperController - .setWallpaperCropUtils(wallpaperCropUtils); - } - - @Override public boolean isUidFocused(int uid) { synchronized (mGlobalLock) { for (int i = mRoot.getChildCount() - 1; i >= 0; i--) { @@ -9374,23 +9367,6 @@ public class WindowManagerService extends IWindowManager.Stub return focusedActivity; } - if (!Flags.allowMultipleAdjacentTaskFragments()) { - final TaskFragment adjacentTaskFragment = taskFragment.getAdjacentTaskFragment(); - final ActivityRecord adjacentTopActivity = adjacentTaskFragment.topRunningActivity(); - if (adjacentTopActivity == null) { - // Return if no adjacent activity. - return focusedActivity; - } - - if (adjacentTopActivity.getLastWindowCreateTime() - < focusedActivity.getLastWindowCreateTime()) { - // Return if the current focus activity has more recently active window. - return focusedActivity; - } - - return adjacentTopActivity; - } - // Find the adjacent activity with more recently active window. final ActivityRecord[] mostRecentActiveActivity = { focusedActivity }; final long[] mostRecentActiveTime = { focusedActivity.getLastWindowCreateTime() }; @@ -9461,20 +9437,15 @@ public class WindowManagerService extends IWindowManager.Stub // No adjacent window. return false; } - final TaskFragment adjacentFragment; - if (Flags.allowMultipleAdjacentTaskFragments()) { - if (fromFragment.getAdjacentTaskFragments().size() > 2) { - throw new IllegalStateException("Not yet support 3+ adjacent for non-Task TFs"); - } - final TaskFragment[] tmpAdjacent = new TaskFragment[1]; - fromFragment.forOtherAdjacentTaskFragments(adjacentTF -> { - tmpAdjacent[0] = adjacentTF; - return true; - }); - adjacentFragment = tmpAdjacent[0]; - } else { - adjacentFragment = fromFragment.getAdjacentTaskFragment(); + if (fromFragment.getAdjacentTaskFragments().size() > 2) { + throw new IllegalStateException("Not yet support 3+ adjacent for non-Task TFs"); } + final TaskFragment[] tmpAdjacent = new TaskFragment[1]; + fromFragment.forOtherAdjacentTaskFragments(adjacentTF -> { + tmpAdjacent[0] = adjacentTF; + return true; + }); + final TaskFragment adjacentFragment = tmpAdjacent[0]; if (adjacentFragment.isIsolatedNav()) { // Don't move the focus if the adjacent TF is isolated navigation. return false; diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index ea1f35a130b0..a012ec137892 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -1671,13 +1671,9 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } if (!taskFragment.isAdjacentTo(secondaryTaskFragment)) { // Only have lifecycle effect if the adjacent changed. - if (Flags.allowMultipleAdjacentTaskFragments()) { - // Activity Embedding only set two TFs adjacent. - taskFragment.setAdjacentTaskFragments( - new TaskFragment.AdjacentSet(taskFragment, secondaryTaskFragment)); - } else { - taskFragment.setAdjacentTaskFragment(secondaryTaskFragment); - } + // Activity Embedding only set two TFs adjacent. + taskFragment.setAdjacentTaskFragments( + new TaskFragment.AdjacentSet(taskFragment, secondaryTaskFragment)); effects |= TRANSACT_EFFECTS_LIFECYCLE; } @@ -2220,30 +2216,6 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } private int setAdjacentRootsHierarchyOp(WindowContainerTransaction.HierarchyOp hop) { - if (!Flags.allowMultipleAdjacentTaskFragments()) { - final WindowContainer wc1 = WindowContainer.fromBinder(hop.getContainer()); - if (wc1 == null || !wc1.isAttached()) { - Slog.e(TAG, "Attempt to operate on unknown or detached container: " + wc1); - return TRANSACT_EFFECTS_NONE; - } - final TaskFragment root1 = wc1.asTaskFragment(); - final WindowContainer wc2 = WindowContainer.fromBinder(hop.getAdjacentRoot()); - if (wc2 == null || !wc2.isAttached()) { - Slog.e(TAG, "Attempt to operate on unknown or detached container: " + wc2); - return TRANSACT_EFFECTS_NONE; - } - final TaskFragment root2 = wc2.asTaskFragment(); - if (!root1.mCreatedByOrganizer || !root2.mCreatedByOrganizer) { - throw new IllegalArgumentException("setAdjacentRootsHierarchyOp: Not created by" - + " organizer root1=" + root1 + " root2=" + root2); - } - if (root1.isAdjacentTo(root2)) { - return TRANSACT_EFFECTS_NONE; - } - root1.setAdjacentTaskFragment(root2); - return TRANSACT_EFFECTS_LIFECYCLE; - } - final IBinder[] containers = hop.getContainers(); final ArraySet<TaskFragment> adjacentRoots = new ArraySet<>(); for (IBinder container : containers) { diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp index 95776088aad8..66d04df8095b 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -193,10 +193,6 @@ cc_defaults { "android.hardware.tv.input@1.0", "android.hardware.tv.input-V2-ndk", "android.hardware.vibrator-V3-ndk", - "android.hardware.vibrator@1.0", - "android.hardware.vibrator@1.1", - "android.hardware.vibrator@1.2", - "android.hardware.vibrator@1.3", "android.hardware.vr@1.0", "android.hidl.token@1.0-utils", "android.frameworks.schedulerservice@1.0", diff --git a/services/core/jni/com_android_server_display_DisplayControl.cpp b/services/core/jni/com_android_server_display_DisplayControl.cpp index aeae13d83809..c674f9037aa4 100644 --- a/services/core/jni/com_android_server_display_DisplayControl.cpp +++ b/services/core/jni/com_android_server_display_DisplayControl.cpp @@ -24,12 +24,13 @@ namespace android { static jobject nativeCreateVirtualDisplay(JNIEnv* env, jclass clazz, jstring nameObj, - jboolean secure, jstring uniqueIdStr, - jfloat requestedRefreshRate) { + jboolean secure, jboolean optimizeForPower, + jstring uniqueIdStr, jfloat requestedRefreshRate) { const ScopedUtfChars name(env, nameObj); const ScopedUtfChars uniqueId(env, uniqueIdStr); sp<IBinder> token(SurfaceComposerClient::createVirtualDisplay(std::string(name.c_str()), bool(secure), + bool(optimizeForPower), std::string(uniqueId.c_str()), requestedRefreshRate)); return javaObjectForIBinder(env, token); @@ -182,7 +183,7 @@ static jobject nativeGetPhysicalDisplayToken(JNIEnv* env, jclass clazz, jlong ph static const JNINativeMethod sDisplayMethods[] = { // clang-format off - {"nativeCreateVirtualDisplay", "(Ljava/lang/String;ZLjava/lang/String;F)Landroid/os/IBinder;", + {"nativeCreateVirtualDisplay", "(Ljava/lang/String;ZZLjava/lang/String;F)Landroid/os/IBinder;", (void*)nativeCreateVirtualDisplay }, {"nativeDestroyVirtualDisplay", "(Landroid/os/IBinder;)V", (void*)nativeDestroyVirtualDisplay }, diff --git a/services/core/jni/com_android_server_vibrator_VibratorController.cpp b/services/core/jni/com_android_server_vibrator_VibratorController.cpp index 534dbb1f6cf1..11dbbdfb850e 100644 --- a/services/core/jni/com_android_server_vibrator_VibratorController.cpp +++ b/services/core/jni/com_android_server_vibrator_VibratorController.cpp @@ -19,7 +19,6 @@ #include <aidl/android/hardware/vibrator/IVibrator.h> #include <android/binder_parcel.h> #include <android/binder_parcel_jni.h> -#include <android/hardware/vibrator/1.3/IVibrator.h> #include <android/persistable_bundle_aidl.h> #include <android_os_vibrator.h> #include <nativehelper/JNIHelp.h> @@ -32,8 +31,6 @@ #include "core_jni_helpers.h" #include "jni.h" -namespace V1_0 = android::hardware::vibrator::V1_0; -namespace V1_3 = android::hardware::vibrator::V1_3; namespace Aidl = aidl::android::hardware::vibrator; using aidl::android::os::PersistableBundle; @@ -80,31 +77,6 @@ static struct { jfieldID timeMillis; } sPwlePointClassInfo; -static_assert(static_cast<uint8_t>(V1_0::EffectStrength::LIGHT) == - static_cast<uint8_t>(Aidl::EffectStrength::LIGHT)); -static_assert(static_cast<uint8_t>(V1_0::EffectStrength::MEDIUM) == - static_cast<uint8_t>(Aidl::EffectStrength::MEDIUM)); -static_assert(static_cast<uint8_t>(V1_0::EffectStrength::STRONG) == - static_cast<uint8_t>(Aidl::EffectStrength::STRONG)); - -static_assert(static_cast<uint8_t>(V1_3::Effect::CLICK) == - static_cast<uint8_t>(Aidl::Effect::CLICK)); -static_assert(static_cast<uint8_t>(V1_3::Effect::DOUBLE_CLICK) == - static_cast<uint8_t>(Aidl::Effect::DOUBLE_CLICK)); -static_assert(static_cast<uint8_t>(V1_3::Effect::TICK) == static_cast<uint8_t>(Aidl::Effect::TICK)); -static_assert(static_cast<uint8_t>(V1_3::Effect::THUD) == static_cast<uint8_t>(Aidl::Effect::THUD)); -static_assert(static_cast<uint8_t>(V1_3::Effect::POP) == static_cast<uint8_t>(Aidl::Effect::POP)); -static_assert(static_cast<uint8_t>(V1_3::Effect::HEAVY_CLICK) == - static_cast<uint8_t>(Aidl::Effect::HEAVY_CLICK)); -static_assert(static_cast<uint8_t>(V1_3::Effect::RINGTONE_1) == - static_cast<uint8_t>(Aidl::Effect::RINGTONE_1)); -static_assert(static_cast<uint8_t>(V1_3::Effect::RINGTONE_2) == - static_cast<uint8_t>(Aidl::Effect::RINGTONE_2)); -static_assert(static_cast<uint8_t>(V1_3::Effect::RINGTONE_15) == - static_cast<uint8_t>(Aidl::Effect::RINGTONE_15)); -static_assert(static_cast<uint8_t>(V1_3::Effect::TEXTURE_TICK) == - static_cast<uint8_t>(Aidl::Effect::TEXTURE_TICK)); - static std::shared_ptr<vibrator::HalController> findVibrator(int32_t vibratorId) { vibrator::ManagerHalController* manager = android_server_vibrator_VibratorManagerService_getManager(); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 215d6ca964eb..51ed6bb2aa40 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -245,6 +245,7 @@ import static android.app.admin.ProvisioningException.ERROR_REMOVE_NON_REQUIRED_ import static android.app.admin.ProvisioningException.ERROR_SETTING_PROFILE_OWNER_FAILED; import static android.app.admin.ProvisioningException.ERROR_SET_DEVICE_OWNER_FAILED; import static android.app.admin.ProvisioningException.ERROR_STARTING_PROFILE_FAILED; +import static android.content.Context.RECEIVER_NOT_EXPORTED; import static android.content.Intent.ACTION_MANAGED_PROFILE_AVAILABLE; import static android.content.Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; @@ -486,6 +487,7 @@ import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.telephony.data.ApnSetting; +import android.telephony.euicc.EuiccManager; import android.text.TextUtils; import android.text.format.DateUtils; import android.util.ArrayMap; @@ -643,6 +645,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { static final String ACTION_PROFILE_OFF_DEADLINE = "com.android.server.ACTION_PROFILE_OFF_DEADLINE"; + /** Broadcast action invoked when a managed eSIM is removed while deleting work profile. */ + private static final String ACTION_ESIM_REMOVED_WITH_MANAGED_PROFILE = + "com.android.server.ACTION_ESIM_REMOVED_WITH_MANAGED_PROFILE"; + + /** Extra for the subscription ID of the managed eSIM removed while deleting work profile. */ + private static final String EXTRA_REMOVED_ESIM_SUBSCRIPTION_ID = + "com.android.server.EXTRA_ESIM_REMOVED_WITH_MANAGED_PROFILE_SUBSCRIPTION_ID"; + private static final String CALLED_FROM_PARENT = "calledFromParent"; private static final String NOT_CALLED_FROM_PARENT = "notCalledFromParent"; @@ -1266,6 +1276,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { removeCredentialManagementApp(intent.getData().getSchemeSpecificPart()); } else if (Intent.ACTION_MANAGED_PROFILE_ADDED.equals(action)) { clearWipeProfileNotification(); + } else if (Intent.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) { + removeManagedEmbeddedSubscriptionsForUser(userHandle); } else if (Intent.ACTION_DATE_CHANGED.equals(action) || Intent.ACTION_TIME_CHANGED.equals(action)) { // Update freeze period record when clock naturally progresses to the next day @@ -1298,6 +1310,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { triggerPolicyComplianceCheckIfNeeded(userHandle, suspended); } else if (LOGIN_ACCOUNTS_CHANGED_ACTION.equals(action)) { calculateHasIncompatibleAccounts(); + } else if (ACTION_ESIM_REMOVED_WITH_MANAGED_PROFILE.equals(action)) { + int removedSubscriptionId = intent.getIntExtra(EXTRA_REMOVED_ESIM_SUBSCRIPTION_ID, + -1); + Slogf.i(LOG_TAG, + "Deleted subscription with ID %d because owning managed profile was " + + "removed", + removedSubscriptionId); } } @@ -2219,9 +2238,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, filter, null, mHandler); filter = new IntentFilter(); filter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED); + filter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED); filter.addAction(Intent.ACTION_TIME_CHANGED); filter.addAction(Intent.ACTION_DATE_CHANGED); mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, filter, null, mHandler); + filter = new IntentFilter(); + filter.addAction(ACTION_ESIM_REMOVED_WITH_MANAGED_PROFILE); + mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, filter, null, + mHandler, RECEIVER_NOT_EXPORTED); LocalServices.addService(DevicePolicyManagerInternal.class, mLocalService); @@ -3782,9 +3806,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // Update user switcher message to activity manager. ActivityManagerInternal activityManagerInternal = mInjector.getActivityManagerInternal(); - activityManagerInternal.setSwitchingFromSystemUserMessage( + int deviceOwnerUserId = UserHandle.getUserId(deviceOwner.getUid()); + activityManagerInternal.setSwitchingFromUserMessage(deviceOwnerUserId, deviceOwner.startUserSessionMessage); - activityManagerInternal.setSwitchingToSystemUserMessage( + activityManagerInternal.setSwitchingToUserMessage(deviceOwnerUserId, deviceOwner.endUserSessionMessage); } @@ -3970,6 +3995,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { deletedUsers.remove(userInfo.id); } for (Integer userId : deletedUsers) { + removeManagedEmbeddedSubscriptionsForUser(userId); removeUserData(userId); mDevicePolicyEngine.handleUserRemoved(userId); } @@ -8099,6 +8125,45 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { mInjector.getNotificationManager().cancel(SystemMessage.NOTE_PROFILE_WIPED); } + /** + * Remove eSIM subscriptions that are managed by any of the admin packages of the given + * userHandle. + */ + private void removeManagedEmbeddedSubscriptionsForUser(int userHandle) { + if (!Flags.removeManagedEsimOnWorkProfileDeletion()) { + return; + } + + Slogf.i(LOG_TAG, + "Managed profile with ID=%d deleted: going to remove managed embedded " + + "subscriptions", userHandle); + String profileOwnerPackage = mOwners.getProfileOwnerPackage(userHandle); + if (profileOwnerPackage == null) { + Slogf.wtf(LOG_TAG, "Profile owner package for managed profile is null"); + return; + } + IntArray managedSubscriptionIds = getSubscriptionIdsInternal(profileOwnerPackage); + deleteEmbeddedSubscriptions(managedSubscriptionIds); + } + + private void deleteEmbeddedSubscriptions(IntArray subscriptionIds) { + EuiccManager euiccManager = mContext.getSystemService(EuiccManager.class); + for (int subscriptionId : subscriptionIds.toArray()) { + Slogf.i(LOG_TAG, "Deleting embedded subscription with ID %d", subscriptionId); + euiccManager.deleteSubscription(subscriptionId, + createCallbackPendingIntentForRemovingManagedSubscription( + subscriptionId)); + } + } + + private PendingIntent createCallbackPendingIntentForRemovingManagedSubscription( + Integer subscriptionId) { + Intent intent = new Intent(ACTION_ESIM_REMOVED_WITH_MANAGED_PROFILE); + intent.putExtra(EXTRA_REMOVED_ESIM_SUBSCRIPTION_ID, subscriptionId); + return PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_IMMUTABLE); + } + + @Override public void setFactoryResetProtectionPolicy(ComponentName who, String callerPackageName, @Nullable FactoryResetProtectionPolicy policy) { @@ -19652,7 +19717,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } mInjector.getActivityManagerInternal() - .setSwitchingFromSystemUserMessage(startUserSessionMessageString); + .setSwitchingFromUserMessage(caller.getUserId(), startUserSessionMessageString); } @Override @@ -19677,7 +19742,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } mInjector.getActivityManagerInternal() - .setSwitchingToSystemUserMessage(endUserSessionMessageString); + .setSwitchingToUserMessage(caller.getUserId(), endUserSessionMessageString); } @Override diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java b/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java index 8e749529978e..de78271acddc 100644 --- a/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java +++ b/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java @@ -20,6 +20,7 @@ import static android.hardware.SensorManager.SENSOR_DELAY_FASTEST; import static android.hardware.devicestate.DeviceState.PROPERTY_POLICY_UNSUPPORTED_WHEN_POWER_SAVE_MODE; import static android.hardware.devicestate.DeviceState.PROPERTY_POLICY_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL; import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER; +import static android.os.Trace.TRACE_TAG_SYSTEM_SERVER; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.TYPE_EXTERNAL; @@ -324,6 +325,11 @@ public final class FoldableDeviceStateProvider implements DeviceStateProvider, } if (newState != INVALID_DEVICE_STATE_IDENTIFIER && newState != mLastReportedState) { + if (Trace.isTagEnabled(TRACE_TAG_SYSTEM_SERVER)) { + Trace.instant(TRACE_TAG_SYSTEM_SERVER, + "[Device state changed] Last hinge sensor event timestamp: " + + mLastHingeAngleSensorEvent.timestamp); + } mLastReportedState = newState; stateToReport = newState; } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 860b6fb1dcd1..788b3b883160 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -366,6 +366,8 @@ public final class SystemServer implements Dumpable { "com.android.clockwork.time.WearTimeService"; private static final String WEAR_SETTINGS_SERVICE_CLASS = "com.android.clockwork.settings.WearSettingsService"; + private static final String WEAR_GESTURE_SERVICE_CLASS = + "com.android.clockwork.gesture.WearGestureService"; private static final String WRIST_ORIENTATION_SERVICE_CLASS = "com.android.clockwork.wristorientation.WristOrientationService"; private static final String IOT_SERVICE_CLASS = @@ -2844,6 +2846,13 @@ public final class SystemServer implements Dumpable { mSystemServiceManager.startService(WRIST_ORIENTATION_SERVICE_CLASS); t.traceEnd(); } + + if (android.server.Flags.wearGestureApi() + && SystemProperties.getBoolean("config.enable_gesture_api", false)) { + t.traceBegin("StartWearGestureService"); + mSystemServiceManager.startService(WEAR_GESTURE_SERVICE_CLASS); + t.traceEnd(); + } } if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_SLICES_DISABLED)) { diff --git a/services/java/com/android/server/flags.aconfig b/services/java/com/android/server/flags.aconfig index 86ccd878de7c..7a6bd75e5893 100644 --- a/services/java/com/android/server/flags.aconfig +++ b/services/java/com/android/server/flags.aconfig @@ -65,4 +65,12 @@ flag { namespace: "package_manager_service" description: "Remove AppIntegrityManagerService" bug: "364200023" +} + +flag { + name: "wear_gesture_api" + namespace: "wear_frameworks" + description: "Whether the Wear Gesture API is available." + bug: "396154116" + is_exported: true }
\ No newline at end of file diff --git a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java index 2dd16f68dc56..80a3a8788d80 100644 --- a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java +++ b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java @@ -109,6 +109,7 @@ import com.android.server.EventLogTags; import com.android.server.LocalServices; import com.android.server.backup.BackupAgentConnectionManager; import com.android.server.backup.BackupRestoreTask; +import com.android.server.backup.BackupRestoreTask.CancellationReason; import com.android.server.backup.BackupWakeLock; import com.android.server.backup.DataChangedJournal; import com.android.server.backup.KeyValueBackupJob; @@ -2412,7 +2413,7 @@ public class KeyValueBackupTaskTest { KeyValueBackupTask task = spy(createKeyValueBackupTask(transportMock, PACKAGE_1)); doNothing().when(task).waitCancel(); - task.handleCancel(true); + task.handleCancel(CancellationReason.EXTERNAL); InOrder inOrder = inOrder(task); inOrder.verify(task).markCancel(); @@ -2420,12 +2421,14 @@ public class KeyValueBackupTaskTest { } @Test - public void testHandleCancel_whenCancelAllFalse_throws() throws Exception { + public void testHandleCancel_timeout_throws() throws Exception { TransportMock transportMock = setUpInitializedTransport(mTransport); setUpAgentWithData(PACKAGE_1); KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1); - expectThrows(IllegalArgumentException.class, () -> task.handleCancel(false)); + expectThrows( + IllegalArgumentException.class, + () -> task.handleCancel(CancellationReason.TIMEOUT)); } /** Do not update backup token if no data was moved. */ diff --git a/services/tests/DynamicInstrumentationManagerServiceTests/OWNERS b/services/tests/DynamicInstrumentationManagerServiceTests/OWNERS new file mode 100644 index 000000000000..2522426d93f8 --- /dev/null +++ b/services/tests/DynamicInstrumentationManagerServiceTests/OWNERS @@ -0,0 +1 @@ +include platform/packages/modules/UprobeStats:/OWNERS
\ No newline at end of file diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/util/IgnoreableExpect.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/util/IgnoreableExpect.kt index afb18f5be669..5c9ba401bf88 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/util/IgnoreableExpect.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/util/IgnoreableExpect.kt @@ -32,7 +32,7 @@ internal class IgnoreableExpect : TestRule { private var ignore = false - override fun apply(base: Statement?, description: Description?): Statement { + override fun apply(base: Statement, description: Description): Statement { return object : Statement() { override fun evaluate() { ignore = false diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java index 1f8ccde98d35..2770caa8aaa4 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -324,7 +324,8 @@ public class DisplayManagerServiceTest { return new VirtualDisplayAdapter(syncRoot, context, handler, displayAdapterListener, new VirtualDisplayAdapter.SurfaceControlDisplayFactory() { @Override - public IBinder createDisplay(String name, boolean secure, String uniqueId, + public IBinder createDisplay(String name, boolean secure, + boolean optimizeForPower, String uniqueId, float requestedRefreshRate) { return mMockDisplayToken; } 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 206c90d0481a..6dc7361e5366 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt @@ -47,7 +47,7 @@ class DisplayTopologyCoordinatorTest { private val mockTopology = mock<DisplayTopology>() private val mockTopologyCopy = mock<DisplayTopology>() private val mockTopologyGraph = mock<DisplayTopologyGraph>() - private val mockIsExtendedDisplayEnabled = mock<() -> Boolean>() + private val mockIsExtendedDisplayAllowed = mock<() -> Boolean>() private val mockTopologySavedCallback = mock<() -> Unit>() private val mockTopologyChangedCallback = mock<(android.util.Pair<DisplayTopology, DisplayTopologyGraph>) -> Unit>() @@ -73,10 +73,10 @@ class DisplayTopologyCoordinatorTest { ) = mockTopologyStore } - whenever(mockIsExtendedDisplayEnabled()).thenReturn(true) + whenever(mockIsExtendedDisplayAllowed()).thenReturn(true) whenever(mockTopology.copy()).thenReturn(mockTopologyCopy) whenever(mockTopologyCopy.getGraph(any())).thenReturn(mockTopologyGraph) - coordinator = DisplayTopologyCoordinator(injector, mockIsExtendedDisplayEnabled, + coordinator = DisplayTopologyCoordinator(injector, mockIsExtendedDisplayAllowed, mockTopologyChangedCallback, topologyChangeExecutor, DisplayManagerService.SyncRoot(), mockTopologySavedCallback) } @@ -195,7 +195,7 @@ class DisplayTopologyCoordinatorTest { @Test fun addDisplay_external_extendedDisplaysDisabled() { - whenever(mockIsExtendedDisplayEnabled()).thenReturn(false) + whenever(mockIsExtendedDisplayAllowed()).thenReturn(false) for (displayInfo in displayInfos) { coordinator.onDisplayAdded(displayInfo) @@ -208,7 +208,7 @@ class DisplayTopologyCoordinatorTest { @Test fun addDisplay_overlay_extendedDisplaysDisabled() { displayInfos[0].type = Display.TYPE_OVERLAY - whenever(mockIsExtendedDisplayEnabled()).thenReturn(false) + whenever(mockIsExtendedDisplayAllowed()).thenReturn(false) for (displayInfo in displayInfos) { coordinator.onDisplayAdded(displayInfo) @@ -314,7 +314,7 @@ class DisplayTopologyCoordinatorTest { @Test fun updateDisplay_external_extendedDisplaysDisabled() { - whenever(mockIsExtendedDisplayEnabled()).thenReturn(false) + whenever(mockIsExtendedDisplayAllowed()).thenReturn(false) for (displayInfo in displayInfos) { coordinator.onDisplayChanged(displayInfo) @@ -328,7 +328,7 @@ class DisplayTopologyCoordinatorTest { @Test fun updateDisplay_overlay_extendedDisplaysDisabled() { displayInfos[0].type = Display.TYPE_OVERLAY - whenever(mockIsExtendedDisplayEnabled()).thenReturn(false) + whenever(mockIsExtendedDisplayAllowed()).thenReturn(false) coordinator.onDisplayChanged(displayInfos[0]) diff --git a/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java index 0bef3b89547f..10bea7d331cd 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java @@ -416,7 +416,7 @@ public class VirtualDisplayAdapterTest { final String uniqueId = "uniqueId"; final IBinder displayToken = new Binder(); when(mMockSufaceControlDisplayFactory.createDisplay( - any(), anyBoolean(), eq(uniqueId), anyFloat())) + any(), anyBoolean(), anyBoolean(), eq(uniqueId), anyFloat())) .thenReturn(displayToken); // The display needs to be public, otherwise it will be considered never blank. @@ -456,6 +456,49 @@ public class VirtualDisplayAdapterTest { verify(mMockCallback).onPaused(); } + @EnableFlags( + android.companion.virtualdevice.flags.Flags.FLAG_CORRECT_VIRTUAL_DISPLAY_POWER_STATE) + @Test + public void createVirtualDisplayLocked_neverBlank_optimizesForPower() { + final String uniqueId = "uniqueId"; + final IBinder displayToken = new Binder(); + final String name = "name"; + when(mVirtualDisplayConfigMock.getName()).thenReturn(name); + when(mMockSufaceControlDisplayFactory.createDisplay( + any(), anyBoolean(), anyBoolean(), eq(uniqueId), anyFloat())) + .thenReturn(displayToken); + + // Use a private display to cause the display to be never blank. + mAdapter.createVirtualDisplayLocked(mMockCallback, + /* projection= */ null, /* ownerUid= */ 10, /* packageName= */ "testpackage", + uniqueId, /* surface= */ mSurfaceMock, 0, mVirtualDisplayConfigMock); + + verify(mMockSufaceControlDisplayFactory).createDisplay(eq(name), eq(false), eq(true), + eq(uniqueId), anyFloat()); + } + + @EnableFlags( + android.companion.virtualdevice.flags.Flags.FLAG_CORRECT_VIRTUAL_DISPLAY_POWER_STATE) + @Test + public void createVirtualDisplayLocked_blankable_optimizesForPerformance() { + final String uniqueId = "uniqueId"; + final IBinder displayToken = new Binder(); + final String name = "name"; + when(mVirtualDisplayConfigMock.getName()).thenReturn(name); + when(mMockSufaceControlDisplayFactory.createDisplay( + any(), anyBoolean(), anyBoolean(), eq(uniqueId), anyFloat())) + .thenReturn(displayToken); + + // Use a public display to cause the display to be blankable + mAdapter.createVirtualDisplayLocked(mMockCallback, + /* projection= */ null, /* ownerUid= */ 10, /* packageName= */ "testpackage", + uniqueId, /* surface= */ mSurfaceMock, DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC, + mVirtualDisplayConfigMock); + + verify(mMockSufaceControlDisplayFactory).createDisplay(eq(name), eq(false), eq(false), + eq(uniqueId), anyFloat()); + } + private IVirtualDisplayCallback createCallback() { return new IVirtualDisplayCallback.Stub() { diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java index 5d8f57866f7d..e094111c327a 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java @@ -746,6 +746,36 @@ public class MockingOomAdjusterTests { @SuppressWarnings("GuardedBy") @Test @EnableFlags(Flags.FLAG_USE_CPU_TIME_CAPABILITY) + public void testUpdateOomAdjFreezeState_bindingWithAllowFreeze() { + ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID, + MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true)); + WindowProcessController wpc = app.getWindowProcessController(); + doReturn(true).when(wpc).hasVisibleActivities(); + + final ProcessRecord app2 = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID, + MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false)); + + // App with a visible activity binds to app2 without any special flag. + bindService(app2, app, null, null, 0, mock(IBinder.class)); + + final ProcessRecord app3 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID, + MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false)); + + // App with a visible activity binds to app3 with ALLOW_FREEZE. + bindService(app3, app, null, null, Context.BIND_ALLOW_FREEZE, mock(IBinder.class)); + + setProcessesToLru(app, app2, app3); + + updateOomAdj(app); + + assertCpuTime(app); + assertCpuTime(app2); + assertNoCpuTime(app3); + } + + @SuppressWarnings("GuardedBy") + @Test + @EnableFlags(Flags.FLAG_USE_CPU_TIME_CAPABILITY) @DisableFlags(Flags.FLAG_PROTOTYPE_AGGRESSIVE_FREEZING) public void testUpdateOomAdjFreezeState_bindingFromFgs() { final ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID, diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/BackupAgentConnectionManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/BackupAgentConnectionManagerTest.java index 8aaa72339c5b..33bd95ec9f5b 100644 --- a/services/tests/mockingservicestests/src/com/android/server/backup/BackupAgentConnectionManagerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/backup/BackupAgentConnectionManagerTest.java @@ -51,6 +51,7 @@ import android.platform.test.flag.junit.SetFlagsRule; import androidx.test.runner.AndroidJUnit4; import com.android.server.LocalServices; +import com.android.server.backup.BackupRestoreTask.CancellationReason; import com.android.server.backup.internal.LifecycleOperationStorage; import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges; @@ -368,9 +369,12 @@ public class BackupAgentConnectionManagerTest { mConnectionManager.agentDisconnected(TEST_PACKAGE); mTestThread.join(); - verify(mUserBackupManagerService).handleCancel(eq(123), eq(true)); - verify(mUserBackupManagerService).handleCancel(eq(456), eq(true)); - verify(mUserBackupManagerService).handleCancel(eq(789), eq(true)); + verify(mUserBackupManagerService) + .handleCancel(eq(123), eq(CancellationReason.AGENT_DISCONNECTED)); + verify(mUserBackupManagerService) + .handleCancel(eq(456), eq(CancellationReason.AGENT_DISCONNECTED)); + verify(mUserBackupManagerService) + .handleCancel(eq(789), eq(CancellationReason.AGENT_DISCONNECTED)); } @Test diff --git a/services/tests/mockingservicestests/src/com/android/server/power/ScreenUndimDetectorTest.java b/services/tests/mockingservicestests/src/com/android/server/power/ScreenUndimDetectorTest.java index 8ce05e2fa115..c9f86b04be22 100644 --- a/services/tests/mockingservicestests/src/com/android/server/power/ScreenUndimDetectorTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/power/ScreenUndimDetectorTest.java @@ -23,7 +23,6 @@ import static android.hardware.display.DisplayManagerInternal.DisplayPowerReques import static android.provider.DeviceConfig.NAMESPACE_ATTENTION_MANAGER_SERVICE; import static android.view.Display.DEFAULT_DISPLAY_GROUP; -import static com.android.server.power.ScreenUndimDetector.DEFAULT_MAX_DURATION_BETWEEN_UNDIMS_MILLIS; import static com.android.server.power.ScreenUndimDetector.KEY_KEEP_SCREEN_ON_ENABLED; import static com.android.server.power.ScreenUndimDetector.KEY_MAX_DURATION_BETWEEN_UNDIMS_MILLIS; import static com.android.server.power.ScreenUndimDetector.KEY_UNDIMS_REQUIRED; @@ -49,6 +48,7 @@ import org.junit.runners.JUnit4; import java.util.Arrays; import java.util.List; +import java.util.concurrent.TimeUnit; /** * Tests for {@link com.android.server.power.ScreenUndimDetector} @@ -61,7 +61,8 @@ public class ScreenUndimDetectorTest { POLICY_DIM, POLICY_BRIGHT); private static final int OTHER_DISPLAY_GROUP = DEFAULT_DISPLAY_GROUP + 1; - + private static final long DEFAULT_MAX_DURATION_BETWEEN_UNDIMS_MILLIS = + TimeUnit.MINUTES.toMillis(5); @ClassRule public static final TestableContext sContext = new TestableContext( InstrumentationRegistry.getInstrumentation().getTargetContext(), null); @@ -88,7 +89,8 @@ public class ScreenUndimDetectorTest { @Before public void setup() { InstrumentationRegistry.getInstrumentation().waitForIdleSync(); - + DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE, + KEY_KEEP_SCREEN_ON_ENABLED, Boolean.TRUE.toString(), false /*makeDefault*/); DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE, KEY_UNDIMS_REQUIRED, Integer.toString(1), false /*makeDefault*/); @@ -108,10 +110,10 @@ public class ScreenUndimDetectorTest { @Test public void recordScreenPolicy_disabledByFlag_noop() { + setup(); DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE, KEY_KEEP_SCREEN_ON_ENABLED, Boolean.FALSE.toString(), false /*makeDefault*/); mScreenUndimDetector.readValuesFromDeviceConfig(); - mScreenUndimDetector.recordScreenPolicy(DEFAULT_DISPLAY_GROUP, POLICY_DIM); mScreenUndimDetector.recordScreenPolicy(DEFAULT_DISPLAY_GROUP, POLICY_BRIGHT); diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java index 49c37f163ff2..241ffdc19ce4 100644 --- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java @@ -36,24 +36,29 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.mockito.MockitoAnnotations.initMocks; +import android.content.res.Resources; import android.graphics.Point; import android.graphics.Rect; import android.platform.test.annotations.Presubmit; import android.platform.test.annotations.RequiresFlagsEnabled; +import android.util.ArraySet; import android.util.Log; import android.util.SparseArray; import android.view.Display; import android.view.DisplayInfo; +import android.view.WindowInsets; +import android.view.WindowManager; +import android.view.WindowMetrics; import androidx.test.runner.AndroidJUnit4; import com.android.dx.mockito.inline.extended.ExtendedMockito; import com.android.dx.mockito.inline.extended.StaticMockitoSession; +import com.android.internal.R; import org.junit.AfterClass; import org.junit.Before; @@ -70,10 +75,11 @@ import java.io.File; import java.io.IOException; import java.util.Comparator; import java.util.List; +import java.util.Set; /** * Unit tests for the most important helpers of {@link WallpaperCropper}, in particular - * {@link WallpaperCropper#getCrop(Point, Point, SparseArray, boolean)}. + * {@link WallpaperCropper#getCrop(Point, WallpaperDefaultDisplayInfo, Point, SparseArray, boolean)}. */ @Presubmit @RunWith(AndroidJUnit4.class) @@ -83,6 +89,12 @@ public class WallpaperCropperTest { @Mock private WallpaperDisplayHelper mWallpaperDisplayHelper; + + @Mock + private WindowManager mWindowManager; + + @Mock + private Resources mResources; private WallpaperCropper mWallpaperCropper; private static final Point PORTRAIT_ONE = new Point(500, 800); @@ -175,14 +187,21 @@ public class WallpaperCropperTest { return tempDir; } - private void setUpWithDisplays(List<Point> displaySizes) { + private WallpaperDefaultDisplayInfo setUpWithDisplays(List<Point> displaySizes) { mDisplaySizes = new SparseArray<>(); displaySizes.forEach(size -> { mDisplaySizes.put(getOrientation(size), size); Point rotated = new Point(size.y, size.x); mDisplaySizes.put(getOrientation(rotated), rotated); }); + Set<WindowMetrics> windowMetrics = new ArraySet<>(); + for (Point displaySize : displaySizes) { + windowMetrics.add( + new WindowMetrics(new Rect(0, 0, displaySize.x, displaySize.y), + new WindowInsets.Builder().build())); + } when(mWallpaperDisplayHelper.getDefaultDisplaySizes()).thenReturn(mDisplaySizes); + when(mWindowManager.getPossibleMaximumWindowMetrics(anyInt())).thenReturn(windowMetrics); if (displaySizes.size() == 2) { Point largestDisplay = displaySizes.stream().max( Comparator.comparingInt(p -> p.x * p.y)).get(); @@ -192,11 +211,16 @@ public class WallpaperCropperTest { mFolded = getOrientation(smallestDisplay); mUnfoldedRotated = getRotatedOrientation(mUnfolded); mFoldedRotated = getRotatedOrientation(mFolded); + // foldable + doReturn(new int[]{0}).when(mResources).getIntArray(R.array.config_foldedDeviceStates); + } else { + // no foldable + doReturn(new int[]{}).when(mResources).getIntArray(R.array.config_foldedDeviceStates); } - doAnswer(invocation -> getFoldedOrientation(invocation.getArgument(0))) - .when(mWallpaperDisplayHelper).getFoldedOrientation(anyInt()); - doAnswer(invocation -> getUnfoldedOrientation(invocation.getArgument(0))) - .when(mWallpaperDisplayHelper).getUnfoldedOrientation(anyInt()); + WallpaperDefaultDisplayInfo defaultDisplayInfo = new WallpaperDefaultDisplayInfo( + mWindowManager, mResources); + when(mWallpaperDisplayHelper.getDefaultDisplayInfo()).thenReturn(defaultDisplayInfo); + return defaultDisplayInfo; } private int getFoldedOrientation(int orientation) { @@ -435,7 +459,7 @@ public class WallpaperCropperTest { */ @Test public void testGetCrop_noSuggestedCrops() { - setUpWithDisplays(STANDARD_DISPLAY); + WallpaperDefaultDisplayInfo defaultDisplayInfo = setUpWithDisplays(STANDARD_DISPLAY); Point bitmapSize = new Point(800, 1000); Rect bitmapRect = new Rect(0, 0, bitmapSize.x, bitmapSize.y); SparseArray<Rect> suggestedCrops = new SparseArray<>(); @@ -455,8 +479,9 @@ public class WallpaperCropperTest { for (boolean rtl : List.of(false, true)) { Rect expectedCrop = rtl ? rightOf(bitmapRect, expectedCropSize) : leftOf(bitmapRect, expectedCropSize); - assertThat(mWallpaperCropper.getCrop( - displaySize, bitmapSize, suggestedCrops, rtl)) + assertThat( + WallpaperCropper.getCrop( + displaySize, defaultDisplayInfo, bitmapSize, suggestedCrops, rtl)) .isEqualTo(expectedCrop); } } @@ -469,7 +494,7 @@ public class WallpaperCropperTest { */ @Test public void testGetCrop_hasSuggestedCrop() { - setUpWithDisplays(STANDARD_DISPLAY); + WallpaperDefaultDisplayInfo defaultDisplayInfo = setUpWithDisplays(STANDARD_DISPLAY); Point bitmapSize = new Point(800, 1000); SparseArray<Rect> suggestedCrops = new SparseArray<>(); suggestedCrops.put(ORIENTATION_PORTRAIT, new Rect(0, 0, 400, 800)); @@ -479,11 +504,13 @@ public class WallpaperCropperTest { } for (boolean rtl : List.of(false, true)) { - assertThat(mWallpaperCropper.getCrop( - new Point(300, 800), bitmapSize, suggestedCrops, rtl)) + assertThat( + WallpaperCropper.getCrop(new Point(300, 800), defaultDisplayInfo, bitmapSize, + suggestedCrops, rtl)) .isEqualTo(suggestedCrops.get(ORIENTATION_PORTRAIT)); - assertThat(mWallpaperCropper.getCrop( - new Point(500, 800), bitmapSize, suggestedCrops, rtl)) + assertThat( + WallpaperCropper.getCrop(new Point(500, 800), defaultDisplayInfo, bitmapSize, + suggestedCrops, rtl)) .isEqualTo(new Rect(0, 0, 500, 800)); } } @@ -499,7 +526,7 @@ public class WallpaperCropperTest { */ @Test public void testGetCrop_hasRotatedSuggestedCrop() { - setUpWithDisplays(STANDARD_DISPLAY); + WallpaperDefaultDisplayInfo defaultDisplayInfo = setUpWithDisplays(STANDARD_DISPLAY); Point bitmapSize = new Point(2000, 1800); Rect bitmapRect = new Rect(0, 0, bitmapSize.x, bitmapSize.y); SparseArray<Rect> suggestedCrops = new SparseArray<>(); @@ -510,12 +537,14 @@ public class WallpaperCropperTest { suggestedCrops.put(ORIENTATION_PORTRAIT, centerOf(bitmapRect, portrait)); suggestedCrops.put(ORIENTATION_SQUARE_LANDSCAPE, centerOf(bitmapRect, squareLandscape)); for (boolean rtl : List.of(false, true)) { - assertThat(mWallpaperCropper.getCrop( - landscape, bitmapSize, suggestedCrops, rtl)) + assertThat( + WallpaperCropper.getCrop(landscape, defaultDisplayInfo, bitmapSize, + suggestedCrops, rtl)) .isEqualTo(centerOf(bitmapRect, landscape)); - assertThat(mWallpaperCropper.getCrop( - squarePortrait, bitmapSize, suggestedCrops, rtl)) + assertThat( + WallpaperCropper.getCrop(squarePortrait, defaultDisplayInfo, bitmapSize, + suggestedCrops, rtl)) .isEqualTo(centerOf(bitmapRect, squarePortrait)); } } @@ -532,7 +561,7 @@ public class WallpaperCropperTest { @Test public void testGetCrop_hasUnfoldedSuggestedCrop() { for (List<Point> displaySizes : ALL_FOLDABLE_DISPLAYS) { - setUpWithDisplays(displaySizes); + WallpaperDefaultDisplayInfo defaultDisplayInfo = setUpWithDisplays(displaySizes); Point bitmapSize = new Point(2000, 2400); Rect bitmapRect = new Rect(0, 0, bitmapSize.x, bitmapSize.y); @@ -569,8 +598,9 @@ public class WallpaperCropperTest { expectedCrop.right = Math.min( unfoldedCrop.right, unfoldedCrop.right + maxParallax); } - assertThat(mWallpaperCropper.getCrop( - foldedDisplay, bitmapSize, suggestedCrops, rtl)) + assertThat( + WallpaperCropper.getCrop(foldedDisplay, defaultDisplayInfo, bitmapSize, + suggestedCrops, rtl)) .isEqualTo(expectedCrop); } } @@ -588,7 +618,7 @@ public class WallpaperCropperTest { @Test public void testGetCrop_hasFoldedSuggestedCrop() { for (List<Point> displaySizes : ALL_FOLDABLE_DISPLAYS) { - setUpWithDisplays(displaySizes); + WallpaperDefaultDisplayInfo defaultDisplayInfo = setUpWithDisplays(displaySizes); Point bitmapSize = new Point(2000, 2000); Rect bitmapRect = new Rect(0, 0, 2000, 2000); @@ -610,12 +640,14 @@ public class WallpaperCropperTest { Point unfoldedDisplayTwo = mDisplaySizes.get(unfoldedTwo); for (boolean rtl : List.of(false, true)) { - assertThat(centerOf(mWallpaperCropper.getCrop( - unfoldedDisplayOne, bitmapSize, suggestedCrops, rtl), foldedDisplayOne)) + assertThat(centerOf( + WallpaperCropper.getCrop(unfoldedDisplayOne, defaultDisplayInfo, bitmapSize, + suggestedCrops, rtl), foldedDisplayOne)) .isEqualTo(foldedCropOne); - assertThat(centerOf(mWallpaperCropper.getCrop( - unfoldedDisplayTwo, bitmapSize, suggestedCrops, rtl), foldedDisplayTwo)) + assertThat(centerOf( + WallpaperCropper.getCrop(unfoldedDisplayTwo, defaultDisplayInfo, bitmapSize, + suggestedCrops, rtl), foldedDisplayTwo)) .isEqualTo(foldedCropTwo); } } @@ -633,7 +665,7 @@ public class WallpaperCropperTest { @Test public void testGetCrop_hasRotatedUnfoldedSuggestedCrop() { for (List<Point> displaySizes : ALL_FOLDABLE_DISPLAYS) { - setUpWithDisplays(displaySizes); + WallpaperDefaultDisplayInfo defaultDisplayInfo = setUpWithDisplays(displaySizes); Point bitmapSize = new Point(2000, 2000); Rect bitmapRect = new Rect(0, 0, 2000, 2000); Point largestDisplay = displaySizes.stream().max( @@ -650,8 +682,9 @@ public class WallpaperCropperTest { Point rotatedFoldedDisplay = mDisplaySizes.get(rotatedFolded); for (boolean rtl : List.of(false, true)) { - assertThat(mWallpaperCropper.getCrop( - rotatedFoldedDisplay, bitmapSize, suggestedCrops, rtl)) + assertThat( + WallpaperCropper.getCrop(rotatedFoldedDisplay, defaultDisplayInfo, + bitmapSize, suggestedCrops, rtl)) .isEqualTo(centerOf(rotatedUnfoldedCrop, rotatedFoldedDisplay)); } } @@ -670,7 +703,7 @@ public class WallpaperCropperTest { @Test public void testGetCrop_hasRotatedFoldedSuggestedCrop() { for (List<Point> displaySizes : ALL_FOLDABLE_DISPLAYS) { - setUpWithDisplays(displaySizes); + WallpaperDefaultDisplayInfo defaultDisplayInfo = setUpWithDisplays(displaySizes); Point bitmapSize = new Point(2000, 2000); Rect bitmapRect = new Rect(0, 0, 2000, 2000); @@ -689,8 +722,8 @@ public class WallpaperCropperTest { Point rotatedUnfoldedDisplay = mDisplaySizes.get(rotatedUnfolded); for (boolean rtl : List.of(false, true)) { - Rect rotatedUnfoldedCrop = mWallpaperCropper.getCrop( - rotatedUnfoldedDisplay, bitmapSize, suggestedCrops, rtl); + Rect rotatedUnfoldedCrop = WallpaperCropper.getCrop(rotatedUnfoldedDisplay, + defaultDisplayInfo, bitmapSize, suggestedCrops, rtl); assertThat(centerOf(rotatedUnfoldedCrop, rotatedFoldedDisplay)) .isEqualTo(rotatedFoldedCrop); } diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperDefaultDisplayInfoTest.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperDefaultDisplayInfoTest.java new file mode 100644 index 000000000000..312db91afb12 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperDefaultDisplayInfoTest.java @@ -0,0 +1,340 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wallpaper; + +import static android.app.WallpaperManager.ORIENTATION_LANDSCAPE; +import static android.app.WallpaperManager.ORIENTATION_PORTRAIT; +import static android.app.WallpaperManager.ORIENTATION_SQUARE_LANDSCAPE; +import static android.app.WallpaperManager.ORIENTATION_SQUARE_PORTRAIT; +import static android.app.WallpaperManager.ORIENTATION_UNKNOWN; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.when; +import static org.mockito.MockitoAnnotations.initMocks; + +import android.content.res.Resources; +import android.graphics.Point; +import android.graphics.Rect; +import android.platform.test.annotations.Presubmit; +import android.util.SparseArray; +import android.view.WindowInsets; +import android.view.WindowManager; +import android.view.WindowMetrics; + +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.R; +import com.android.server.wallpaper.WallpaperDefaultDisplayInfo.FoldableOrientations; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; + +import java.util.Set; + +/** Unit tests for {@link WallpaperDefaultDisplayInfo}. */ +@Presubmit +@RunWith(AndroidJUnit4.class) +public class WallpaperDefaultDisplayInfoTest { + @Mock + private WindowManager mWindowManager; + + @Mock + private Resources mResources; + + @Before + public void setUp() { + initMocks(this); + } + + @Test + public void defaultDisplayInfo_foldable_shouldHaveExpectedContent() { + doReturn(new int[]{0}).when(mResources).getIntArray(eq(R.array.config_foldedDeviceStates)); + Rect innerDisplayBounds = new Rect(0, 0, 2076, 2152); + Rect outerDisplayBounds = new Rect(0, 0, 1080, 2424); + WindowMetrics innerDisplayMetrics = + new WindowMetrics(innerDisplayBounds, new WindowInsets.Builder().build(), + /* density= */ 2.4375f); + WindowMetrics outerDisplayMetrics = + new WindowMetrics(outerDisplayBounds, new WindowInsets.Builder().build(), + /* density= */ 2.4375f); + when(mWindowManager.getPossibleMaximumWindowMetrics(anyInt())) + .thenReturn(Set.of(innerDisplayMetrics, outerDisplayMetrics)); + + WallpaperDefaultDisplayInfo defaultDisplayInfo = new WallpaperDefaultDisplayInfo( + mWindowManager, mResources); + + SparseArray<Point> displaySizes = new SparseArray<>(); + displaySizes.put(ORIENTATION_PORTRAIT, new Point(1080, 2424)); + displaySizes.put(ORIENTATION_LANDSCAPE, new Point(2424, 1080)); + displaySizes.put(ORIENTATION_SQUARE_PORTRAIT, new Point(2076, 2152)); + displaySizes.put(ORIENTATION_SQUARE_LANDSCAPE, new Point(2152, 2076)); + assertThat(defaultDisplayInfo.defaultDisplaySizes.contentEquals(displaySizes)).isTrue(); + assertThat(defaultDisplayInfo.isFoldable).isTrue(); + assertThat(defaultDisplayInfo.isLargeScreen).isFalse(); + assertThat(defaultDisplayInfo.foldableOrientations).containsExactly( + new FoldableOrientations( + /* foldedOrientation= */ ORIENTATION_PORTRAIT, + /* unfoldedOrientation= */ ORIENTATION_SQUARE_PORTRAIT), + new FoldableOrientations( + /* foldedOrientation= */ ORIENTATION_LANDSCAPE, + /* unfoldedOrientation= */ ORIENTATION_SQUARE_LANDSCAPE)); + } + + @Test + public void defaultDisplayInfo_tablet_shouldHaveExpectedContent() { + doReturn(new int[]{}).when(mResources).getIntArray(eq(R.array.config_foldedDeviceStates)); + Rect displayBounds = new Rect(0, 0, 2560, 1600); + WindowMetrics displayMetrics = + new WindowMetrics(displayBounds, new WindowInsets.Builder().build(), + /* density= */ 2f); + when(mWindowManager.getPossibleMaximumWindowMetrics(anyInt())) + .thenReturn(Set.of(displayMetrics)); + + WallpaperDefaultDisplayInfo defaultDisplayInfo = new WallpaperDefaultDisplayInfo( + mWindowManager, mResources); + + SparseArray<Point> displaySizes = new SparseArray<>(); + displaySizes.put(ORIENTATION_PORTRAIT, new Point(1600, 2560)); + displaySizes.put(ORIENTATION_LANDSCAPE, new Point(2560, 1600)); + assertThat(defaultDisplayInfo.defaultDisplaySizes.contentEquals(displaySizes)).isTrue(); + assertThat(defaultDisplayInfo.isFoldable).isFalse(); + assertThat(defaultDisplayInfo.isLargeScreen).isTrue(); + assertThat(defaultDisplayInfo.foldableOrientations).isEmpty(); + } + + @Test + public void defaultDisplayInfo_phone_shouldHaveExpectedContent() { + doReturn(new int[]{}).when(mResources).getIntArray(eq(R.array.config_foldedDeviceStates)); + Rect displayBounds = new Rect(0, 0, 1280, 2856); + WindowMetrics displayMetrics = + new WindowMetrics(displayBounds, new WindowInsets.Builder().build(), + /* density= */ 3f); + when(mWindowManager.getPossibleMaximumWindowMetrics(anyInt())) + .thenReturn(Set.of(displayMetrics)); + + WallpaperDefaultDisplayInfo defaultDisplayInfo = new WallpaperDefaultDisplayInfo( + mWindowManager, mResources); + + SparseArray<Point> displaySizes = new SparseArray<>(); + displaySizes.put(ORIENTATION_PORTRAIT, new Point(1280, 2856)); + displaySizes.put(ORIENTATION_LANDSCAPE, new Point(2856, 1280)); + assertThat(defaultDisplayInfo.defaultDisplaySizes.contentEquals(displaySizes)).isTrue(); + assertThat(defaultDisplayInfo.isFoldable).isFalse(); + assertThat(defaultDisplayInfo.isLargeScreen).isFalse(); + assertThat(defaultDisplayInfo.foldableOrientations).isEmpty(); + } + + @Test + public void defaultDisplayInfo_equals_sameContent_shouldEqual() { + doReturn(new int[]{0}).when(mResources).getIntArray(eq(R.array.config_foldedDeviceStates)); + Rect innerDisplayBounds = new Rect(0, 0, 2076, 2152); + Rect outerDisplayBounds = new Rect(0, 0, 1080, 2424); + WindowMetrics innerDisplayMetrics = + new WindowMetrics(innerDisplayBounds, new WindowInsets.Builder().build(), + /* density= */ 2.4375f); + WindowMetrics outerDisplayMetrics = + new WindowMetrics(outerDisplayBounds, new WindowInsets.Builder().build(), + /* density= */ 2.4375f); + when(mWindowManager.getPossibleMaximumWindowMetrics(anyInt())) + .thenReturn(Set.of(innerDisplayMetrics, outerDisplayMetrics)); + + WallpaperDefaultDisplayInfo defaultDisplayInfo = new WallpaperDefaultDisplayInfo( + mWindowManager, mResources); + WallpaperDefaultDisplayInfo otherDefaultDisplayInfo = new WallpaperDefaultDisplayInfo( + mWindowManager, mResources); + + assertThat(defaultDisplayInfo).isEqualTo(otherDefaultDisplayInfo); + } + + @Test + public void defaultDisplayInfo_equals_differentBounds_shouldNotEqual() { + doReturn(new int[]{0}).when(mResources).getIntArray(eq(R.array.config_foldedDeviceStates)); + Rect innerDisplayBounds = new Rect(0, 0, 2076, 2152); + Rect outerDisplayBounds = new Rect(0, 0, 1080, 2424); + WindowMetrics innerDisplayMetrics = + new WindowMetrics(innerDisplayBounds, new WindowInsets.Builder().build(), + /* density= */ 2.4375f); + WindowMetrics outerDisplayMetrics = + new WindowMetrics(outerDisplayBounds, new WindowInsets.Builder().build(), + /* density= */ 2.4375f); + when(mWindowManager.getPossibleMaximumWindowMetrics(anyInt())) + // For the first call + .thenReturn(Set.of(innerDisplayMetrics, outerDisplayMetrics)) + // For the second+ call + .thenReturn(Set.of(innerDisplayMetrics)); + + WallpaperDefaultDisplayInfo defaultDisplayInfo = new WallpaperDefaultDisplayInfo( + mWindowManager, mResources); + WallpaperDefaultDisplayInfo otherDefaultDisplayInfo = new WallpaperDefaultDisplayInfo( + mWindowManager, mResources); + + assertThat(defaultDisplayInfo).isNotEqualTo(otherDefaultDisplayInfo); + } + + @Test + public void defaultDisplayInfo_hashCode_sameContent_shouldEqual() { + doReturn(new int[]{0}).when(mResources).getIntArray(eq(R.array.config_foldedDeviceStates)); + Rect innerDisplayBounds = new Rect(0, 0, 2076, 2152); + Rect outerDisplayBounds = new Rect(0, 0, 1080, 2424); + WindowMetrics innerDisplayMetrics = + new WindowMetrics(innerDisplayBounds, new WindowInsets.Builder().build(), + /* density= */ 2.4375f); + WindowMetrics outerDisplayMetrics = + new WindowMetrics(outerDisplayBounds, new WindowInsets.Builder().build(), + /* density= */ 2.4375f); + when(mWindowManager.getPossibleMaximumWindowMetrics(anyInt())) + .thenReturn(Set.of(innerDisplayMetrics, outerDisplayMetrics)); + + WallpaperDefaultDisplayInfo defaultDisplayInfo = new WallpaperDefaultDisplayInfo( + mWindowManager, mResources); + WallpaperDefaultDisplayInfo otherDefaultDisplayInfo = new WallpaperDefaultDisplayInfo( + mWindowManager, mResources); + + assertThat(defaultDisplayInfo.hashCode()).isEqualTo(otherDefaultDisplayInfo.hashCode()); + } + + @Test + public void defaultDisplayInfo_hashCode_differentBounds_shouldNotEqual() { + doReturn(new int[]{0}).when(mResources).getIntArray(eq(R.array.config_foldedDeviceStates)); + Rect innerDisplayBounds = new Rect(0, 0, 2076, 2152); + Rect outerDisplayBounds = new Rect(0, 0, 1080, 2424); + WindowMetrics innerDisplayMetrics = + new WindowMetrics(innerDisplayBounds, new WindowInsets.Builder().build(), + /* density= */ 2.4375f); + WindowMetrics outerDisplayMetrics = + new WindowMetrics(outerDisplayBounds, new WindowInsets.Builder().build(), + /* density= */ 2.4375f); + when(mWindowManager.getPossibleMaximumWindowMetrics(anyInt())) + // For the first call + .thenReturn(Set.of(innerDisplayMetrics, outerDisplayMetrics)) + // For the second+ call + .thenReturn(Set.of(innerDisplayMetrics)); + + WallpaperDefaultDisplayInfo defaultDisplayInfo = new WallpaperDefaultDisplayInfo( + mWindowManager, mResources); + WallpaperDefaultDisplayInfo otherDefaultDisplayInfo = new WallpaperDefaultDisplayInfo( + mWindowManager, mResources); + + assertThat(defaultDisplayInfo.hashCode()).isNotEqualTo(otherDefaultDisplayInfo.hashCode()); + } + + @Test + public void getFoldedOrientation_foldable_shouldReturnExpectedOrientation() { + doReturn(new int[]{0}).when(mResources).getIntArray(eq(R.array.config_foldedDeviceStates)); + Rect innerDisplayBounds = new Rect(0, 0, 2076, 2152); + Rect outerDisplayBounds = new Rect(0, 0, 1080, 2424); + WindowMetrics innerDisplayMetrics = + new WindowMetrics(innerDisplayBounds, new WindowInsets.Builder().build(), + /* density= */ 2.4375f); + WindowMetrics outerDisplayMetrics = + new WindowMetrics(outerDisplayBounds, new WindowInsets.Builder().build(), + /* density= */ 2.4375f); + when(mWindowManager.getPossibleMaximumWindowMetrics(anyInt())) + .thenReturn(Set.of(innerDisplayMetrics, outerDisplayMetrics)); + WallpaperDefaultDisplayInfo defaultDisplayInfo = new WallpaperDefaultDisplayInfo( + mWindowManager, mResources); + + assertThat(defaultDisplayInfo.getFoldedOrientation(ORIENTATION_SQUARE_PORTRAIT)) + .isEqualTo(ORIENTATION_PORTRAIT); + assertThat(defaultDisplayInfo.getFoldedOrientation(ORIENTATION_SQUARE_LANDSCAPE)) + .isEqualTo(ORIENTATION_LANDSCAPE); + // Use a folded orientation for a folded orientation should return unknown. + assertThat(defaultDisplayInfo.getFoldedOrientation(ORIENTATION_PORTRAIT)) + .isEqualTo(ORIENTATION_UNKNOWN); + assertThat(defaultDisplayInfo.getFoldedOrientation(ORIENTATION_LANDSCAPE)) + .isEqualTo(ORIENTATION_UNKNOWN); + } + + @Test + public void getUnfoldedOrientation_foldable_shouldReturnExpectedOrientation() { + doReturn(new int[]{0}).when(mResources).getIntArray(eq(R.array.config_foldedDeviceStates)); + Rect innerDisplayBounds = new Rect(0, 0, 2076, 2152); + Rect outerDisplayBounds = new Rect(0, 0, 1080, 2424); + WindowMetrics innerDisplayMetrics = + new WindowMetrics(innerDisplayBounds, new WindowInsets.Builder().build(), + /* density= */ 2.4375f); + WindowMetrics outerDisplayMetrics = + new WindowMetrics(outerDisplayBounds, new WindowInsets.Builder().build(), + /* density= */ 2.4375f); + when(mWindowManager.getPossibleMaximumWindowMetrics(anyInt())) + .thenReturn(Set.of(innerDisplayMetrics, outerDisplayMetrics)); + WallpaperDefaultDisplayInfo defaultDisplayInfo = new WallpaperDefaultDisplayInfo( + mWindowManager, mResources); + + assertThat(defaultDisplayInfo.getUnfoldedOrientation(ORIENTATION_PORTRAIT)) + .isEqualTo(ORIENTATION_SQUARE_PORTRAIT); + assertThat(defaultDisplayInfo.getUnfoldedOrientation(ORIENTATION_LANDSCAPE)) + .isEqualTo(ORIENTATION_SQUARE_LANDSCAPE); + // Use an unfolded orientation for an unfolded orientation should return unknown. + assertThat(defaultDisplayInfo.getUnfoldedOrientation(ORIENTATION_SQUARE_PORTRAIT)) + .isEqualTo(ORIENTATION_UNKNOWN); + assertThat(defaultDisplayInfo.getUnfoldedOrientation(ORIENTATION_SQUARE_LANDSCAPE)) + .isEqualTo(ORIENTATION_UNKNOWN); + } + + @Test + public void getFoldedOrientation_nonFoldable_shouldReturnUnknown() { + doReturn(new int[]{}).when(mResources).getIntArray(eq(R.array.config_foldedDeviceStates)); + Rect displayBounds = new Rect(0, 0, 2560, 1600); + WindowMetrics displayMetrics = + new WindowMetrics(displayBounds, new WindowInsets.Builder().build(), + /* density= */ 2f); + when(mWindowManager.getPossibleMaximumWindowMetrics(anyInt())) + .thenReturn(Set.of(displayMetrics)); + + WallpaperDefaultDisplayInfo defaultDisplayInfo = new WallpaperDefaultDisplayInfo( + mWindowManager, mResources); + + assertThat(defaultDisplayInfo.getFoldedOrientation(ORIENTATION_SQUARE_PORTRAIT)) + .isEqualTo(ORIENTATION_UNKNOWN); + assertThat(defaultDisplayInfo.getFoldedOrientation(ORIENTATION_SQUARE_LANDSCAPE)) + .isEqualTo(ORIENTATION_UNKNOWN); + assertThat(defaultDisplayInfo.getFoldedOrientation(ORIENTATION_PORTRAIT)) + .isEqualTo(ORIENTATION_UNKNOWN); + assertThat(defaultDisplayInfo.getFoldedOrientation(ORIENTATION_LANDSCAPE)) + .isEqualTo(ORIENTATION_UNKNOWN); + } + + @Test + public void getUnFoldedOrientation_nonFoldable_shouldReturnUnknown() { + doReturn(new int[]{}).when(mResources).getIntArray(eq(R.array.config_foldedDeviceStates)); + Rect displayBounds = new Rect(0, 0, 2560, 1600); + WindowMetrics displayMetrics = + new WindowMetrics(displayBounds, new WindowInsets.Builder().build(), + /* density= */ 2f); + when(mWindowManager.getPossibleMaximumWindowMetrics(anyInt())) + .thenReturn(Set.of(displayMetrics)); + + WallpaperDefaultDisplayInfo defaultDisplayInfo = new WallpaperDefaultDisplayInfo( + mWindowManager, mResources); + + assertThat(defaultDisplayInfo.getUnfoldedOrientation(ORIENTATION_SQUARE_PORTRAIT)) + .isEqualTo(ORIENTATION_UNKNOWN); + assertThat(defaultDisplayInfo.getUnfoldedOrientation(ORIENTATION_SQUARE_LANDSCAPE)) + .isEqualTo(ORIENTATION_UNKNOWN); + assertThat(defaultDisplayInfo.getUnfoldedOrientation(ORIENTATION_PORTRAIT)) + .isEqualTo(ORIENTATION_UNKNOWN); + assertThat(defaultDisplayInfo.getUnfoldedOrientation(ORIENTATION_LANDSCAPE)) + .isEqualTo(ORIENTATION_UNKNOWN); + } +} diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java index 5165e34c7fcd..fc864dd230d9 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java @@ -22,6 +22,7 @@ import static com.google.common.truth.Truth.assertWithMessage; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.spy; @@ -330,31 +331,24 @@ public class BatteryStatsHistoryTest { return invocation.callRealMethod(); }).when(mHistory).readFragmentToParcel(any(), any()); - // Prepare history for iteration - mHistory.iterate(0, MonotonicClock.UNDEFINED); - - Parcel parcel = mHistory.getNextParcel(0, Long.MAX_VALUE); - assertThat(parcel).isNotNull(); - assertThat(mReadFiles).containsExactly("123.bh"); - - // Skip to the end to force reading the next parcel - parcel.setDataPosition(parcel.dataSize()); - mReadFiles.clear(); - parcel = mHistory.getNextParcel(0, Long.MAX_VALUE); - assertThat(parcel).isNotNull(); - assertThat(mReadFiles).containsExactly("1000.bh"); - - parcel.setDataPosition(parcel.dataSize()); - mReadFiles.clear(); - parcel = mHistory.getNextParcel(0, Long.MAX_VALUE); - assertThat(parcel).isNotNull(); - assertThat(mReadFiles).containsExactly("2000.bh"); + int eventsRead = 0; + BatteryStatsHistoryIterator iterator = mHistory.iterate(0, MonotonicClock.UNDEFINED); + while (iterator.hasNext()) { + HistoryItem item = iterator.next(); + if (item.eventCode == HistoryItem.EVENT_JOB_START) { + eventsRead++; + assertThat(mReadFiles).containsExactly("123.bh"); + } else if (item.eventCode == HistoryItem.EVENT_JOB_FINISH) { + eventsRead++; + assertThat(mReadFiles).containsExactly("123.bh", "1000.bh"); + } else if (item.eventCode == HistoryItem.EVENT_ALARM) { + eventsRead++; + assertThat(mReadFiles).containsExactly("123.bh", "1000.bh", "2000.bh"); + } + } - parcel.setDataPosition(parcel.dataSize()); - mReadFiles.clear(); - parcel = mHistory.getNextParcel(0, Long.MAX_VALUE); - assertThat(parcel).isNull(); - assertThat(mReadFiles).isEmpty(); + assertThat(eventsRead).isEqualTo(3); + assertThat(mReadFiles).containsExactly("123.bh", "1000.bh", "2000.bh", "3000.bh"); } @Test @@ -372,25 +366,19 @@ public class BatteryStatsHistoryTest { return invocation.callRealMethod(); }).when(mHistory).readFragmentToParcel(any(), any()); - // Prepare history for iteration - mHistory.iterate(1000, 3000); - - Parcel parcel = mHistory.getNextParcel(1000, 3000); - assertThat(parcel).isNotNull(); - assertThat(mReadFiles).containsExactly("1000.bh"); - - // Skip to the end to force reading the next parcel - parcel.setDataPosition(parcel.dataSize()); - mReadFiles.clear(); - parcel = mHistory.getNextParcel(1000, 3000); - assertThat(parcel).isNotNull(); - assertThat(mReadFiles).containsExactly("2000.bh"); + BatteryStatsHistoryIterator iterator = mHistory.iterate(1000, 3000); + while (iterator.hasNext()) { + HistoryItem item = iterator.next(); + if (item.eventCode == HistoryItem.EVENT_JOB_START) { + fail("Event outside the range"); + } else if (item.eventCode == HistoryItem.EVENT_JOB_FINISH) { + assertThat(mReadFiles).containsExactly("1000.bh"); + } else if (item.eventCode == HistoryItem.EVENT_ALARM) { + fail("Event outside the range"); + } + } - parcel.setDataPosition(parcel.dataSize()); - mReadFiles.clear(); - parcel = mHistory.getNextParcel(1000, 3000); - assertThat(parcel).isNull(); - assertThat(mReadFiles).isEmpty(); + assertThat(mReadFiles).containsExactly("1000.bh", "2000.bh"); } private void prepareMultiFileHistory() { diff --git a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java index ea83825cd810..ea25e7992dd9 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java @@ -43,6 +43,7 @@ import android.view.accessibility.AccessibilityManager; import com.android.internal.accessibility.util.AccessibilityUtils; import com.android.server.accessibility.AccessibilityTraceManager; +import com.android.server.accessibility.BaseEventStreamTransformation; import org.junit.After; import org.junit.Before; @@ -70,6 +71,19 @@ public class AutoclickControllerTest { @Mock private WindowManager mMockWindowManager; private AutoclickController mController; + private static class MotionEventCaptor extends BaseEventStreamTransformation { + public MotionEvent downEvent; + + @Override + public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + downEvent = event; + break; + } + } + } + @Before public void setUp() { mTestableLooper = TestableLooper.get(this); @@ -660,6 +674,153 @@ public class AutoclickControllerTest { assertThat(mController.mClickScheduler.getScheduledClickTimeForTesting()).isEqualTo(-1); } + @Test + @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR) + public void onMotionEvent_flagOn_lazyInitAutoclickScrollPanel() { + assertThat(mController.mAutoclickScrollPanel).isNull(); + + injectFakeMouseActionHoverMoveEvent(); + + assertThat(mController.mAutoclickScrollPanel).isNotNull(); + } + + @Test + @DisableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR) + public void onMotionEvent_flagOff_notInitAutoclickScrollPanel() { + assertThat(mController.mAutoclickScrollPanel).isNull(); + + injectFakeMouseActionHoverMoveEvent(); + + assertThat(mController.mAutoclickScrollPanel).isNull(); + } + + @Test + @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR) + public void onDestroy_flagOn_hideAutoclickScrollPanel() { + injectFakeMouseActionHoverMoveEvent(); + AutoclickScrollPanel mockAutoclickScrollPanel = mock(AutoclickScrollPanel.class); + mController.mAutoclickScrollPanel = mockAutoclickScrollPanel; + + mController.onDestroy(); + + verify(mockAutoclickScrollPanel).hide(); + } + + @Test + @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR) + public void changeFromScrollToOtherClickType_hidesScrollPanel() { + injectFakeMouseActionHoverMoveEvent(); + + // Set active click type to SCROLL. + mController.clickPanelController.handleAutoclickTypeChange( + AutoclickTypePanel.AUTOCLICK_TYPE_SCROLL); + + // Show the scroll panel. + mController.mAutoclickScrollPanel.show(); + assertThat(mController.mAutoclickScrollPanel.isVisible()).isTrue(); + + // Change click type to LEFT_CLICK. + mController.clickPanelController.handleAutoclickTypeChange( + AutoclickTypePanel.AUTOCLICK_TYPE_LEFT_CLICK); + + // Verify scroll panel is hidden. + assertThat(mController.mAutoclickScrollPanel.isVisible()).isFalse(); + } + + @Test + public void sendClick_clickType_leftClick() { + MotionEventCaptor motionEventCaptor = new MotionEventCaptor(); + mController.setNext(motionEventCaptor); + + injectFakeMouseActionHoverMoveEvent(); + // Set delay to zero so click is scheduled to run immediately. + mController.mClickScheduler.updateDelay(0); + + // Send hover move event. + MotionEvent hoverMove = MotionEvent.obtain( + /* downTime= */ 0, + /* eventTime= */ 100, + /* action= */ MotionEvent.ACTION_HOVER_MOVE, + /* x= */ 30f, + /* y= */ 0f, + /* metaState= */ 0); + hoverMove.setSource(InputDevice.SOURCE_MOUSE); + mController.onMotionEvent(hoverMove, hoverMove, /* policyFlags= */ 0); + mTestableLooper.processAllMessages(); + + // Verify left click sent. + assertThat(motionEventCaptor.downEvent).isNotNull(); + assertThat(motionEventCaptor.downEvent.getButtonState()).isEqualTo( + MotionEvent.BUTTON_PRIMARY); + } + + @Test + public void sendClick_clickType_rightClick() { + MotionEventCaptor motionEventCaptor = new MotionEventCaptor(); + mController.setNext(motionEventCaptor); + + injectFakeMouseActionHoverMoveEvent(); + // Set delay to zero so click is scheduled to run immediately. + mController.mClickScheduler.updateDelay(0); + + // Set click type to right click. + mController.clickPanelController.handleAutoclickTypeChange( + AutoclickTypePanel.AUTOCLICK_TYPE_RIGHT_CLICK); + + // Send hover move event. + MotionEvent hoverMove = MotionEvent.obtain( + /* downTime= */ 0, + /* eventTime= */ 100, + /* action= */ MotionEvent.ACTION_HOVER_MOVE, + /* x= */ 30f, + /* y= */ 0f, + /* metaState= */ 0); + hoverMove.setSource(InputDevice.SOURCE_MOUSE); + mController.onMotionEvent(hoverMove, hoverMove, /* policyFlags= */ 0); + mTestableLooper.processAllMessages(); + + // Verify right click sent. + assertThat(motionEventCaptor.downEvent).isNotNull(); + assertThat(motionEventCaptor.downEvent.getButtonState()).isEqualTo( + MotionEvent.BUTTON_SECONDARY); + } + + @Test + @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR) + public void hoverOnAutoclickPanel_rightClickType_forceTriggerLeftClick() { + MotionEventCaptor motionEventCaptor = new MotionEventCaptor(); + mController.setNext(motionEventCaptor); + + injectFakeMouseActionHoverMoveEvent(); + // Set delay to zero so click is scheduled to run immediately. + mController.mClickScheduler.updateDelay(0); + + // Set click type to right click. + mController.clickPanelController.handleAutoclickTypeChange( + AutoclickTypePanel.AUTOCLICK_TYPE_RIGHT_CLICK); + // Set mouse to hover panel. + AutoclickTypePanel mockAutoclickTypePanel = mock(AutoclickTypePanel.class); + when(mockAutoclickTypePanel.isHovered()).thenReturn(true); + mController.mAutoclickTypePanel = mockAutoclickTypePanel; + + // Send hover move event. + MotionEvent hoverMove = MotionEvent.obtain( + /* downTime= */ 0, + /* eventTime= */ 100, + /* action= */ MotionEvent.ACTION_HOVER_MOVE, + /* x= */ 30f, + /* y= */ 0f, + /* metaState= */ 0); + hoverMove.setSource(InputDevice.SOURCE_MOUSE); + mController.onMotionEvent(hoverMove, hoverMove, /* policyFlags= */ 0); + mTestableLooper.processAllMessages(); + + // Verify left click is sent due to the mouse hovering the panel. + assertThat(motionEventCaptor.downEvent).isNotNull(); + assertThat(motionEventCaptor.downEvent.getButtonState()).isEqualTo( + MotionEvent.BUTTON_PRIMARY); + } + private void injectFakeMouseActionHoverMoveEvent() { MotionEvent event = getFakeMotionHoverMoveEvent(); event.setSource(InputDevice.SOURCE_MOUSE); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickScrollPanelTest.java b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickScrollPanelTest.java new file mode 100644 index 000000000000..f445b50c7d9c --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickScrollPanelTest.java @@ -0,0 +1,92 @@ +/* + * Copyright 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.accessibility.autoclick; + +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.times; + +import android.content.Context; +import android.testing.AndroidTestingRunner; +import android.testing.TestableContext; +import android.testing.TestableLooper; +import android.view.WindowManager; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +/** Test cases for {@link AutoclickScrollPanel}. */ +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +public class AutoclickScrollPanelTest { + @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); + + @Rule + public TestableContext mTestableContext = + new TestableContext(getInstrumentation().getContext()); + + @Mock private WindowManager mMockWindowManager; + private AutoclickScrollPanel mScrollPanel; + + @Before + public void setUp() { + mTestableContext.addMockSystemService(Context.WINDOW_SERVICE, mMockWindowManager); + mScrollPanel = new AutoclickScrollPanel(mTestableContext, mMockWindowManager); + } + + @Test + public void show_addsViewToWindowManager() { + mScrollPanel.show(); + + // Verify view is added to window manager. + verify(mMockWindowManager).addView(any(), any(WindowManager.LayoutParams.class)); + + // Verify isVisible reflects correct state. + assertThat(mScrollPanel.isVisible()).isTrue(); + } + + @Test + public void show_alreadyVisible_doesNotAddAgain() { + // Show twice. + mScrollPanel.show(); + mScrollPanel.show(); + + // Verify addView was only called once. + verify(mMockWindowManager, times(1)).addView(any(), any()); + } + + @Test + public void hide_removesViewFromWindowManager() { + // First show the panel. + mScrollPanel.show(); + // Then hide it. + mScrollPanel.hide(); + // Verify view is removed from window manager. + verify(mMockWindowManager).removeView(any()); + // Verify scroll panel is hidden. + assertThat(mScrollPanel.isVisible()).isFalse(); + } +} diff --git a/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java index 1af59daa9c78..5922b12edc1e 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java @@ -37,6 +37,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -46,6 +47,7 @@ import android.content.Context; import android.graphics.PointF; import android.os.Looper; import android.os.SystemClock; +import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; import android.testing.DexmakerShareClassLoaderRule; @@ -504,6 +506,36 @@ public class TouchExplorerTest { assertThat(sentRawEvent.getDisplayId()).isEqualTo(rawDisplayId); } + @Test + @DisableFlags(Flags.FLAG_POINTER_UP_MOTION_EVENT_IN_TOUCH_EXPLORATION) + public void handleMotionEventStateTouchExploring_pointerUp_doesNotSendToManager() { + mTouchExplorer.getState().setServiceDetectsGestures(true); + mTouchExplorer.getState().clear(); + + mLastEvent = pointerDownEvent(); + mTouchExplorer.getState().startTouchExploring(); + MotionEvent event = fromTouchscreen(pointerUpEvent()); + + mTouchExplorer.onMotionEvent(event, event, /*policyFlags=*/0); + + verify(mMockAms, never()).sendMotionEventToListeningServices(event); + } + + @Test + @EnableFlags(Flags.FLAG_POINTER_UP_MOTION_EVENT_IN_TOUCH_EXPLORATION) + public void handleMotionEventStateTouchExploring_pointerUp_sendsToManager() { + mTouchExplorer.getState().setServiceDetectsGestures(true); + mTouchExplorer.getState().clear(); + + mLastEvent = pointerDownEvent(); + mTouchExplorer.getState().startTouchExploring(); + MotionEvent event = fromTouchscreen(pointerUpEvent()); + + mTouchExplorer.onMotionEvent(event, event, /*policyFlags=*/0); + + verify(mMockAms).sendMotionEventToListeningServices(event); + } + /** * Used to play back event data of a gesture by parsing the log into MotionEvents and sending * them to TouchExplorer. diff --git a/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchStateTest.java b/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchStateTest.java new file mode 100644 index 000000000000..3e7d9fd05327 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchStateTest.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.accessibility.gestures; + +import static android.view.accessibility.AccessibilityEvent.TYPE_TOUCH_INTERACTION_END; + +import static com.android.server.accessibility.gestures.TouchState.STATE_CLEAR; +import static com.android.server.accessibility.gestures.TouchState.STATE_TOUCH_EXPLORING; + +import static com.google.common.truth.Truth.assertThat; + +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; +import android.view.Display; + +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.accessibility.AccessibilityManagerService; +import com.android.server.accessibility.Flags; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; + +@RunWith(AndroidJUnit4.class) +public class TouchStateTest { + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + + private TouchState mTouchState; + @Mock private AccessibilityManagerService mMockAms; + + @Before + public void setup() { + mTouchState = new TouchState(Display.DEFAULT_DISPLAY, mMockAms); + } + + @EnableFlags(Flags.FLAG_POINTER_UP_MOTION_EVENT_IN_TOUCH_EXPLORATION) + @Test + public void injectedEvent_interactionEnd_pointerDown_startsTouchExploring() { + mTouchState.mReceivedPointerTracker.mReceivedPointersDown = 1; + mTouchState.onInjectedAccessibilityEvent(TYPE_TOUCH_INTERACTION_END); + assertThat(mTouchState.getState()).isEqualTo(STATE_TOUCH_EXPLORING); + } + + @EnableFlags(Flags.FLAG_POINTER_UP_MOTION_EVENT_IN_TOUCH_EXPLORATION) + @Test + public void injectedEvent_interactionEnd_pointerUp_clears() { + mTouchState.mReceivedPointerTracker.mReceivedPointersDown = 0; + mTouchState.onInjectedAccessibilityEvent(TYPE_TOUCH_INTERACTION_END); + assertThat(mTouchState.getState()).isEqualTo(STATE_CLEAR); + } + + @DisableFlags(Flags.FLAG_POINTER_UP_MOTION_EVENT_IN_TOUCH_EXPLORATION) + @Test + public void injectedEvent_interactionEnd_clears() { + mTouchState.onInjectedAccessibilityEvent(TYPE_TOUCH_INTERACTION_END); + assertThat(mTouchState.getState()).isEqualTo(STATE_CLEAR); + } +} diff --git a/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpXmlPersistenceTest.java b/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpXmlPersistenceTest.java index ae973be17904..56f802b278c6 100644 --- a/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpXmlPersistenceTest.java +++ b/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpXmlPersistenceTest.java @@ -89,8 +89,7 @@ public class DiscreteAppOpXmlPersistenceTest { int attributionChainId = AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE; mDiscreteRegistry.recordDiscreteAccess(uid, packageName, deviceId, op, null, opFlags, - uidState, accessTime, duration, attributionFlags, attributionChainId, - DiscreteOpsXmlRegistry.ACCESS_TYPE_FINISH_OP); + uidState, accessTime, duration, attributionFlags, attributionChainId); // Verify in-memory object is correct fetchDiscreteOpsAndValidate(uid, packageName, op, deviceId, null, accessTime, @@ -121,8 +120,7 @@ public class DiscreteAppOpXmlPersistenceTest { int attributionChainId = 10; mDiscreteRegistry.recordDiscreteAccess(uid, packageName, deviceId, op, null, opFlags, - uidState, accessTime, duration, attributionFlags, attributionChainId, - DiscreteOpsXmlRegistry.ACCESS_TYPE_START_OP); + uidState, accessTime, duration, attributionFlags, attributionChainId); fetchDiscreteOpsAndValidate(uid, packageName, op, deviceId, null, accessTime, duration, uidState, opFlags, attributionFlags, attributionChainId); diff --git a/services/tests/servicestests/src/com/android/server/appop/DiscreteOpsMigrationAndRollbackTest.java b/services/tests/servicestests/src/com/android/server/appop/DiscreteOpsMigrationAndRollbackTest.java index 8eea1c73d4f2..6c66f149baa7 100644 --- a/services/tests/servicestests/src/com/android/server/appop/DiscreteOpsMigrationAndRollbackTest.java +++ b/services/tests/servicestests/src/com/android/server/appop/DiscreteOpsMigrationAndRollbackTest.java @@ -70,7 +70,7 @@ public class DiscreteOpsMigrationAndRollbackTest { opEvent.getDeviceId(), opEvent.getOpCode(), opEvent.getAttributionTag(), opEvent.getOpFlags(), opEvent.getUidState(), opEvent.getAccessTime(), opEvent.getDuration(), opEvent.getAttributionFlags(), - (int) opEvent.getChainId(), DiscreteOpsRegistry.ACCESS_TYPE_NOTE_OP); + (int) opEvent.getChainId()); } xmlRegistry.writeAndClearOldAccessHistory(); assertThat(xmlRegistry.readLargestChainIdFromDiskLocked()).isEqualTo(RECORD_COUNT); @@ -104,7 +104,7 @@ public class DiscreteOpsMigrationAndRollbackTest { opEvent.getDeviceId(), opEvent.getOpCode(), opEvent.getAttributionTag(), opEvent.getOpFlags(), opEvent.getUidState(), opEvent.getAccessTime(), opEvent.getDuration(), opEvent.getAttributionFlags(), - (int) opEvent.getChainId(), DiscreteOpsRegistry.ACCESS_TYPE_NOTE_OP); + (int) opEvent.getChainId()); } // flush records from cache to the database. sqlRegistry.shutdown(); diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index 30aa8cebdff6..01bcc2584fe1 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -143,6 +143,7 @@ import android.provider.DeviceConfig; import android.provider.Settings; import android.security.KeyChain; import android.security.keystore.AttestationUtils; +import android.telephony.SubscriptionInfo; import android.telephony.TelephonyManager; import android.telephony.data.ApnSetting; import android.test.MoreAsserts; @@ -8715,6 +8716,47 @@ public class DevicePolicyManagerTest extends DpmTestBase { } } + @RequiresFlagsEnabled(Flags.FLAG_REMOVE_MANAGED_ESIM_ON_WORK_PROFILE_DELETION) + @Test + public void testManagedProfileDeleted_managedEmbeddedSubscriptionDeleted() throws Exception { + // Setup PO mode. + setupProfileOwner(); + // Mock SubscriptionManager to return a subscription managed by the profile owner package. + int managedSubscriptionId = 42; + SubscriptionInfo managedSubscription = new SubscriptionInfo.Builder().setCardId(1).setId( + managedSubscriptionId).setGroupOwner(admin1.getPackageName()).build(); + when(getServices().subscriptionManager.getAvailableSubscriptionInfoList()).thenReturn( + List.of(managedSubscription)); + + // Send a ACTION_MANAGED_PROFILE_REMOVED broadcast to emulate a managed profile being + // removed. + sendBroadcastWithUser(dpms, Intent.ACTION_MANAGED_PROFILE_REMOVED, CALLER_USER_HANDLE); + + // Verify that EuiccManager was called to delete the subscription. + verify(getServices().euiccManager).deleteSubscription(eq(managedSubscriptionId), any()); + } + + @RequiresFlagsDisabled(Flags.FLAG_REMOVE_MANAGED_ESIM_ON_WORK_PROFILE_DELETION) + @Test + public void testManagedProfileDeleted_flagDisabled_managedEmbeddedSubscriptionDeleted() + throws Exception { + // Set up PO mode. + setupProfileOwner(); + // Mock SubscriptionManager to return a subscription managed by the profile owner package. + int managedSubscriptionId = 42; + SubscriptionInfo managedSubscription = new SubscriptionInfo.Builder().setCardId(1).setId( + managedSubscriptionId).setGroupOwner(admin1.getPackageName()).build(); + when(getServices().subscriptionManager.getAvailableSubscriptionInfoList()).thenReturn( + List.of(managedSubscription)); + + // Send a ACTION_MANAGED_PROFILE_REMOVED broadcast to emulate a managed profile being + // removed. + sendBroadcastWithUser(dpms, Intent.ACTION_MANAGED_PROFILE_REMOVED, CALLER_USER_HANDLE); + + // Verify that EuiccManager was not called to delete the subscription. + verifyZeroInteractions(getServices().euiccManager); + } + private void setupVpnAuthorization(String userVpnPackage, int userVpnUid) { final AppOpsManager.PackageOps vpnOp = new AppOpsManager.PackageOps(userVpnPackage, userVpnUid, List.of(new AppOpsManager.OpEntry( diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java index 00b0c558b4e3..479af73bae3c 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java @@ -253,6 +253,8 @@ public class DpmMockContext extends MockContext { return mMockSystemServices.subscriptionManager; case Context.USB_SERVICE: return mMockSystemServices.usbManager; + case Context.EUICC_SERVICE: + return mMockSystemServices.euiccManager; } throw new UnsupportedOperationException(); } @@ -487,6 +489,14 @@ public class DpmMockContext extends MockContext { } @Override + public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user, + IntentFilter filter, String broadcastPermission, Handler scheduler, int flags) { + mMockSystemServices.registerReceiver(receiver, filter, scheduler); + return spiedContext.registerReceiverAsUser(receiver, user, filter, broadcastPermission, + scheduler, flags); + } + + @Override public void unregisterReceiver(BroadcastReceiver receiver) { mMockSystemServices.unregisterReceiver(receiver); spiedContext.unregisterReceiver(receiver); diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java index 3e4448c1dafa..d01fa91e22c2 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java @@ -68,6 +68,7 @@ import android.provider.Settings; import android.security.KeyChain; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; +import android.telephony.euicc.EuiccManager; import android.test.mock.MockContentProvider; import android.test.mock.MockContentResolver; import android.util.ArrayMap; @@ -151,6 +152,7 @@ public class MockSystemServices { public final File dataDir; public final PolicyPathProvider pathProvider; public final SupervisionManagerInternal supervisionManagerInternal; + public final EuiccManager euiccManager; private final Map<String, PackageState> mTestPackageStates = new ArrayMap<>(); @@ -206,6 +208,7 @@ public class MockSystemServices { roleManagerForMock = mock(RoleManagerForMock.class); subscriptionManager = mock(SubscriptionManager.class); supervisionManagerInternal = mock(SupervisionManagerInternal.class); + euiccManager = mock(EuiccManager.class); // Package manager is huge, so we use a partial mock instead. packageManager = spy(realContext.getPackageManager()); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java index b0ffebb973a1..aa1d5835bfc8 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java @@ -2295,6 +2295,38 @@ public class HdmiCecLocalDeviceTvTest { .hasSize(1); } + @Test + public void onOneTouchPlay_wakeUp_exist_device() { + HdmiCecMessage requestActiveSource = + HdmiCecMessageBuilder.buildRequestActiveSource(ADDR_TV); + + // Go to standby to trigger RequestActiveSourceAction for playback_1 + mHdmiControlService.onStandby(STANDBY_SCREEN_OFF); + mTestLooper.dispatchAll(); + + mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.SUCCESS); + mHdmiControlService.onWakeUp(WAKE_UP_SCREEN_ON); + mTestLooper.dispatchAll(); + + // Skip the LauncherX API timeout. + mTestLooper.moveTimeForward(TIMEOUT_WAIT_FOR_TV_ASSERT_ACTIVE_SOURCE_MS); + mTestLooper.dispatchAll(); + + assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource); + mNativeWrapper.clearResultMessages(); + + // turn off TV and wake up with one touch play + mHdmiControlService.onStandby(STANDBY_SCREEN_OFF); + mTestLooper.dispatchAll(); + + // FakePowerManagerWrapper#wakeUp() doesn't broadcast Intent.ACTION_SCREEN_ON + // manually trigger onWakeUp to mock OTP + mHdmiControlService.onWakeUp(WAKE_UP_SCREEN_ON); + mTestLooper.dispatchAll(); + + assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestActiveSource); + } + @Test public void handleReportAudioStatus_SamOnAvrStandby_startSystemAudioActionFromTv() { diff --git a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java index 2da2f50447c7..e836780b3f71 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java @@ -16,6 +16,7 @@ package com.android.server.locksettings; +import static android.content.pm.UserInfo.FLAG_FOR_TESTING; import static android.content.pm.UserInfo.FLAG_FULL; import static android.content.pm.UserInfo.FLAG_MAIN; import static android.content.pm.UserInfo.FLAG_PRIMARY; @@ -44,6 +45,8 @@ import static org.mockito.Mockito.when; import android.app.PropertyInvalidatedCache; import android.app.admin.PasswordMetrics; +import android.content.ComponentName; +import android.content.pm.UserInfo; import android.os.RemoteException; import android.platform.test.annotations.Presubmit; @@ -357,6 +360,45 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests { } @Test + public void testEscrowDataRetainedWhenManagedUserVerifiesCredential() throws RemoteException { + when(mDeviceStateCache.isUserOrganizationManaged(anyInt())).thenReturn(true); + + LockscreenCredential password = newPassword("password"); + initSpAndSetCredential(PRIMARY_USER_ID, password); + + mService.verifyCredential(password, PRIMARY_USER_ID, 0 /* flags */); + + assertTrue("Escrow data was destroyed", mSpManager.hasEscrowData(PRIMARY_USER_ID)); + } + + @Test + public void testEscrowDataRetainedWhenUnmanagedTestUserVerifiesCredential() + throws RemoteException { + when(mDeviceStateCache.isUserOrganizationManaged(anyInt())).thenReturn(false); + UserInfo userInfo = mUserManagerInternal.getUserInfo(PRIMARY_USER_ID); + userInfo.flags |= FLAG_FOR_TESTING; + + LockscreenCredential password = newPassword("password"); + initSpAndSetCredential(PRIMARY_USER_ID, password); + + mService.verifyCredential(password, PRIMARY_USER_ID, 0 /* flags */); + + assertTrue("Escrow data was destroyed", mSpManager.hasEscrowData(PRIMARY_USER_ID)); + } + + @Test + public void testEscrowDataDeletedWhenUnmanagedUserVerifiesCredential() throws RemoteException { + when(mDeviceStateCache.isUserOrganizationManaged(anyInt())).thenReturn(false); + + LockscreenCredential password = newPassword("password"); + initSpAndSetCredential(PRIMARY_USER_ID, password); + + mService.verifyCredential(password, PRIMARY_USER_ID, 0 /* flags */); + + assertFalse("Escrow data wasn't destroyed", mSpManager.hasAnyEscrowData(PRIMARY_USER_ID)); + } + + @Test public void testTokenBasedClearPassword() throws RemoteException { LockscreenCredential password = newPassword("password"); LockscreenCredential pattern = newPattern("123654"); diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java index f9946604ad5d..b842d3a42f26 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java @@ -389,6 +389,44 @@ public final class UserManagerTest { } @Test + public void testSupervisingProfile() throws Exception { + assumeTrue("Device doesn't support supervising profiles ", + mUserManager.isUserTypeEnabled(UserManager.USER_TYPE_PROFILE_SUPERVISING)); + + final UserTypeDetails userTypeDetails = + UserTypeFactory.getUserTypes().get(UserManager.USER_TYPE_PROFILE_SUPERVISING); + assertWithMessage("No supervising user type on device").that(userTypeDetails).isNotNull(); + + + // Create supervising profile if it doesn't exist + UserInfo supervisingUser = getSupervisingProfile(); + if (supervisingUser == null) { + supervisingUser = createUser("Supervising", + UserManager.USER_TYPE_PROFILE_SUPERVISING, /*flags*/ 0); + } + assertWithMessage("Couldn't create supervising profile").that(supervisingUser).isNotNull(); + UserHandle supervisingHandle = supervisingUser.getUserHandle(); + + // Test that only one supervising profile can be created + final UserInfo secondSupervisingProfile = + createUser("Supervising", UserManager.USER_TYPE_PROFILE_SUPERVISING, + /*flags*/ 0); + assertThat(secondSupervisingProfile).isNull(); + + // Verify that the supervising profile doesn't have a parent + assertThat(mUserManager.getProfileParent(supervisingHandle.getIdentifier())).isNull(); + + // Make sure that the supervising profile can be started in the background, and that it + // is visible + final boolean isStarted = mActivityManager.startProfile(supervisingHandle); + assertWithMessage("Unable to start supervising profile").that(isStarted).isTrue(); + final UserManager umSupervising = (UserManager) mContext.createPackageContextAsUser( + "android", 0, supervisingHandle).getSystemService(Context.USER_SERVICE); + assertWithMessage("Supervising profile not visible").that( + umSupervising.isUserVisible()).isTrue(); + } + + @Test public void testGetProfileAccessibilityString_throwsExceptionForNonProfileUser() { UserInfo user1 = createUser("Guest 1", UserInfo.FLAG_GUEST); assertThat(user1).isNotNull(); @@ -2198,4 +2236,13 @@ public final class UserManagerTest { assertEquals(actual.getLevel(), expected.getLevel()); } + @Nullable + private UserInfo getSupervisingProfile() { + for (UserInfo user : mUserManager.getUsers()) { + if (user.userType.equals(UserManager.USER_TYPE_PROFILE_SUPERVISING)) { + return user; + } + } + return null; + } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java index 4eac1d126202..a9759c8a61f3 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java @@ -102,7 +102,9 @@ import platform.test.runner.parameterized.ParameterizedAndroidJunit4; import platform.test.runner.parameterized.Parameters; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; @SmallTest @SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the class. @@ -4443,4 +4445,97 @@ public class GroupHelperTest extends UiServiceTestCase { verify(mCallback).sendAppProvidedSummaryDeleteIntent(eq(pkg), eq(deleteIntentofFirstSummary)); } + + @Test + @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING) + public void testGroupSummaryAdded_hadUngroupedNotif_doesNotAutogroup() { + // Scenario: + // * child notification posted before summary; added to ungrouped notifications + // * summary posted, so now the child has a group summary and is no longer "ungrouped + // * another ungrouped notification is posted + // Confirm that the first notification (that now has a summary) is not autogrouped. + + // Bookkeeping items + List<NotificationRecord> notifList = new ArrayList<>(); + Map<String, NotificationRecord> summaryByGroupKey = new HashMap<>(); + + // Setup: post AUTOGROUP_AT_COUNT - 2 notifications so that the next notification would not + // trigger autogrouping, but the one after that would + for (int i = 0; i < AUTOGROUP_AT_COUNT - 2; i++) { + NotificationRecord child = getNotificationRecord(mPkg, i, "", mUser, "group" + i, false, + IMPORTANCE_DEFAULT); + notifList.add(child); + mGroupHelper.onNotificationPostedWithDelay(child, notifList, summaryByGroupKey); + } + + // Group child: posted enough before its associated summary to be put in the "ungrouped" + // set of notifications + NotificationRecord groupChild = getNotificationRecord(mPkg, AUTOGROUP_AT_COUNT - 2, "", + mUser, "specialGroup", false, IMPORTANCE_DEFAULT); + notifList.add(groupChild); + mGroupHelper.onNotificationPostedWithDelay(groupChild, notifList, summaryByGroupKey); + + // Group summary: posted after child 1 + NotificationRecord groupSummary = getNotificationRecord(mPkg, AUTOGROUP_AT_COUNT - 1, "", + mUser, "specialGroup", true, IMPORTANCE_DEFAULT); + notifList.add(groupSummary); + summaryByGroupKey.put(groupSummary.getSbn().getGroupKey(), groupSummary); + mGroupHelper.onGroupSummaryAdded(groupSummary, notifList); + mGroupHelper.onNotificationPostedWithDelay(groupSummary, notifList, summaryByGroupKey); + + // One more notification posted to the group; because its summary already exists, it should + // never be counted as an "ungrouped" notification + NotificationRecord groupChild2 = getNotificationRecord(mPkg, AUTOGROUP_AT_COUNT, "", + mUser, "specialGroup", false, IMPORTANCE_DEFAULT); + notifList.add(groupChild2); + mGroupHelper.onNotificationPostedWithDelay(groupChild2, notifList, summaryByGroupKey); + + // Now one more ungrouped notification; this would have put the number of "ungrouped" + // notifications above the limit if the first groupChild notification were left ungrouped + NotificationRecord extra = getNotificationRecord(mPkg, AUTOGROUP_AT_COUNT + 1, "", mUser, + "yetAnotherGroup", false, IMPORTANCE_DEFAULT); + notifList.add(extra); + mGroupHelper.onNotificationPostedWithDelay(extra, notifList, summaryByGroupKey); + + // no autogrouping should have occurred + verifyZeroInteractions(mCallback); + } + + @Test + @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING) + public void testGroupSummaryAdded_onlyUnrelatedGroupedNotifs() { + // If all of the existing ungrouped notifications have nothing to do with the summary + // they should still get grouped as needed. + List<NotificationRecord> notifList = new ArrayList<>(); + Map<String, NotificationRecord> summaryByGroupKey = new HashMap<>(); + + // Post 1 fewer than the autogroupable notifications, each associated with a different + // group without a summary. + for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) { + NotificationRecord child = getNotificationRecord(mPkg, i, "", mUser, "group" + i, false, + IMPORTANCE_DEFAULT); + notifList.add(child); + mGroupHelper.onNotificationPostedWithDelay(child, notifList, summaryByGroupKey); + } + + // At this point we do not yet expect autogrouping. + // Add a group summary that is a summary associated with none of the above notifications. + // Because this gets considered a "summary without children", all of these notifications + // should now be autogrouped. + NotificationRecord summary = getNotificationRecord(mPkg, AUTOGROUP_AT_COUNT, "", mUser, + "summaryGroup", true, IMPORTANCE_DEFAULT); + notifList.add(summary); + summaryByGroupKey.put(summary.getSbn().getKey(), summary); + mGroupHelper.onGroupSummaryAdded(summary, notifList); + mGroupHelper.onNotificationPostedWithDelay(summary, notifList, summaryByGroupKey); + + // all of the above posted notifications should be autogrouped + String expectedGroupKey = getExpectedAutogroupKey( + getNotificationRecord(mPkg, 0, String.valueOf(0), mUser)); + verify(mCallback, times(1)).addAutoGroupSummary( + anyInt(), eq(mPkg), anyString(), eq(expectedGroupKey), + anyInt(), eq(getNotificationAttributes(BASE_FLAGS))); + verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(), + eq(expectedGroupKey), anyBoolean()); + } } diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java index 04335df8c454..2baf0c141c89 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java @@ -247,6 +247,7 @@ public class VibrationThreadTest { assertThat(mVibratorProviders.get(VIBRATOR_ID).getAmplitudes()).isEmpty(); } + @EnableFlags(Flags.FLAG_FIX_VIBRATION_THREAD_CALLBACK_HANDLING) @Test public void vibrate_singleVibratorWaveform_runsVibrationAndChangesAmplitudes() { mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); @@ -269,7 +270,10 @@ public class VibrationThreadTest { } @Test - @EnableFlags(Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED) + @EnableFlags({ + Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED, + Flags.FLAG_FIX_VIBRATION_THREAD_CALLBACK_HANDLING, + }) public void vibrate_singleWaveformWithAdaptiveHapticsScaling_scalesAmplitudesProperly() { // No user settings scale. setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, @@ -296,7 +300,10 @@ public class VibrationThreadTest { } @Test - @EnableFlags(Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED) + @EnableFlags({ + Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED, + Flags.FLAG_FIX_VIBRATION_THREAD_CALLBACK_HANDLING, + }) public void vibrate_withVibrationParamsRequestStalling_timeoutRequestAndApplyNoScaling() { // No user settings scale. setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, @@ -357,6 +364,7 @@ public class VibrationThreadTest { } } + @EnableFlags(Flags.FLAG_FIX_VIBRATION_THREAD_CALLBACK_HANDLING) @Test public void vibrate_singleVibratorRepeatingShortAlwaysOnWaveform_turnsVibratorOnForLonger() throws Exception { @@ -380,6 +388,7 @@ public class VibrationThreadTest { .containsExactly(expectedOneShot(5000)).inOrder(); } + @EnableFlags(Flags.FLAG_FIX_VIBRATION_THREAD_CALLBACK_HANDLING) @Test public void vibrate_singleVibratorPatternWithZeroDurationSteps_skipsZeroDurationSteps() { mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); @@ -399,6 +408,7 @@ public class VibrationThreadTest { .containsExactlyElementsIn(expectedOneShots(100L, 150L)).inOrder(); } + @EnableFlags(Flags.FLAG_FIX_VIBRATION_THREAD_CALLBACK_HANDLING) @Test public void vibrate_singleVibratorPatternWithZeroDurationAndAmplitude_skipsZeroDurationSteps() { mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); @@ -537,6 +547,7 @@ public class VibrationThreadTest { assertThat(fakeVibrator.getEffectSegments(vibration.id)).hasSize(10); } + @EnableFlags(Flags.FLAG_FIX_VIBRATION_THREAD_CALLBACK_HANDLING) @Test public void vibrate_singleVibratorRepeatingLongAlwaysOnWaveform_turnsVibratorOnForACycle() throws Exception { @@ -700,6 +711,7 @@ public class VibrationThreadTest { .containsExactly(expectedPrebaked(VibrationEffect.EFFECT_THUD)).inOrder(); } + @EnableFlags(Flags.FLAG_FIX_VIBRATION_THREAD_CALLBACK_HANDLING) @Test public void vibrate_singleVibratorPrebakedAndUnsupportedEffectWithFallback_runsFallback() { mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); @@ -1247,6 +1259,7 @@ public class VibrationThreadTest { .containsExactly(expected).inOrder(); } + @EnableFlags(Flags.FLAG_FIX_VIBRATION_THREAD_CALLBACK_HANDLING) @Test public void vibrate_multipleStereo_runsVibrationOnRightVibrators() { mockVibrators(1, 2, 3, 4); @@ -1479,6 +1492,7 @@ public class VibrationThreadTest { assertThat(mVibratorProviders.get(1).getAmplitudes()).isEmpty(); } + @EnableFlags(Flags.FLAG_FIX_VIBRATION_THREAD_CALLBACK_HANDLING) @Test public void vibrate_multipleWaveforms_playsWaveformsInParallel() throws Exception { mockVibrators(1, 2, 3); @@ -1544,8 +1558,8 @@ public class VibrationThreadTest { VibrationEffect.createOneShot( expectedDuration, VibrationEffect.DEFAULT_AMPLITUDE))); - startThreadAndDispatcher(vibration); long startTime = SystemClock.elapsedRealtime(); + startThreadAndDispatcher(vibration); vibration.waitForEnd(); long vibrationEndTime = SystemClock.elapsedRealtime(); @@ -1554,15 +1568,13 @@ public class VibrationThreadTest { long completionTime = SystemClock.elapsedRealtime(); verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong()); - // Vibration ends after duration, thread completed after ramp down - assertThat(vibrationEndTime - startTime).isAtLeast(expectedDuration); + // Vibration ends before ramp down, thread completed after ramp down assertThat(vibrationEndTime - startTime).isLessThan(expectedDuration + rampDownDuration); assertThat(completionTime - startTime).isAtLeast(expectedDuration + rampDownDuration); } @Test - public void vibrate_withVibratorCallbackDelayShorterThanTimeout_vibrationFinishedAfterDelay() - throws Exception { + public void vibrate_withVibratorCallbackDelayShorterThanTimeout_vibrationFinishedAfterDelay() { long expectedDuration = 10; long callbackDelay = VibrationStepConductor.CALLBACKS_EXTRA_TIMEOUT / 2; @@ -1577,10 +1589,8 @@ public class VibrationThreadTest { long startTime = SystemClock.elapsedRealtime(); startThreadAndDispatcher(vibration); - vibration.waitForEnd(); - long vibrationEndTime = SystemClock.elapsedRealtime(); - waitForCompletion(TEST_TIMEOUT_MILLIS); + long vibrationEndTime = SystemClock.elapsedRealtime(); verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong()); assertThat(vibrationEndTime - startTime).isAtLeast(expectedDuration + callbackDelay); @@ -1588,8 +1598,7 @@ public class VibrationThreadTest { @LargeTest @Test - public void vibrate_withVibratorCallbackDelayLongerThanTimeout_vibrationFinishedAfterTimeout() - throws Exception { + public void vibrate_withVibratorCallbackDelayLongerThanTimeout_vibrationFinishedAfterTimeout() { long expectedDuration = 10; long callbackTimeout = VibrationStepConductor.CALLBACKS_EXTRA_TIMEOUT; long callbackDelay = callbackTimeout * 2; @@ -1602,21 +1611,17 @@ public class VibrationThreadTest { VibrationEffect.createOneShot( expectedDuration, VibrationEffect.DEFAULT_AMPLITUDE))); - startThreadAndDispatcher(vibration); long startTime = SystemClock.elapsedRealtime(); - - vibration.waitForEnd(); - long vibrationEndTime = SystemClock.elapsedRealtime(); + startThreadAndDispatcher(vibration); waitForCompletion(callbackDelay + TEST_TIMEOUT_MILLIS); - long completionTime = SystemClock.elapsedRealtime(); + long vibrationEndTime = SystemClock.elapsedRealtime(); verify(mControllerCallbacks, never()) .onComplete(eq(VIBRATOR_ID), eq(vibration.id), anyLong()); // Vibration ends and thread completes after timeout, before the HAL callback assertThat(vibrationEndTime - startTime).isAtLeast(expectedDuration + callbackTimeout); assertThat(vibrationEndTime - startTime).isLessThan(expectedDuration + callbackDelay); - assertThat(completionTime - startTime).isLessThan(expectedDuration + callbackDelay); } @LargeTest @@ -1637,8 +1642,8 @@ public class VibrationThreadTest { Arrays.fill(amplitudes, VibrationEffect.DEFAULT_AMPLITUDE); VibrationEffect effect = VibrationEffect.createWaveform(timings, amplitudes, -1); - startThreadAndDispatcher(effect); long startTime = SystemClock.elapsedRealtime(); + startThreadAndDispatcher(effect); waitForCompletion(totalDuration + TEST_TIMEOUT_MILLIS); long delay = Math.abs(SystemClock.elapsedRealtime() - startTime - totalDuration); @@ -1806,6 +1811,7 @@ public class VibrationThreadTest { assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isFalse(); } + @EnableFlags(Flags.FLAG_FIX_VIBRATION_THREAD_CALLBACK_HANDLING) @Test public void vibrate_waveformWithRampDown_addsRampDownAfterVibrationCompleted() { when(mVibrationConfigMock.getRampDownDurationMs()).thenReturn(15); @@ -1833,6 +1839,7 @@ public class VibrationThreadTest { } } + @EnableFlags(Flags.FLAG_FIX_VIBRATION_THREAD_CALLBACK_HANDLING) @Test public void vibrate_waveformWithRampDown_triggersCallbackWhenOriginalVibrationEnds() throws Exception { @@ -1863,6 +1870,7 @@ public class VibrationThreadTest { verify(mManagerHooks).onVibrationThreadReleased(vibration.id); } + @EnableFlags(Flags.FLAG_FIX_VIBRATION_THREAD_CALLBACK_HANDLING) @Test public void vibrate_waveformCancelledWithRampDown_addsRampDownAfterVibrationCancelled() throws Exception { @@ -2057,6 +2065,7 @@ public class VibrationThreadTest { .containsExactly(expectedPrebaked(EFFECT_CLICK)).inOrder(); } + @EnableFlags(Flags.FLAG_FIX_VIBRATION_THREAD_CALLBACK_HANDLING) @Test public void vibrate_multipleVibratorsSequentialInSession_runsInOrderWithoutDelaysAndNoOffs() { mockVibrators(1, 2, 3); diff --git a/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java b/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java index 3ca019728c2b..fcdf88f16550 100644 --- a/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java @@ -605,29 +605,6 @@ public class KeyGestureEventTests extends ShortcutKeyTestBase { } @Test - public void testKeyGestureAccessibilityShortcutChord() { - Assert.assertTrue( - sendKeyGestureEventStart( - KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD)); - mPhoneWindowManager.moveTimeForward(5000); - Assert.assertTrue( - sendKeyGestureEventCancel( - KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD)); - mPhoneWindowManager.assertAccessibilityKeychordCalled(); - } - - @Test - public void testKeyGestureAccessibilityShortcutChordCancelled() { - Assert.assertTrue( - sendKeyGestureEventStart( - KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD)); - Assert.assertTrue( - sendKeyGestureEventCancel( - KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD)); - mPhoneWindowManager.assertAccessibilityKeychordNotCalled(); - } - - @Test public void testKeyGestureRingerToggleChord() { mPhoneWindowManager.overridePowerVolumeUp(POWER_VOLUME_UP_BEHAVIOR_MUTE); Assert.assertTrue( @@ -670,29 +647,6 @@ public class KeyGestureEventTests extends ShortcutKeyTestBase { } @Test - public void testKeyGestureAccessibilityTvShortcutChord() { - Assert.assertTrue( - sendKeyGestureEventStart( - KeyGestureEvent.KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD)); - mPhoneWindowManager.moveTimeForward(5000); - Assert.assertTrue( - sendKeyGestureEventCancel( - KeyGestureEvent.KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD)); - mPhoneWindowManager.assertAccessibilityKeychordCalled(); - } - - @Test - public void testKeyGestureAccessibilityTvShortcutChordCancelled() { - Assert.assertTrue( - sendKeyGestureEventStart( - KeyGestureEvent.KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD)); - Assert.assertTrue( - sendKeyGestureEventCancel( - KeyGestureEvent.KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD)); - mPhoneWindowManager.assertAccessibilityKeychordNotCalled(); - } - - @Test public void testKeyGestureTvTriggerBugReport() { Assert.assertTrue( sendKeyGestureEventStart(KeyGestureEvent.KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT)); diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java index f88492477487..e56fd3c6272d 100644 --- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java +++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java @@ -750,11 +750,6 @@ class TestPhoneWindowManager { verify(mAccessibilityShortcutController).performAccessibilityShortcut(); } - void assertAccessibilityKeychordNotCalled() { - mTestLooper.dispatchAll(); - verify(mAccessibilityShortcutController, never()).performAccessibilityShortcut(); - } - void assertCloseAllDialogs() { verify(mContext).closeSystemDialogs(); } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivitySnapshotControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivitySnapshotControllerTests.java index 948371f74a9c..ad706e879b72 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivitySnapshotControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivitySnapshotControllerTests.java @@ -63,11 +63,23 @@ public class ActivitySnapshotControllerTests extends TaskSnapshotPersisterTestBa super(0.8f /* highResScale */, 0.5f /* lowResScale */); } + private class TestActivitySnapshotController extends ActivitySnapshotController { + TestActivitySnapshotController(WindowManagerService service, + SnapshotPersistQueue persistQueue) { + super(service, persistQueue); + } + @Override + BaseAppSnapshotPersister.PersistInfoProvider createPersistInfoProvider( + WindowManagerService service) { + return mPersister.mPersistInfoProvider; + } + } @Override @Before public void setUp() { super.setUp(); - mActivitySnapshotController = new ActivitySnapshotController(mWm, mSnapshotPersistQueue); + mActivitySnapshotController = new TestActivitySnapshotController( + mWm, mSnapshotPersistQueue); spyOn(mActivitySnapshotController); doReturn(false).when(mActivitySnapshotController).shouldDisableSnapshots(); mActivitySnapshotController.resetTmpFields(); diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java index e3e9cc426bb3..08b0077c49b3 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java @@ -797,7 +797,7 @@ public class ActivityStarterTests extends WindowTestsBase { // Create adjacent tasks and put one activity under it final Task parent = new TaskBuilder(mSupervisor).build(); final Task adjacentParent = new TaskBuilder(mSupervisor).build(); - parent.setAdjacentTaskFragment(adjacentParent); + parent.setAdjacentTaskFragments(new TaskFragment.AdjacentSet(parent, adjacentParent)); final ActivityRecord activity = new ActivityBuilder(mAtm) .setParentTask(parent) .setCreateTask(true).build(); diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java index a9be47d71213..86d901b640ff 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java @@ -488,14 +488,13 @@ public class ActivityTaskSupervisorTests extends WindowTestsBase { WINDOWING_MODE_MULTI_WINDOW, /* opaque */ true, /* filling */ false); final TaskFragment tf2 = createChildTaskFragment(/* parent */ rootTask, WINDOWING_MODE_MULTI_WINDOW, /* opaque */ true, /* filling */ false); - tf1.setAdjacentTaskFragment(tf2); + tf1.setAdjacentTaskFragments(new TaskFragment.AdjacentSet(tf1, tf2)); assertThat(mSupervisor.mOpaqueContainerHelper.isOpaque(rootTask)).isTrue(); } @Test - @EnableFlags({Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND, - Flags.FLAG_ALLOW_MULTIPLE_ADJACENT_TASK_FRAGMENTS}) + @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) public void testOpaque_rootTask_nonFillingOpaqueAdjacentChildren_multipleAdjacent_isOpaque() { final Task rootTask = new TaskBuilder(mSupervisor).setOnTop(true).build(); final TaskFragment tf1 = createChildTaskFragment(/* parent */ rootTask, diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java index 8fe08553db95..cb98b9a490d8 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java @@ -243,6 +243,11 @@ class AppCompatActivityRobot { .getAspectRatioOverrides()).getUserMinAspectRatio(); } + void setShouldRefreshActivityForCameraCompat(boolean enabled) { + doReturn(enabled).when(mActivityStack.top().mAppCompatController.getCameraOverrides()) + .shouldRefreshActivityForCameraCompat(); + } + void setIgnoreOrientationRequest(boolean enabled) { mDisplayContent.setIgnoreOrientationRequest(enabled); } diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationRobot.java index 05f6ed644632..7ef85262dfc2 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationRobot.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationRobot.java @@ -64,6 +64,19 @@ class AppCompatConfigurationRobot { .isCameraCompatTreatmentEnabledAtBuildTime(); } + void setCameraCompatAspectRatio(float aspectRatio) { + doReturn(aspectRatio).when(mAppCompatConfiguration).getCameraCompatAspectRatio(); + } + + void enableCameraCompatRefresh(boolean enabled) { + doReturn(enabled).when(mAppCompatConfiguration).isCameraCompatRefreshEnabled(); + } + + void enableCameraCompatRefreshCycleThroughStop(boolean enabled) { + doReturn(enabled).when(mAppCompatConfiguration) + .isCameraCompatRefreshCycleThroughStopEnabled(); + } + void enableUserAppAspectRatioFullscreen(boolean enabled) { doReturn(enabled).when(mAppCompatConfiguration).isUserAppAspectRatioFullscreenEnabled(); } diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java index bdee3c323549..dd3e9fcbbdaf 100644 --- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java @@ -343,8 +343,7 @@ public class BackNavigationControllerTests extends WindowTestsBase { // Adjacent + no companion => unable to predict // TF1 | TF2 - tf1.setAdjacentTaskFragment(tf2); - tf2.setAdjacentTaskFragment(tf1); + tf1.setAdjacentTaskFragments(new TaskFragment.AdjacentSet(tf1, tf2)); predictable = BackNavigationController.getAnimatablePrevActivities(task, topAr, outPrevActivities); assertTrue(outPrevActivities.isEmpty()); @@ -393,8 +392,7 @@ public class BackNavigationControllerTests extends WindowTestsBase { // Adjacent => predict for previous activity. // TF2 | TF3 // TF1 - tf2.setAdjacentTaskFragment(tf3); - tf3.setAdjacentTaskFragment(tf2); + tf2.setAdjacentTaskFragments(new TaskFragment.AdjacentSet(tf2, tf3)); predictable = BackNavigationController.getAnimatablePrevActivities(task, topAr, outPrevActivities); assertTrue(outPrevActivities.contains(prevAr)); @@ -657,8 +655,7 @@ public class BackNavigationControllerTests extends WindowTestsBase { final TaskFragment secondaryTf = createTaskFragmentWithEmbeddedActivity(task, organizer); final ActivityRecord primaryActivity = primaryTf.getTopMostActivity(); final ActivityRecord secondaryActivity = secondaryTf.getTopMostActivity(); - primaryTf.setAdjacentTaskFragment(secondaryTf); - secondaryTf.setAdjacentTaskFragment(primaryTf); + primaryTf.setAdjacentTaskFragments(new TaskFragment.AdjacentSet(primaryTf, secondaryTf)); final WindowState primaryWindow = mock(WindowState.class); final WindowState secondaryWindow = mock(WindowState.class); diff --git a/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java index f5bec04a98d5..6f959812d742 100644 --- a/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java @@ -21,13 +21,13 @@ import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_LANDSCAPE_ import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE; import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE; import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_PORTRAIT; +import static android.app.CameraCompatTaskInfo.FreeformCameraCompatMode; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE; import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP; import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_SIMULATE_REQUESTED_ORIENTATION; import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT; -import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_FULL_USER; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; @@ -40,18 +40,15 @@ import static android.view.Surface.ROTATION_90; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; import static com.android.server.wm.AppCompatConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO; import static com.android.window.flags.Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING; import static com.android.window.flags.Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; @@ -59,13 +56,11 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import android.annotation.NonNull; -import android.app.CameraCompatTaskInfo; import android.app.IApplicationThread; import android.app.WindowConfiguration.WindowingMode; import android.app.servertransaction.RefreshCallbackItem; import android.app.servertransaction.ResumeActivityItem; import android.compat.testing.PlatformCompatChangeRule; -import android.content.ComponentName; import android.content.pm.ActivityInfo.ScreenOrientation; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; @@ -73,17 +68,16 @@ import android.content.res.Configuration.Orientation; import android.graphics.Rect; import android.hardware.camera2.CameraManager; import android.os.Handler; +import android.os.RemoteException; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; -import android.view.DisplayInfo; import android.view.Surface; import androidx.test.filters.SmallTest; import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges; -import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TestRule; @@ -91,6 +85,7 @@ import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import java.util.concurrent.Executor; +import java.util.function.Consumer; /** * Tests for {@link CameraCompatFreeformPolicy}. @@ -109,30 +104,18 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase { private static final String TEST_PACKAGE_1 = "com.android.frameworks.wmtests"; private static final String TEST_PACKAGE_2 = "com.test.package.two"; private static final String CAMERA_ID_1 = "camera-1"; - private AppCompatConfiguration mAppCompatConfiguration; - - private CameraManager.AvailabilityCallback mCameraAvailabilityCallback; - private CameraCompatFreeformPolicy mCameraCompatFreeformPolicy; - private ActivityRecord mActivity; - - // TODO(b/384465100): use a robot structure. - @Before - public void setUp() throws Exception { - setupAppCompatConfiguration(); - setupCameraManager(); - setupHandler(); - doReturn(true).when(() -> DesktopModeHelper.canEnterDesktopMode(any())); - } @Test @DisableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) public void testFeatureDisabled_cameraCompatFreeformPolicyNotCreated() { - configureActivity(SCREEN_ORIENTATION_PORTRAIT); + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT); - mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); - assertNull(mCameraCompatFreeformPolicy); + robot.checkCameraCompatPolicyNotCreated(); + }); } @Test @@ -140,31 +123,37 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase { FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT}) @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_SIMULATE_REQUESTED_ORIENTATION}) public void testIsCameraRunningAndWindowingModeEligible_disabledViaOverride_returnsFalse() { - configureActivity(SCREEN_ORIENTATION_PORTRAIT); + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT); - mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); - assertFalse(mCameraCompatFreeformPolicy.isCameraRunningAndWindowingModeEligible(mActivity)); + robot.checkIsCameraRunningAndWindowingModeEligible(false); + }); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) public void testIsCameraRunningAndWindowingModeEligible_cameraNotRunning_returnsFalse() { - configureActivity(SCREEN_ORIENTATION_PORTRAIT); + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT); - assertFalse(mCameraCompatFreeformPolicy.isCameraRunningAndWindowingModeEligible(mActivity)); + robot.checkIsCameraRunningAndWindowingModeEligible(false); + }); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) public void testIsCameraRunningAndWindowingModeEligible_notFreeformWindowing_returnsFalse() { - configureActivity(SCREEN_ORIENTATION_PORTRAIT, WINDOWING_MODE_FULLSCREEN); + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT, WINDOWING_MODE_FULLSCREEN); - onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); - assertFalse(mCameraCompatFreeformPolicy.isCameraRunningAndWindowingModeEligible(mActivity)); + robot.checkIsCameraRunningAndWindowingModeEligible(false); + }); } @Test @@ -172,64 +161,76 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase { @DisableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT) @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) public void testIsCameraRunningAndWindowingModeEligible_optInFreeformCameraRunning_true() { - configureActivity(SCREEN_ORIENTATION_PORTRAIT); + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT); - onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); - assertTrue(mCameraCompatFreeformPolicy.isCameraRunningAndWindowingModeEligible(mActivity)); + robot.checkIsCameraRunningAndWindowingModeEligible(true); + }); } @Test @EnableFlags({FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING, FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT}) public void testIsCameraRunningAndWindowingModeEligible_freeformCameraRunning_true() { - configureActivity(SCREEN_ORIENTATION_PORTRAIT); + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT); - onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); - assertTrue(mCameraCompatFreeformPolicy.isCameraRunningAndWindowingModeEligible(mActivity)); + robot.checkIsCameraRunningAndWindowingModeEligible(true); + }); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @DisableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT) public void testIsFreeformLetterboxingForCameraAllowed_optInMechanism_notOptedIn_retFalse() { - configureActivity(SCREEN_ORIENTATION_PORTRAIT); + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT); - onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); - assertFalse(mCameraCompatFreeformPolicy.isFreeformLetterboxingForCameraAllowed(mActivity)); + robot.checkIsFreeformLetterboxingForCameraAllowed(false); + }); } @Test @EnableFlags({FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING, FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT}) public void testIsFreeformLetterboxingForCameraAllowed_notOptedOut_returnsTrue() { - configureActivity(SCREEN_ORIENTATION_PORTRAIT); + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT); - onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); - assertTrue(mCameraCompatFreeformPolicy.isFreeformLetterboxingForCameraAllowed(mActivity)); + robot.checkIsFreeformLetterboxingForCameraAllowed(true); + }); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) public void testIsFreeformLetterboxingForCameraAllowed_cameraNotRunning_returnsFalse() { - configureActivity(SCREEN_ORIENTATION_PORTRAIT); + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT); - assertFalse(mCameraCompatFreeformPolicy.isFreeformLetterboxingForCameraAllowed(mActivity)); + robot.checkIsFreeformLetterboxingForCameraAllowed(false); + }); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) public void testIsFreeformLetterboxingForCameraAllowed_notFreeformWindowing_returnsFalse() { - configureActivity(SCREEN_ORIENTATION_PORTRAIT, WINDOWING_MODE_FULLSCREEN); + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT, WINDOWING_MODE_FULLSCREEN); - onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); - assertFalse(mCameraCompatFreeformPolicy.isFreeformLetterboxingForCameraAllowed(mActivity)); + robot.checkIsFreeformLetterboxingForCameraAllowed(false); + }); } @Test @@ -237,519 +238,603 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase { @DisableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT) @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) public void testIsFreeformLetterboxingForCameraAllowed_optInFreeformCameraRunning_true() { - configureActivity(SCREEN_ORIENTATION_PORTRAIT); + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT); - onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); - assertTrue(mCameraCompatFreeformPolicy.isFreeformLetterboxingForCameraAllowed(mActivity)); + robot.checkIsFreeformLetterboxingForCameraAllowed(true); + }); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) public void testFullscreen_doesNotActivateCameraCompatMode() { - configureActivity(SCREEN_ORIENTATION_PORTRAIT, WINDOWING_MODE_FULLSCREEN); - doReturn(false).when(mActivity).inFreeformWindowingMode(); + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT, WINDOWING_MODE_FULLSCREEN); + robot.setInFreeformWindowingMode(false); - onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); - assertNotInCameraCompatMode(); + robot.assertNotInCameraCompatMode(); + }); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) public void testOrientationUnspecified_doesNotActivateCameraCompatMode() { - configureActivity(SCREEN_ORIENTATION_UNSPECIFIED); + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_UNSPECIFIED); - assertNotInCameraCompatMode(); + robot.assertNotInCameraCompatMode(); + }); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) public void testNoCameraConnection_doesNotActivateCameraCompatMode() { - configureActivity(SCREEN_ORIENTATION_PORTRAIT); - assertNotInCameraCompatMode(); + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT); + + robot.assertNotInCameraCompatMode(); + }); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) - public void testCameraConnected_deviceInPortrait_portraitCameraCompatMode() throws Exception { - configureActivity(SCREEN_ORIENTATION_PORTRAIT); - setDisplayRotation(ROTATION_0); + public void testCameraConnected_deviceInPortrait_portraitCameraCompatMode() { + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT); + robot.activity().rotateDisplayForTopActivity(ROTATION_0); - onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); - assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_PORTRAIT); - assertActivityRefreshRequested(/* refreshRequested */ false); + robot.assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_PORTRAIT); + robot.assertActivityRefreshRequested(/* refreshRequested */ false); + }); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) - public void testCameraConnected_deviceInLandscape_portraitCameraCompatMode() throws Exception { - configureActivity(SCREEN_ORIENTATION_PORTRAIT); - setDisplayRotation(ROTATION_270); + public void testCameraConnected_deviceInLandscape_portraitCameraCompatMode() { + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT); + robot.activity().rotateDisplayForTopActivity(ROTATION_270); - onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); - assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE); - assertActivityRefreshRequested(/* refreshRequested */ false); + robot.assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE); + robot.assertActivityRefreshRequested(/* refreshRequested */ false); + }); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) - public void testCameraConnected_deviceInPortrait_landscapeCameraCompatMode() throws Exception { - configureActivity(SCREEN_ORIENTATION_LANDSCAPE); - setDisplayRotation(ROTATION_0); + public void testCameraConnected_deviceInPortrait_landscapeCameraCompatMode() { + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_LANDSCAPE); + robot.activity().rotateDisplayForTopActivity(ROTATION_0); - onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); - assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_LANDSCAPE_DEVICE_IN_PORTRAIT); - assertActivityRefreshRequested(/* refreshRequested */ false); + robot.assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_LANDSCAPE_DEVICE_IN_PORTRAIT); + robot.assertActivityRefreshRequested(/* refreshRequested */ false); + }); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) - public void testCameraConnected_deviceInLandscape_landscapeCameraCompatMode() throws Exception { - configureActivity(SCREEN_ORIENTATION_LANDSCAPE); - setDisplayRotation(ROTATION_270); + public void testCameraConnected_deviceInLandscape_landscapeCameraCompatMode() { + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_LANDSCAPE); + robot.activity().rotateDisplayForTopActivity(ROTATION_270); - onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); - assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_LANDSCAPE_DEVICE_IN_LANDSCAPE); - assertActivityRefreshRequested(/* refreshRequested */ false); + robot.assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_LANDSCAPE_DEVICE_IN_LANDSCAPE); + robot.assertActivityRefreshRequested(/* refreshRequested */ false); + }); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) - public void testCameraReconnected_cameraCompatModeAndRefresh() throws Exception { - configureActivity(SCREEN_ORIENTATION_PORTRAIT); - setDisplayRotation(ROTATION_270); + public void testCameraReconnected_cameraCompatModeAndRefresh() { + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT); + robot.activity().rotateDisplayForTopActivity(ROTATION_270); - onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); - callOnActivityConfigurationChanging(mActivity, /* letterboxNew= */ true, + robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + robot.callOnActivityConfigurationChanging(/* letterboxNew= */ true, /* lastLetterbox= */ false); - assertActivityRefreshRequested(/* refreshRequested */ true); - onCameraClosed(CAMERA_ID_1); - onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); - // Activity is letterboxed from the previous configuration change. - callOnActivityConfigurationChanging(mActivity, /* letterboxNew= */ true, - /* lastLetterbox= */ true); + robot.assertActivityRefreshRequested(/* refreshRequested */ true); + robot.onCameraClosed(CAMERA_ID_1); + robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + // Activity is letterboxed from the previous configuration change. + robot.callOnActivityConfigurationChanging(/* letterboxNew= */ true, + /* lastLetterbox= */ true); - assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE); - assertActivityRefreshRequested(/* refreshRequested */ true); + robot.assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE); + robot.assertActivityRefreshRequested(/* refreshRequested */ true); + }); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) public void testCameraOpenedForDifferentPackage_notInCameraCompatMode() { - configureActivity(SCREEN_ORIENTATION_PORTRAIT); + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT); - onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_2); + robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_2); - assertNotInCameraCompatMode(); + robot.assertNotInCameraCompatMode(); + }); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @DisableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT) public void testShouldApplyCameraCompatFreeformTreatment_overrideNotEnabled_returnsFalse() { - configureActivity(SCREEN_ORIENTATION_PORTRAIT); + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT); - onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); - assertFalse(mCameraCompatFreeformPolicy.isTreatmentEnabledForActivity(mActivity, - /* checkOrientation */ true)); + robot.checkIsCameraCompatTreatmentActiveForTopActivity(false); + }); } @Test @EnableFlags({FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING, FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING_OPT_OUT}) public void testShouldApplyCameraCompatFreeformTreatment_notOptedOut_returnsTrue() { - configureActivity(SCREEN_ORIENTATION_PORTRAIT); + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT); - onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); - assertTrue(mCameraCompatFreeformPolicy.isTreatmentEnabledForActivity(mActivity, - /* checkOrientation */ true)); + robot.checkIsCameraCompatTreatmentActiveForTopActivity(true); + }); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @EnableCompatChanges(OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT) public void testShouldApplyCameraCompatFreeformTreatment_enabledByOverride_returnsTrue() { - configureActivity(SCREEN_ORIENTATION_PORTRAIT); + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT); - onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); - assertTrue(mActivity.info - .isChangeEnabled(OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT)); - assertTrue(mCameraCompatFreeformPolicy.isTreatmentEnabledForActivity(mActivity, - /* checkOrientation */ true)); + robot.checkIsCameraCompatTreatmentActiveForTopActivity(true); + }); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) public void testShouldRefreshActivity_appBoundsChanged_returnsTrue() { - configureActivity(SCREEN_ORIENTATION_PORTRAIT); - Configuration oldConfiguration = createConfiguration(/* letterbox= */ false); - Configuration newConfiguration = createConfiguration(/* letterbox= */ true); - onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT); - assertTrue(mCameraCompatFreeformPolicy.shouldRefreshActivity(mActivity, newConfiguration, - oldConfiguration)); + robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + + robot.checkShouldRefreshActivity(/* expected= */ true, + robot.createConfiguration(/* letterbox= */ true, /* rotation= */ 0), + robot.createConfiguration(/* letterbox= */ false, /* rotation= */ 0)); + }); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) public void testShouldRefreshActivity_displayRotationChanged_returnsTrue() { - configureActivity(SCREEN_ORIENTATION_PORTRAIT); - Configuration oldConfiguration = createConfiguration(/* letterbox= */ true); - Configuration newConfiguration = createConfiguration(/* letterbox= */ true); + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT); - oldConfiguration.windowConfiguration.setDisplayRotation(0); - newConfiguration.windowConfiguration.setDisplayRotation(90); - onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); - assertTrue(mCameraCompatFreeformPolicy.shouldRefreshActivity(mActivity, newConfiguration, - oldConfiguration)); + robot.checkShouldRefreshActivity(/* expected= */ true, + robot.createConfiguration(/* letterbox= */ true, /* rotation= */ 90), + robot.createConfiguration(/* letterbox= */ true, /* rotation= */ 0)); + }); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) public void testShouldRefreshActivity_appBoundsNorDisplayChanged_returnsFalse() { - configureActivity(SCREEN_ORIENTATION_PORTRAIT); - Configuration oldConfiguration = createConfiguration(/* letterbox= */ true); - Configuration newConfiguration = createConfiguration(/* letterbox= */ true); + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT); - oldConfiguration.windowConfiguration.setDisplayRotation(0); - newConfiguration.windowConfiguration.setDisplayRotation(0); - onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); - assertFalse(mCameraCompatFreeformPolicy.shouldRefreshActivity(mActivity, newConfiguration, - oldConfiguration)); + robot.checkShouldRefreshActivity(/* expected= */ false, + robot.createConfiguration(/* letterbox= */ true, /* rotation= */ 0), + robot.createConfiguration(/* letterbox= */ true, /* rotation= */ 0)); + }); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) - public void testOnActivityConfigurationChanging_refreshDisabledViaFlag_noRefresh() - throws Exception { - configureActivity(SCREEN_ORIENTATION_PORTRAIT); - - doReturn(false).when(mActivity.mAppCompatController.getCameraOverrides()) - .shouldRefreshActivityForCameraCompat(); + public void testOnActivityConfigurationChanging_refreshDisabledViaFlag_noRefresh() { + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT); + robot.activity().setShouldRefreshActivityForCameraCompat(false); - onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); - callOnActivityConfigurationChanging(mActivity); + robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + robot.callOnActivityConfigurationChanging(); - assertActivityRefreshRequested(/* refreshRequested */ false); + robot.assertActivityRefreshRequested(/* refreshRequested */ false); + }); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) - public void testOnActivityConfigurationChanging_cycleThroughStopDisabled() throws Exception { - when(mAppCompatConfiguration.isCameraCompatRefreshCycleThroughStopEnabled()) - .thenReturn(false); + public void testOnActivityConfigurationChanging_cycleThroughStopDisabled() { + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT); + robot.conf().enableCameraCompatRefreshCycleThroughStop(false); - configureActivity(SCREEN_ORIENTATION_PORTRAIT); + robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + robot.callOnActivityConfigurationChanging(); - onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); - callOnActivityConfigurationChanging(mActivity); - - assertActivityRefreshRequested(/* refreshRequested */ true, /* cycleThroughStop */ false); + robot.assertActivityRefreshRequested(/* refreshRequested */ true, + /* cycleThroughStop */ false); + }); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) - public void testOnActivityConfigurationChanging_cycleThroughStopDisabledForApp() - throws Exception { - configureActivity(SCREEN_ORIENTATION_PORTRAIT); - doReturn(true).when(mActivity.mAppCompatController.getCameraOverrides()) - .shouldRefreshActivityViaPauseForCameraCompat(); + public void testOnActivityConfigurationChanging_cycleThroughStopDisabledForApp() { + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT); + robot.setShouldRefreshActivityViaPause(true); - onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); - callOnActivityConfigurationChanging(mActivity); + robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + robot.callOnActivityConfigurationChanging(); - assertActivityRefreshRequested(/* refreshRequested */ true, /* cycleThroughStop */ false); + robot.assertActivityRefreshRequested(/* refreshRequested */ true, + /* cycleThroughStop */ false); + }); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) public void testGetCameraCompatAspectRatio_activityNotInCameraCompat_returnsDefaultAspRatio() { - configureActivity(SCREEN_ORIENTATION_FULL_USER); + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_FULL_USER); - onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); - callOnActivityConfigurationChanging(mActivity); + robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + robot.callOnActivityConfigurationChanging(); - assertEquals(MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO, - mCameraCompatFreeformPolicy.getCameraCompatAspectRatio(mActivity), - /* delta= */ 0.001); + robot.checkCameraCompatAspectRatioEquals(MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO); + }); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) public void testGetCameraCompatAspectRatio_activityInCameraCompat_returnsConfigAspectRatio() { - configureActivity(SCREEN_ORIENTATION_PORTRAIT); - final float configAspectRatio = 1.5f; - mWm.mAppCompatConfiguration.setCameraCompatAspectRatio(configAspectRatio); + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT); + final float configAspectRatio = 1.5f; + robot.conf().setCameraCompatAspectRatio(configAspectRatio); - onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); - callOnActivityConfigurationChanging(mActivity); + robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + robot.callOnActivityConfigurationChanging(); - assertEquals(configAspectRatio, - mCameraCompatFreeformPolicy.getCameraCompatAspectRatio(mActivity), - /* delta= */ 0.001); + robot.checkCameraCompatAspectRatioEquals(configAspectRatio); + }); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) public void testGetCameraCompatAspectRatio_inCameraCompatPerAppOverride_returnDefAspectRatio() { - configureActivity(SCREEN_ORIENTATION_PORTRAIT); - final float configAspectRatio = 1.5f; - mWm.mAppCompatConfiguration.setCameraCompatAspectRatio(configAspectRatio); - doReturn(true).when(mActivity.mAppCompatController.getCameraOverrides()) - .isOverrideMinAspectRatioForCameraEnabled(); + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT); + robot.conf().setCameraCompatAspectRatio(1.5f); + robot.setOverrideMinAspectRatioEnabled(true); - onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); - callOnActivityConfigurationChanging(mActivity); + robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + robot.callOnActivityConfigurationChanging(); - assertEquals(MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO, - mCameraCompatFreeformPolicy.getCameraCompatAspectRatio(mActivity), - /* delta= */ 0.001); + robot.checkCameraCompatAspectRatioEquals(MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO); + }); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) - public void testOnCameraOpened_portraitActivity_sandboxesDisplayRotationAndUpdatesApp() throws - Exception { - configureActivity(SCREEN_ORIENTATION_PORTRAIT); - setDisplayRotation(ROTATION_270); + public void testOnCameraOpened_portraitActivity_sandboxesDisplayRotationAndUpdatesApp() { + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_PORTRAIT); + robot.activity().rotateDisplayForTopActivity(ROTATION_270); - onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); - // This is a portrait rotation for a device with portrait natural orientation (most common, - // currently the only one supported). - assertCompatibilityInfoSentWithDisplayRotation(ROTATION_0); + // This is a portrait rotation for a device with portrait natural orientation (most + // common, currently the only one supported). + robot.assertCompatibilityInfoSentWithDisplayRotation(ROTATION_0); + }); } @Test @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT}) - public void testOnCameraOpened_landscapeActivity_sandboxesDisplayRotationAndUpdatesApp() throws - Exception { - configureActivity(SCREEN_ORIENTATION_LANDSCAPE); - setDisplayRotation(ROTATION_0); - - onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); - - // This is a landscape rotation for a device with portrait natural orientation (most common, - // currently the only one supported). - assertCompatibilityInfoSentWithDisplayRotation(ROTATION_90); - } - - private void setupAppCompatConfiguration() { - mAppCompatConfiguration = mDisplayContent.mWmService.mAppCompatConfiguration; - spyOn(mAppCompatConfiguration); - when(mAppCompatConfiguration.isCameraCompatTreatmentEnabled()).thenReturn(true); - when(mAppCompatConfiguration.isCameraCompatTreatmentEnabledAtBuildTime()).thenReturn(true); - when(mAppCompatConfiguration.isCameraCompatRefreshEnabled()).thenReturn(true); - when(mAppCompatConfiguration.isCameraCompatSplitScreenAspectRatioEnabled()) - .thenReturn(false); - when(mAppCompatConfiguration.isCameraCompatRefreshCycleThroughStopEnabled()) - .thenReturn(true); - } - - private void setupCameraManager() { - final CameraManager mockCameraManager = mock(CameraManager.class); - doAnswer(invocation -> { - mCameraAvailabilityCallback = invocation.getArgument(1); - return null; - }).when(mockCameraManager).registerAvailabilityCallback( - any(Executor.class), any(CameraManager.AvailabilityCallback.class)); - - when(mContext.getSystemService(CameraManager.class)).thenReturn(mockCameraManager); - } - - private void setupHandler() { - final Handler handler = mDisplayContent.mWmService.mH; - spyOn(handler); - - when(handler.postDelayed(any(Runnable.class), anyLong())).thenAnswer( - invocation -> { - ((Runnable) invocation.getArgument(0)).run(); - return null; - }); - } - - private void configureActivity(@ScreenOrientation int activityOrientation) { - configureActivity(activityOrientation, WINDOWING_MODE_FREEFORM); - } - - private void configureActivity(@ScreenOrientation int activityOrientation, - @WindowingMode int windowingMode) { - configureActivityAndDisplay(activityOrientation, ORIENTATION_PORTRAIT, windowingMode); - } - - private void configureActivityAndDisplay(@ScreenOrientation int activityOrientation, - @Orientation int naturalOrientation, @WindowingMode int windowingMode) { - setupDisplayContent(naturalOrientation); - final Task task = setupTask(windowingMode); - setupActivity(task, activityOrientation, windowingMode); - setupMockApplicationThread(); - - mCameraCompatFreeformPolicy = mDisplayContent.mAppCompatCameraPolicy - .mCameraCompatFreeformPolicy; - } - - private void setupDisplayContent(@Orientation int naturalOrientation) { - // Create a new DisplayContent so that the flag values create the camera freeform policy. - mDisplayContent = new TestDisplayContent.Builder(mAtm, mDisplayContent.getSurfaceWidth(), - mDisplayContent.getSurfaceHeight()).build(); - mDisplayContent.setIgnoreOrientationRequest(true); - setDisplayRotation(ROTATION_90); - doReturn(naturalOrientation).when(mDisplayContent).getNaturalOrientation(); - } - - private Task setupTask(@WindowingMode int windowingMode) { - final TaskDisplayArea tda = mDisplayContent.getDefaultTaskDisplayArea(); - spyOn(tda); - doReturn(true).when(tda).supportsNonResizableMultiWindow(); - - final Task task = new TaskBuilder(mSupervisor) - .setDisplay(mDisplayContent) - .setWindowingMode(windowingMode) - .build(); - task.setBounds(0, 0, 1000, 500); - return task; - } - - private void setupActivity(@NonNull Task task, @ScreenOrientation int activityOrientation, - @WindowingMode int windowingMode) { - mActivity = new ActivityBuilder(mAtm) - // Set the component to be that of the test class in order to enable compat changes - .setComponent(ComponentName.createRelative(mContext, - com.android.server.wm.CameraCompatFreeformPolicyTests.class.getName())) - .setScreenOrientation(activityOrientation) - .setResizeMode(RESIZE_MODE_RESIZEABLE) - .setCreateTask(true) - .setOnTop(true) - .setTask(task) - .build(); - mActivity.mAppCompatController.getSizeCompatModePolicy().clearSizeCompatMode(); - - spyOn(mActivity.mAppCompatController.getCameraOverrides()); - spyOn(mActivity.info); - - doReturn(mActivity).when(mDisplayContent).topRunningActivity(anyBoolean()); - doReturn(windowingMode == WINDOWING_MODE_FREEFORM).when(mActivity) - .inFreeformWindowingMode(); - } - - private void onCameraOpened(@NonNull String cameraId, @NonNull String packageName) { - mCameraAvailabilityCallback.onCameraOpened(cameraId, packageName); - waitHandlerIdle(mDisplayContent.mWmService.mH); - } - - private void onCameraClosed(@NonNull String cameraId) { - mCameraAvailabilityCallback.onCameraClosed(cameraId); - waitHandlerIdle(mDisplayContent.mWmService.mH); - } - - private void assertInCameraCompatMode(@CameraCompatTaskInfo.FreeformCameraCompatMode int mode) { - assertEquals(mode, mCameraCompatFreeformPolicy.getCameraCompatMode(mActivity)); - } - - private void assertNotInCameraCompatMode() { - assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_NONE); - } - - private void assertActivityRefreshRequested(boolean refreshRequested) throws Exception { - assertActivityRefreshRequested(refreshRequested, /* cycleThroughStop*/ true); - } - - private void assertActivityRefreshRequested(boolean refreshRequested, - boolean cycleThroughStop) throws Exception { - verify(mActivity.mAppCompatController.getCameraOverrides(), - times(refreshRequested ? 1 : 0)).setIsRefreshRequested(true); - - final RefreshCallbackItem refreshCallbackItem = - new RefreshCallbackItem(mActivity.token, cycleThroughStop ? ON_STOP : ON_PAUSE); - final ResumeActivityItem resumeActivityItem = new ResumeActivityItem(mActivity.token, - /* isForward */ false, /* shouldSendCompatFakeFocus */ false); - - verify(mActivity.mAtmService.getLifecycleManager(), times(refreshRequested ? 1 : 0)) - .scheduleTransactionItems(mActivity.app.getThread(), - refreshCallbackItem, resumeActivityItem); - } - - private void callOnActivityConfigurationChanging(ActivityRecord activity) { - callOnActivityConfigurationChanging(activity, /* letterboxNew= */ true, - /* lastLetterbox= */false); - } - - private void callOnActivityConfigurationChanging(ActivityRecord activity, boolean letterboxNew, - boolean lastLetterbox) { - mDisplayContent.mAppCompatCameraPolicy.mActivityRefresher - .onActivityConfigurationChanging(activity, - /* newConfig */ createConfiguration(letterboxNew), - /* lastReportedConfig */ createConfiguration(lastLetterbox)); - } - - private Configuration createConfiguration(boolean letterbox) { - final Configuration configuration = new Configuration(); - Rect bounds = letterbox ? new Rect(/*left*/ 300, /*top*/ 0, /*right*/ 700, /*bottom*/ 600) - : new Rect(/*left*/ 0, /*top*/ 0, /*right*/ 1000, /*bottom*/ 600); - configuration.windowConfiguration.setAppBounds(bounds); - return configuration; - } - - private void setDisplayRotation(@Surface.Rotation int displayRotation) { - doAnswer(invocation -> { - DisplayInfo displayInfo = new DisplayInfo(); - mDisplayContent.getDisplay().getDisplayInfo(displayInfo); - displayInfo.rotation = displayRotation; - // Set height so that the natural orientation (rotation is 0) is portrait. This is the - // case for most standard phones and tablets. - // TODO(b/365725400): handle landscape natural orientation. - displayInfo.logicalHeight = displayRotation % 180 == 0 ? 800 : 600; - displayInfo.logicalWidth = displayRotation % 180 == 0 ? 600 : 800; - return displayInfo; - }).when(mDisplayContent.mWmService.mDisplayManagerInternal) - .getDisplayInfo(anyInt()); - } - - private void setupMockApplicationThread() { - IApplicationThread mockApplicationThread = mock(IApplicationThread.class); - spyOn(mActivity.app); - doReturn(mockApplicationThread).when(mActivity.app).getThread(); - } - - private void assertCompatibilityInfoSentWithDisplayRotation(@Surface.Rotation int - expectedRotation) throws Exception { - final ArgumentCaptor<CompatibilityInfo> compatibilityInfoArgumentCaptor = - ArgumentCaptor.forClass(CompatibilityInfo.class); - verify(mActivity.app.getThread()).updatePackageCompatibilityInfo(eq(mActivity.packageName), - compatibilityInfoArgumentCaptor.capture()); - - final CompatibilityInfo compatInfo = compatibilityInfoArgumentCaptor.getValue(); - assertTrue(compatInfo.isOverrideDisplayRotationRequired()); - assertEquals(expectedRotation, compatInfo.applicationDisplayRotation); + public void testOnCameraOpened_landscapeActivity_sandboxesDisplayRotationAndUpdatesApp() { + runTestScenario((robot) -> { + robot.configureActivity(SCREEN_ORIENTATION_LANDSCAPE); + robot.activity().rotateDisplayForTopActivity(ROTATION_0); + + robot.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + + // This is a landscape rotation for a device with portrait natural orientation (most + // common, currently the only one supported). + robot.assertCompatibilityInfoSentWithDisplayRotation(ROTATION_90); + }); + } + + /** + * Runs a test scenario providing a Robot. + */ + void runTestScenario(@NonNull Consumer<CameraCompatFreeformPolicyRobotTests> consumer) { + final CameraCompatFreeformPolicyRobotTests robot = + new CameraCompatFreeformPolicyRobotTests(mWm, mAtm, mSupervisor, this); + consumer.accept(robot); + } + + private static class CameraCompatFreeformPolicyRobotTests extends AppCompatRobotBase { + private final WindowTestsBase mWindowTestsBase; + + private CameraManager.AvailabilityCallback mCameraAvailabilityCallback; + + CameraCompatFreeformPolicyRobotTests(@NonNull WindowManagerService wm, + @NonNull ActivityTaskManagerService atm, + @NonNull ActivityTaskSupervisor supervisor, + @NonNull WindowTestsBase windowTestsBase) { + super(wm, atm, supervisor); + mWindowTestsBase = windowTestsBase; + setupCameraManager(); + setupAppCompatConfiguration(); + } + + @Override + void onPostDisplayContentCreation(@NonNull DisplayContent displayContent) { + super.onPostDisplayContentCreation(displayContent); + spyOn(displayContent.mAppCompatCameraPolicy); + if (displayContent.mAppCompatCameraPolicy.mCameraCompatFreeformPolicy != null) { + spyOn(displayContent.mAppCompatCameraPolicy.mCameraCompatFreeformPolicy); + } + } + + @Override + void onPostActivityCreation(@NonNull ActivityRecord activity) { + super.onPostActivityCreation(activity); + setupCameraManager(); + setupHandler(); + setupMockApplicationThread(); + } + + private void setupMockApplicationThread() { + IApplicationThread mockApplicationThread = mock(IApplicationThread.class); + spyOn(activity().top().app); + doReturn(mockApplicationThread).when(activity().top().app).getThread(); + } + + private Configuration createConfiguration(boolean letterbox, int rotation) { + final Configuration configuration = createConfiguration(letterbox); + configuration.windowConfiguration.setDisplayRotation(rotation); + return configuration; + } + + private Configuration createConfiguration(boolean letterbox) { + final Configuration configuration = new Configuration(); + Rect bounds = letterbox ? new Rect(/*left*/ 300, /*top*/ 0, /*right*/ 700, /*bottom*/ + 600) + : new Rect(/*left*/ 0, /*top*/ 0, /*right*/ 1000, /*bottom*/ 600); + configuration.windowConfiguration.setAppBounds(bounds); + return configuration; + } + + private void setupAppCompatConfiguration() { + applyOnConf((c) -> { + c.enableCameraCompatTreatment(true); + c.enableCameraCompatTreatmentAtBuildTime(true); + c.enableCameraCompatRefresh(true); + c.enableCameraCompatRefreshCycleThroughStop(true); + c.enableCameraCompatSplitScreenAspectRatio(false); + }); + } + + private void setupCameraManager() { + final CameraManager mockCameraManager = mock(CameraManager.class); + doAnswer(invocation -> { + mCameraAvailabilityCallback = invocation.getArgument(1); + return null; + }).when(mockCameraManager).registerAvailabilityCallback( + any(Executor.class), any(CameraManager.AvailabilityCallback.class)); + + doReturn(mockCameraManager).when(mWindowTestsBase.mWm.mContext).getSystemService( + CameraManager.class); + } + + private void setupHandler() { + final Handler handler = activity().top().mWmService.mH; + spyOn(handler); + + doAnswer(invocation -> { + ((Runnable) invocation.getArgument(0)).run(); + return null; + }).when(handler).postDelayed(any(Runnable.class), anyLong()); + } + + private void configureActivity(@ScreenOrientation int activityOrientation) { + configureActivity(activityOrientation, WINDOWING_MODE_FREEFORM); + } + + private void configureActivity(@ScreenOrientation int activityOrientation, + @WindowingMode int windowingMode) { + configureActivityAndDisplay(activityOrientation, ORIENTATION_PORTRAIT, windowingMode); + } + + private void configureActivityAndDisplay(@ScreenOrientation int activityOrientation, + @Orientation int naturalOrientation, @WindowingMode int windowingMode) { + applyOnActivity(a -> { + dw().allowEnterDesktopMode(true); + a.createActivityWithComponentInNewTaskAndDisplay(); + a.setIgnoreOrientationRequest(true); + a.rotateDisplayForTopActivity(ROTATION_90); + a.configureTopActivity(/* minAspect */ -1, /* maxAspect */ -1, + activityOrientation, /* isUnresizable */ false); + a.top().setWindowingMode(windowingMode); + a.displayContent().setWindowingMode(windowingMode); + a.setDisplayNaturalOrientation(naturalOrientation); + spyOn(a.top().mAppCompatController.getCameraOverrides()); + spyOn(a.top().info); + doReturn(a.displayContent().getDisplayInfo()).when( + a.displayContent().mWmService.mDisplayManagerInternal).getDisplayInfo( + a.displayContent().mDisplayId); + }); + } + + private void onCameraOpened(@NonNull String cameraId, @NonNull String packageName) { + mCameraAvailabilityCallback.onCameraOpened(cameraId, packageName); + waitHandlerIdle(); + } + + private void onCameraClosed(@NonNull String cameraId) { + mCameraAvailabilityCallback.onCameraClosed(cameraId); + } + + private void waitHandlerIdle() { + mWindowTestsBase.waitHandlerIdle(activity().displayContent().mWmService.mH); + } + + void setInFreeformWindowingMode(boolean inFreeform) { + doReturn(inFreeform).when(activity().top()).inFreeformWindowingMode(); + } + + void setShouldRefreshActivityViaPause(boolean enabled) { + doReturn(enabled).when(activity().top().mAppCompatController.getCameraOverrides()) + .shouldRefreshActivityViaPauseForCameraCompat(); + } + + void checkShouldRefreshActivity(boolean expected, Configuration newConfig, + Configuration oldConfig) { + assertEquals(expected, cameraCompatFreeformPolicy().shouldRefreshActivity( + activity().top(), newConfig, oldConfig)); + } + + void checkCameraCompatPolicyNotCreated() { + assertNull(cameraCompatFreeformPolicy()); + } + + void checkIsCameraRunningAndWindowingModeEligible(boolean expected) { + assertEquals(expected, cameraCompatFreeformPolicy() + .isCameraRunningAndWindowingModeEligible(activity().top())); + } + + void checkIsFreeformLetterboxingForCameraAllowed(boolean expected) { + assertEquals(expected, cameraCompatFreeformPolicy() + .isFreeformLetterboxingForCameraAllowed(activity().top())); + } + + void checkCameraCompatAspectRatioEquals(float aspectRatio) { + assertEquals(aspectRatio, + cameraCompatFreeformPolicy().getCameraCompatAspectRatio(activity().top()), + /* delta= */ 0.001); + } + + private void assertInCameraCompatMode(@FreeformCameraCompatMode int mode) { + assertEquals(mode, cameraCompatFreeformPolicy().getCameraCompatMode(activity().top())); + } + + private void assertNotInCameraCompatMode() { + assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_NONE); + } + + private void assertActivityRefreshRequested(boolean refreshRequested) { + assertActivityRefreshRequested(refreshRequested, /* cycleThroughStop*/ true); + } + + private void assertActivityRefreshRequested(boolean refreshRequested, + boolean cycleThroughStop) { + verify(activity().top().mAppCompatController.getCameraOverrides(), + times(refreshRequested ? 1 : 0)).setIsRefreshRequested(true); + + final RefreshCallbackItem refreshCallbackItem = + new RefreshCallbackItem(activity().top().token, + cycleThroughStop ? ON_STOP : ON_PAUSE); + final ResumeActivityItem resumeActivityItem = new ResumeActivityItem( + activity().top().token, + /* isForward */ false, /* shouldSendCompatFakeFocus */ false); + try { + verify(activity().top().mAtmService.getLifecycleManager(), + times(refreshRequested ? 1 : 0)) + .scheduleTransactionItems(activity().top().app.getThread(), + refreshCallbackItem, resumeActivityItem); + } catch (RemoteException e) { + fail(e.getMessage()); + } + } + + private void callOnActivityConfigurationChanging() { + callOnActivityConfigurationChanging(/* letterboxNew= */ true, + /* lastLetterbox= */false); + } + + private void callOnActivityConfigurationChanging(boolean letterboxNew, + boolean lastLetterbox) { + activity().displayContent().mAppCompatCameraPolicy.mActivityRefresher + .onActivityConfigurationChanging(activity().top(), + /* newConfig */ createConfiguration(letterboxNew), + /* lastReportedConfig */ createConfiguration(lastLetterbox)); + } + + void checkIsCameraCompatTreatmentActiveForTopActivity(boolean active) { + assertEquals(active, + cameraCompatFreeformPolicy().isTreatmentEnabledForActivity(activity().top(), + /* checkOrientation */ true)); + } + + void setOverrideMinAspectRatioEnabled(boolean enabled) { + doReturn(enabled).when(activity().top().mAppCompatController.getCameraOverrides()) + .isOverrideMinAspectRatioForCameraEnabled(); + } + + void assertCompatibilityInfoSentWithDisplayRotation(@Surface.Rotation int + expectedRotation) { + final ArgumentCaptor<CompatibilityInfo> compatibilityInfoArgumentCaptor = + ArgumentCaptor.forClass(CompatibilityInfo.class); + try { + verify(activity().top().app.getThread()).updatePackageCompatibilityInfo( + eq(activity().top().packageName), + compatibilityInfoArgumentCaptor.capture()); + } catch (RemoteException e) { + fail(e.getMessage()); + } + + final CompatibilityInfo compatInfo = compatibilityInfoArgumentCaptor.getValue(); + assertTrue(compatInfo.isOverrideDisplayRotationRequired()); + assertEquals(expectedRotation, compatInfo.applicationDisplayRotation); + } + + CameraCompatFreeformPolicy cameraCompatFreeformPolicy() { + return activity().displayContent().mAppCompatCameraPolicy.mCameraCompatFreeformPolicy; + } } } diff --git a/services/tests/wmtests/src/com/android/server/wm/DesktopModeHelperTest.java b/services/tests/wmtests/src/com/android/server/wm/DesktopModeHelperTest.java index 1e91bedb5c18..43755ea3165e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DesktopModeHelperTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/DesktopModeHelperTest.java @@ -181,6 +181,7 @@ public class DesktopModeHelperTest { assertThat(DesktopModeHelper.isDeviceEligibleForDesktopMode(mMockContext)).isTrue(); } + @DisableFlags(Flags.FLAG_ENABLE_PROJECTED_DISPLAY_DESKTOP_MODE) @Test public void isDeviceEligibleForDesktopMode_configDEModeOffAndIntDispHostsDesktop_returnsFalse() { doReturn(true).when(mMockResources).getBoolean(eq(R.bool.config_isDesktopModeSupported)); diff --git a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java index bc37496d14a7..e87e107cd793 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java @@ -21,6 +21,7 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE; import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE; import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_SMALL_VALUE; @@ -157,7 +158,7 @@ public class DesktopModeLaunchParamsModifierTests extends @Test @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE, Flags.FLAG_DISABLE_DESKTOP_LAUNCH_PARAMS_OUTSIDE_DESKTOP_BUG_FIX}) - public void testReturnsContinueIfVisibleFreeformTaskExists() { + public void testReturnsContinueIfFreeformTaskExists() { setupDesktopModeLaunchParamsModifier(); when(mTarget.isEnteringDesktopMode(any(), any(), any())).thenCallRealMethod(); @@ -165,7 +166,7 @@ public class DesktopModeLaunchParamsModifierTests extends final Task existingFreeformTask = new TaskBuilder(mSupervisor).setCreateActivity(true) .setWindowingMode(WINDOWING_MODE_FREEFORM).build(); doReturn(existingFreeformTask.getRootActivity()).when(dc) - .getTopMostVisibleFreeformActivity(); + .getTopMostFreeformActivity(); final Task launchingTask = new TaskBuilder(mSupervisor).build(); launchingTask.onDisplayChanged(dc); @@ -269,6 +270,38 @@ public class DesktopModeLaunchParamsModifierTests extends } @Test + @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE, + Flags.FLAG_INHERIT_TASK_BOUNDS_FOR_TRAMPOLINE_TASK_LAUNCHES}) + public void testInheritTaskBoundsFromExistingInstanceIfClosing() { + setupDesktopModeLaunchParamsModifier(); + + final String packageName = "com.same.package"; + // Setup existing task. + final DisplayContent dc = spy(createNewDisplay()); + final Task existingFreeformTask = new TaskBuilder(mSupervisor).setCreateActivity(true) + .setWindowingMode(WINDOWING_MODE_FREEFORM).setPackage(packageName).build(); + existingFreeformTask.setBounds( + /* left */ 0, + /* top */ 0, + /* right */ 500, + /* bottom */ 500); + doReturn(existingFreeformTask.getRootActivity()).when(dc) + .getTopMostVisibleFreeformActivity(); + // Set up new instance of already existing task. By default multi instance is not supported + // so first instance will close. + final Task launchingTask = new TaskBuilder(mSupervisor).setPackage(packageName) + .setCreateActivity(true).build(); + launchingTask.onDisplayChanged(dc); + launchingTask.intent.addFlags(FLAG_ACTIVITY_NEW_TASK); + + // New instance should inherit task bounds of old instance. + assertEquals(RESULT_DONE, + new CalculateRequestBuilder().setTask(launchingTask) + .setActivity(launchingTask.getRootActivity()).calculate()); + assertEquals(existingFreeformTask.getBounds(), mResult.mBounds); + } + + @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE) @DisableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) public void testUsesDesiredBoundsIfEmptyLayoutAndActivityOptionsBounds() { diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayCompatTests.java new file mode 100644 index 000000000000..1445a6982c60 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayCompatTests.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import static android.view.Display.DEFAULT_DISPLAY; + +import static com.android.window.flags.Flags.FLAG_ENABLE_RESTART_MENU_FOR_CONNECTED_DISPLAYS; + +import static junit.framework.Assert.assertFalse; + +import static org.junit.Assert.assertTrue; + +import android.compat.testing.PlatformCompatChangeRule; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.annotations.Presubmit; +import android.view.DisplayInfo; + +import androidx.test.filters.MediumTest; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; +import org.junit.runner.RunWith; + +/** + * Build/Install/Run: + * atest WmTests:DisplayCompatTests + */ +@MediumTest +@Presubmit +@RunWith(WindowTestRunner.class) +public class DisplayCompatTests extends WindowTestsBase { + + @Rule + public TestRule compatChangeRule = new PlatformCompatChangeRule(); + + @EnableFlags(FLAG_ENABLE_RESTART_MENU_FOR_CONNECTED_DISPLAYS) + @Test + public void testFixedMiscConfigurationWhenMovingToDisplay() { + // Create an app on the default display, at which point the restart menu isn't enabled. + final Task task = createTask(mDefaultDisplay); + final ActivityRecord activity = createActivityRecord(task); + assertFalse(task.getTaskInfo().appCompatTaskInfo.isRestartMenuEnabledForDisplayMove()); + + // Move the app to a secondary display, and the restart menu must get enabled. + final DisplayInfo displayInfo = new DisplayInfo(); + displayInfo.copyFrom(mDisplayInfo); + displayInfo.displayId = DEFAULT_DISPLAY + 1; + final DisplayContent secondaryDisplay = createNewDisplay(displayInfo); + task.reparent(secondaryDisplay.getDefaultTaskDisplayArea(), true); + assertTrue(task.getTaskInfo().appCompatTaskInfo.isRestartMenuEnabledForDisplayMove()); + + // Once the app gets restarted, the restart menu must be gone. + activity.restartProcessIfVisible(); + assertFalse(task.getTaskInfo().appCompatTaskInfo.isRestartMenuEnabledForDisplayMove()); + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java index 71e34ef220d3..3c6a89842af9 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java @@ -93,7 +93,7 @@ public class InsetsPolicyTest extends WindowTestsBase { final Task task1 = createTask(mDisplayContent); final Task task2 = createTask(mDisplayContent); - task1.setAdjacentTaskFragment(task2); + task1.setAdjacentTaskFragments(new TaskFragment.AdjacentSet(task1, task2)); final WindowState win = createAppWindow(task1, WINDOWING_MODE_MULTI_WINDOW, "app"); final InsetsSourceControl[] controls = addWindowAndGetControlsForDispatch(win); diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index 33a48aadbd70..e2c4a1d2dfea 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -572,8 +572,8 @@ public class SizeCompatTests extends WindowTestsBase { new TestDisplayContent.Builder(mAtm, 1000, 2000).build(); final InputDevice device = new InputDevice.Builder() .setAssociatedDisplayId(newDisplay.mDisplayId) - .setSources(InputDevice.SOURCE_TOUCHSCREEN | InputDevice.SOURCE_TRACKBALL - | InputDevice.KEYBOARD_TYPE_ALPHABETIC) + .setKeyboardType(InputDevice.KEYBOARD_TYPE_ALPHABETIC) + .setSources(InputDevice.SOURCE_TOUCHSCREEN | InputDevice.SOURCE_TRACKBALL) .build(); final InputDevice[] devices = {device}; doReturn(true).when(newDisplay.mWmService.mInputManager) @@ -596,6 +596,7 @@ public class SizeCompatTests extends WindowTestsBase { assertEquals(originalTouchscreen, newConfiguration.touchscreen); assertEquals(originalNavigation, newConfiguration.navigation); assertEquals(originalKeyboard, newConfiguration.keyboard); + // TODO(b/399749909): assert keyboardHidden, hardkeyboardHidden, and navigationHidden too. } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java index 3776b03695d5..b558fad84efa 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java +++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java @@ -78,6 +78,7 @@ import android.view.SurfaceControl; import com.android.dx.mockito.inline.extended.StaticMockitoSession; import com.android.internal.os.BackgroundThread; +import com.android.internal.protolog.PerfettoProtoLogImpl; import com.android.internal.protolog.ProtoLog; import com.android.internal.protolog.WmProtoLogGroups; import com.android.server.AnimationThread; @@ -187,7 +188,10 @@ public class SystemServicesTestRule implements TestRule { } private void setUp() { - ProtoLog.init(WmProtoLogGroups.values()); + if (ProtoLog.getSingleInstance() == null) { + ProtoLog.init(WmProtoLogGroups.values()); + PerfettoProtoLogImpl.waitForInitialization(); + } if (mOnBeforeServicesCreated != null) { mOnBeforeServicesCreated.run(); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java index 986532ce5897..ec83c50e95aa 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java @@ -87,7 +87,8 @@ public class TaskDisplayAreaTests extends WindowTestsBase { mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD); adjacentRootTask.mCreatedByOrganizer = true; final TaskDisplayArea taskDisplayArea = rootTask.getDisplayArea(); - adjacentRootTask.setAdjacentTaskFragment(rootTask); + adjacentRootTask.setAdjacentTaskFragments( + new TaskFragment.AdjacentSet(adjacentRootTask, rootTask)); taskDisplayArea.setLaunchAdjacentFlagRootTask(adjacentRootTask); Task actualRootTask = taskDisplayArea.getLaunchRootTask( @@ -113,7 +114,8 @@ public class TaskDisplayAreaTests extends WindowTestsBase { final Task adjacentRootTask = createTask( mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD); adjacentRootTask.mCreatedByOrganizer = true; - adjacentRootTask.setAdjacentTaskFragment(rootTask); + adjacentRootTask.setAdjacentTaskFragments( + new TaskFragment.AdjacentSet(adjacentRootTask, rootTask)); taskDisplayArea.setLaunchRootTask(rootTask, new int[]{WINDOWING_MODE_MULTI_WINDOW}, new int[]{ACTIVITY_TYPE_STANDARD}); @@ -135,7 +137,8 @@ public class TaskDisplayAreaTests extends WindowTestsBase { adjacentRootTask.mCreatedByOrganizer = true; createActivityRecord(adjacentRootTask); final TaskDisplayArea taskDisplayArea = rootTask.getDisplayArea(); - adjacentRootTask.setAdjacentTaskFragment(rootTask); + adjacentRootTask.setAdjacentTaskFragments( + new TaskFragment.AdjacentSet(adjacentRootTask, rootTask)); taskDisplayArea.setLaunchAdjacentFlagRootTask(adjacentRootTask); final Task actualRootTask = taskDisplayArea.getLaunchRootTask( @@ -821,7 +824,8 @@ public class TaskDisplayAreaTests extends WindowTestsBase { adjacentRootTask.mCreatedByOrganizer = true; final Task candidateTask = createTaskInRootTask(rootTask, 0 /* userId*/); final TaskDisplayArea taskDisplayArea = rootTask.getDisplayArea(); - adjacentRootTask.setAdjacentTaskFragment(rootTask); + adjacentRootTask.setAdjacentTaskFragments( + new TaskFragment.AdjacentSet(adjacentRootTask, rootTask)); // Verify the launch root with candidate task Task actualRootTask = taskDisplayArea.getLaunchRootTask(WINDOWING_MODE_UNDEFINED, diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java index ab76ae8e378a..76660bdc7355 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java @@ -784,7 +784,8 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { .setFragmentToken(fragmentToken2) .build(); mWindowOrganizerController.mLaunchTaskFragments.put(fragmentToken2, taskFragment2); - mTaskFragment.setAdjacentTaskFragment(taskFragment2); + mTaskFragment.setAdjacentTaskFragments( + new TaskFragment.AdjacentSet(mTaskFragment, taskFragment2)); mTransaction.clearAdjacentTaskFragments(mFragmentToken); mOrganizer.applyTransaction(mTransaction, TASK_FRAGMENT_TRANSIT_CHANGE, @@ -1267,7 +1268,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { } @Test - public void testTaskFragmentInPip_setAdjacentTaskFragment() { + public void testTaskFragmentInPip_setAdjacentTaskFragments() { setupTaskFragmentInPip(); spyOn(mWindowOrganizerController); @@ -1279,7 +1280,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { verify(mWindowOrganizerController).sendTaskFragmentOperationFailure(eq(mIOrganizer), eq(mErrorToken), eq(mTaskFragment), eq(OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS), any(IllegalArgumentException.class)); - verify(mTaskFragment, never()).setAdjacentTaskFragment(any()); + verify(mTaskFragment, never()).setAdjacentTaskFragments(any()); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java index cc2a76dcc9f2..7c1d7fec819b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java @@ -67,7 +67,6 @@ import android.content.res.Configuration; import android.graphics.Color; import android.graphics.Rect; import android.os.Binder; -import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; import android.view.SurfaceControl; import android.view.View; @@ -363,7 +362,7 @@ public class TaskFragmentTest extends WindowTestsBase { doReturn(true).when(primaryActivity).supportsPictureInPicture(); doReturn(false).when(secondaryActivity).supportsPictureInPicture(); - primaryTf.setAdjacentTaskFragment(secondaryTf); + primaryTf.setAdjacentTaskFragments(new TaskFragment.AdjacentSet(primaryTf, secondaryTf)); primaryActivity.setState(RESUMED, "test"); secondaryActivity.setState(RESUMED, "test"); @@ -390,7 +389,8 @@ public class TaskFragmentTest extends WindowTestsBase { task.setWindowingMode(WINDOWING_MODE_FULLSCREEN); taskFragment0.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); taskFragment0.setBounds(taskFragmentBounds); - taskFragment0.setAdjacentTaskFragment(taskFragment1); + taskFragment0.setAdjacentTaskFragments( + new TaskFragment.AdjacentSet(taskFragment0, taskFragment1)); taskFragment0.setCompanionTaskFragment(taskFragment1); taskFragment0.setAnimationParams(new TaskFragmentAnimationParams.Builder() .setAnimationBackgroundColor(Color.GREEN) @@ -779,7 +779,7 @@ public class TaskFragmentTest extends WindowTestsBase { .setOrganizer(mOrganizer) .setFragmentToken(new Binder()) .build(); - tf0.setAdjacentTaskFragment(tf1); + tf0.setAdjacentTaskFragments(new TaskFragment.AdjacentSet(tf0, tf1)); tf0.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); tf1.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); task.setBounds(0, 0, 1200, 1000); @@ -834,7 +834,7 @@ public class TaskFragmentTest extends WindowTestsBase { final Task task = createTask(mDisplayContent); final TaskFragment tf0 = createTaskFragmentWithActivity(task); final TaskFragment tf1 = createTaskFragmentWithActivity(task); - tf0.setAdjacentTaskFragment(tf1); + tf0.setAdjacentTaskFragments(new TaskFragment.AdjacentSet(tf0, tf1)); tf0.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); tf1.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); task.setBounds(0, 0, 1200, 1000); @@ -982,7 +982,8 @@ public class TaskFragmentTest extends WindowTestsBase { .setOrganizer(mOrganizer) .setFragmentToken(new Binder()) .build(); - taskFragmentLeft.setAdjacentTaskFragment(taskFragmentRight); + taskFragmentLeft.setAdjacentTaskFragments( + new TaskFragment.AdjacentSet(taskFragmentLeft, taskFragmentRight)); taskFragmentLeft.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); taskFragmentRight.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); task.setBounds(0, 0, 1200, 1000); @@ -1051,8 +1052,8 @@ public class TaskFragmentTest extends WindowTestsBase { .setParentTask(task) .createActivityCount(1) .build(); - taskFragmentRight.setAdjacentTaskFragment(taskFragmentLeft); - taskFragmentLeft.setAdjacentTaskFragment(taskFragmentRight); + taskFragmentRight.setAdjacentTaskFragments( + new TaskFragment.AdjacentSet(taskFragmentLeft, taskFragmentRight)); final ActivityRecord appLeftTop = taskFragmentLeft.getTopMostActivity(); final ActivityRecord appRightTop = taskFragmentRight.getTopMostActivity(); @@ -1103,7 +1104,6 @@ public class TaskFragmentTest extends WindowTestsBase { Math.min(outConfig.screenWidthDp, outConfig.screenHeightDp)); } - @EnableFlags(Flags.FLAG_ALLOW_MULTIPLE_ADJACENT_TASK_FRAGMENTS) @Test public void testAdjacentSetForTaskFragments() { final Task task = createTask(mDisplayContent); @@ -1119,7 +1119,6 @@ public class TaskFragmentTest extends WindowTestsBase { () -> new TaskFragment.AdjacentSet(tf0, tf1, tf2)); } - @EnableFlags(Flags.FLAG_ALLOW_MULTIPLE_ADJACENT_TASK_FRAGMENTS) @Test public void testSetAdjacentTaskFragments() { final Task task0 = createTask(mDisplayContent); @@ -1148,7 +1147,6 @@ public class TaskFragmentTest extends WindowTestsBase { assertFalse(task2.hasAdjacentTaskFragment()); } - @EnableFlags(Flags.FLAG_ALLOW_MULTIPLE_ADJACENT_TASK_FRAGMENTS) @Test public void testClearAdjacentTaskFragments() { final Task task0 = createTask(mDisplayContent); @@ -1167,7 +1165,6 @@ public class TaskFragmentTest extends WindowTestsBase { assertFalse(task2.hasAdjacentTaskFragment()); } - @EnableFlags(Flags.FLAG_ALLOW_MULTIPLE_ADJACENT_TASK_FRAGMENTS) @Test public void testRemoveFromAdjacentTaskFragments() { final Task task0 = createTask(mDisplayContent); @@ -1190,7 +1187,6 @@ public class TaskFragmentTest extends WindowTestsBase { assertFalse(task1.isAdjacentTo(task1)); } - @EnableFlags(Flags.FLAG_ALLOW_MULTIPLE_ADJACENT_TASK_FRAGMENTS) @Test public void testRemoveFromAdjacentTaskFragmentsWhenRemove() { final Task task0 = createTask(mDisplayContent); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotLowResDisabledTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotLowResDisabledTest.java index 51ea498811fc..f22ecb5eb3f9 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotLowResDisabledTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotLowResDisabledTest.java @@ -68,11 +68,8 @@ public class TaskSnapshotLowResDisabledTest extends TaskSnapshotPersisterTestBas public void testPersistAndLoadSnapshot() { mPersister.persistSnapshot(1, mTestUserId, createSnapshot()); mSnapshotPersistQueue.waitForQueueEmpty(); - final File[] files = new File[]{ - new File(FILES_DIR.getPath() + "/snapshots/1.proto"), - new File(FILES_DIR.getPath() + "/snapshots/1.jpg")}; - final File[] nonExistsFiles = new File[]{ - new File(FILES_DIR.getPath() + "/snapshots/1_reduced.jpg")}; + final File[] files = convertFilePath("1.proto", "1.jpg"); + final File[] nonExistsFiles = convertFilePath("1_reduced.proto"); assertTrueForFiles(files, File::exists, " must exist"); assertTrueForFiles(nonExistsFiles, file -> !file.exists(), " must not exist"); final TaskSnapshot snapshot = mLoader.loadTask(1, mTestUserId, false /* isLowResolution */); @@ -92,14 +89,9 @@ public class TaskSnapshotLowResDisabledTest extends TaskSnapshotPersisterTestBas taskIds.add(1); mPersister.removeObsoleteFiles(taskIds, new int[]{mTestUserId}); mSnapshotPersistQueue.waitForQueueEmpty(); - final File[] existsFiles = new File[]{ - new File(FILES_DIR.getPath() + "/snapshots/1.proto"), - new File(FILES_DIR.getPath() + "/snapshots/1.jpg")}; - final File[] nonExistsFiles = new File[]{ - new File(FILES_DIR.getPath() + "/snapshots/1_reduced.jpg"), - new File(FILES_DIR.getPath() + "/snapshots/2.proto"), - new File(FILES_DIR.getPath() + "/snapshots/2.jpg"), - new File(FILES_DIR.getPath() + "/snapshots/2_reduced.jpg")}; + final File[] existsFiles = convertFilePath("1.proto", "1.jpg"); + final File[] nonExistsFiles = convertFilePath("1_reduced.proto", "2.proto", "2.jpg", + "2_reduced.jpg"); assertTrueForFiles(existsFiles, File::exists, " must exist"); assertTrueForFiles(nonExistsFiles, file -> !file.exists(), " must not exist"); } @@ -112,14 +104,8 @@ public class TaskSnapshotLowResDisabledTest extends TaskSnapshotPersisterTestBas mPersister.removeObsoleteFiles(taskIds, new int[]{mTestUserId}); mPersister.persistSnapshot(2, mTestUserId, createSnapshot()); mSnapshotPersistQueue.waitForQueueEmpty(); - final File[] existsFiles = new File[]{ - new File(FILES_DIR.getPath() + "/snapshots/1.proto"), - new File(FILES_DIR.getPath() + "/snapshots/1.jpg"), - new File(FILES_DIR.getPath() + "/snapshots/2.proto"), - new File(FILES_DIR.getPath() + "/snapshots/2.jpg")}; - final File[] nonExistsFiles = new File[]{ - new File(FILES_DIR.getPath() + "/snapshots/1_reduced.jpg"), - new File(FILES_DIR.getPath() + "/snapshots/2_reduced.jpg")}; + final File[] existsFiles = convertFilePath("1.proto", "1.jpg", "2.proto", "2.jpg"); + final File[] nonExistsFiles = convertFilePath("1_reduced.jpg", "2_reduced.jpg"); assertTrueForFiles(existsFiles, File::exists, " must exist"); assertTrueForFiles(nonExistsFiles, file -> !file.exists(), " must not exist"); } diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java index 4b54e4464ca7..af06c14516a1 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java @@ -75,9 +75,7 @@ public class TaskSnapshotPersisterLoaderTest extends TaskSnapshotPersisterTestBa public void testPersistAndLoadSnapshot() { mPersister.persistSnapshot(1, mTestUserId, createSnapshot()); mSnapshotPersistQueue.waitForQueueEmpty(); - final File[] files = new File[]{new File(FILES_DIR.getPath() + "/snapshots/1.proto"), - new File(FILES_DIR.getPath() + "/snapshots/1.jpg"), - new File(FILES_DIR.getPath() + "/snapshots/1_reduced.jpg")}; + final File[] files = convertFilePath("1.proto", "1.jpg", "1_reduced.jpg"); assertTrueForFiles(files, File::exists, " must exist"); final TaskSnapshot snapshot = mLoader.loadTask(1, mTestUserId, false /* isLowResolution */); assertNotNull(snapshot); @@ -140,13 +138,8 @@ public class TaskSnapshotPersisterLoaderTest extends TaskSnapshotPersisterTestBa mSnapshotPersistQueue.waitForQueueEmpty(); // Make sure 1,2 were purged but removeObsoleteFiles wasn't. - final File[] existsFiles = new File[]{ - new File(FILES_DIR.getPath() + "/snapshots/3.proto"), - new File(FILES_DIR.getPath() + "/snapshots/4.proto")}; - final File[] nonExistsFiles = new File[]{ - new File(FILES_DIR.getPath() + "/snapshots/100.proto"), - new File(FILES_DIR.getPath() + "/snapshots/1.proto"), - new File(FILES_DIR.getPath() + "/snapshots/1.proto")}; + final File[] existsFiles = convertFilePath("3.proto", "4.proto"); + final File[] nonExistsFiles = convertFilePath("100.proto", "1.proto", "2.proto"); assertTrueForFiles(existsFiles, File::exists, " must exist"); assertTrueForFiles(nonExistsFiles, file -> !file.exists(), " must not exist"); } @@ -427,14 +420,8 @@ public class TaskSnapshotPersisterLoaderTest extends TaskSnapshotPersisterTestBa taskIds.add(1); mPersister.removeObsoleteFiles(taskIds, new int[]{mTestUserId}); mSnapshotPersistQueue.waitForQueueEmpty(); - final File[] existsFiles = new File[]{ - new File(FILES_DIR.getPath() + "/snapshots/1.proto"), - new File(FILES_DIR.getPath() + "/snapshots/1.jpg"), - new File(FILES_DIR.getPath() + "/snapshots/1_reduced.jpg")}; - final File[] nonExistsFiles = new File[]{ - new File(FILES_DIR.getPath() + "/snapshots/2.proto"), - new File(FILES_DIR.getPath() + "/snapshots/2.jpg"), - new File(FILES_DIR.getPath() + "/snapshots/2_reduced.jpg")}; + final File[] existsFiles = convertFilePath("1.proto", "1.jpg", "1_reduced.jpg"); + final File[] nonExistsFiles = convertFilePath("2.proto", "2.jpg", "2_reduced.jpg"); assertTrueForFiles(existsFiles, File::exists, " must exist"); assertTrueForFiles(nonExistsFiles, file -> !file.exists(), " must not exist"); } @@ -447,13 +434,8 @@ public class TaskSnapshotPersisterLoaderTest extends TaskSnapshotPersisterTestBa mPersister.removeObsoleteFiles(taskIds, new int[]{mTestUserId}); mPersister.persistSnapshot(2, mTestUserId, createSnapshot()); mSnapshotPersistQueue.waitForQueueEmpty(); - final File[] existsFiles = new File[]{ - new File(FILES_DIR.getPath() + "/snapshots/1.proto"), - new File(FILES_DIR.getPath() + "/snapshots/1.jpg"), - new File(FILES_DIR.getPath() + "/snapshots/1_reduced.jpg"), - new File(FILES_DIR.getPath() + "/snapshots/2.proto"), - new File(FILES_DIR.getPath() + "/snapshots/2.jpg"), - new File(FILES_DIR.getPath() + "/snapshots/2_reduced.jpg")}; + final File[] existsFiles = convertFilePath("1.proto", "1.jpg", "1_reduced.jpg", "2.proto", + "2.jpg", "2_reduced.jpg"); assertTrueForFiles(existsFiles, File::exists, " must exist"); } diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java index 1e16c97de647..b2c195e8ebaa 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java @@ -28,6 +28,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; +import android.annotation.NonNull; import android.content.ComponentName; import android.content.ContextWrapper; import android.content.res.Resources; @@ -46,6 +47,7 @@ import android.window.TaskSnapshot; import com.android.server.LocalServices; import com.android.server.pm.UserManagerInternal; import com.android.server.wm.BaseAppSnapshotPersister.PersistInfoProvider; +import com.android.window.flags.Flags; import org.junit.After; import org.junit.AfterClass; @@ -129,12 +131,33 @@ class TaskSnapshotPersisterTestBase extends WindowTestsBase { return; } for (File file : files) { - if (!file.isDirectory()) { - file.delete(); + if (file.isDirectory()) { + final File[] subFiles = file.listFiles(); + if (subFiles == null) { + continue; + } + for (File subFile : subFiles) { + subFile.delete(); + } } + file.delete(); } } + File[] convertFilePath(@NonNull String... fileNames) { + final File[] files = new File[fileNames.length]; + final String path; + if (Flags.scrambleSnapshotFileName()) { + path = mPersister.mPersistInfoProvider.getDirectory(mTestUserId).getPath(); + } else { + path = FILES_DIR.getPath() + "/snapshots/"; + } + for (int i = 0; i < fileNames.length; i++) { + files[i] = new File(path + fileNames[i]); + } + return files; + } + TaskSnapshot createSnapshot() { return new TaskSnapshotBuilder().setTopActivityComponent(getUniqueComponentName()).build(); } diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java index 724d7e7c111c..044aacc4b988 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java @@ -1750,8 +1750,7 @@ public class TaskTests extends WindowTestsBase { primary.mVisibleRequested = true; secondary.mVisibleRequested = true; - primary.setAdjacentTaskFragment(secondary); - secondary.setAdjacentTaskFragment(primary); + primary.setAdjacentTaskFragments(new TaskFragment.AdjacentSet(primary, secondary)); primary.setEmbeddedDimArea(EMBEDDED_DIM_AREA_PARENT_TASK); doReturn(true).when(primary).shouldBoostDimmer(); task.assignChildLayers(t); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java index 7030d986494f..ae6144713a1d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java @@ -929,7 +929,6 @@ public class WindowOrganizerTests extends WindowTestsBase { assertEquals(dc.getDefaultTaskDisplayArea().mLaunchAdjacentFlagRootTask, null); } - @EnableFlags(Flags.FLAG_ALLOW_MULTIPLE_ADJACENT_TASK_FRAGMENTS) @Test public void testSetAdjacentLaunchRootSet() { final DisplayContent dc = mWm.mRoot.getDisplayContent(Display.DEFAULT_DISPLAY); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index 57ab13ffee89..471b065aebb4 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -122,7 +122,6 @@ import android.window.WindowContainerTransaction; import com.android.internal.policy.AttributeCache; import com.android.internal.util.ArrayUtils; import com.android.internal.util.test.FakeSettingsProvider; -import com.android.server.wallpaper.WallpaperCropper.WallpaperCropUtils; import com.android.server.wm.DisplayWindowSettings.SettingsProvider.SettingsEntry; import org.junit.After; @@ -289,18 +288,6 @@ public class WindowTestsBase extends SystemServiceTestsBase { mAtm.mWindowManager.mAppCompatConfiguration .setIsDisplayAspectRatioEnabledForFixedOrientationLetterbox(false); - // Setup WallpaperController crop utils with a simple center-align strategy - WallpaperCropUtils cropUtils = (displaySize, bitmapSize, suggestedCrops, rtl) -> { - Rect crop = new Rect(0, 0, displaySize.x, displaySize.y); - crop.scale(Math.min( - ((float) bitmapSize.x) / displaySize.x, - ((float) bitmapSize.y) / displaySize.y)); - crop.offset((bitmapSize.x - crop.width()) / 2, (bitmapSize.y - crop.height()) / 2); - return crop; - }; - mDisplayContent.mWallpaperController.setWallpaperCropUtils(cropUtils); - mDefaultDisplay.mWallpaperController.setWallpaperCropUtils(cropUtils); - checkDeviceSpecificOverridesNotApplied(); } @@ -1890,7 +1877,7 @@ public class WindowTestsBase extends SystemServiceTestsBase { mSecondary = mService.mTaskOrganizerController.createRootTask( display, WINDOWING_MODE_MULTI_WINDOW, null); - mPrimary.setAdjacentTaskFragment(mSecondary); + mPrimary.setAdjacentTaskFragments(new TaskFragment.AdjacentSet(mPrimary, mSecondary)); display.getDefaultTaskDisplayArea().setLaunchAdjacentFlagRootTask(mSecondary); final Rect primaryBounds = new Rect(); diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java index 89b98509de34..a727df7a7efb 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java @@ -43,6 +43,8 @@ import android.media.AudioManagerInternal; import android.media.permission.Identity; import android.os.Binder; import android.os.Bundle; +import android.os.Handler; +import android.os.HandlerThread; import android.os.IBinder; import android.os.IRemoteCallback; import android.os.ParcelFileDescriptor; @@ -837,6 +839,13 @@ final class HotwordDetectionConnection { private final int mBindingFlags; private final int mInstanceNumber; + private static final HandlerThread mHandler; + + static { + mHandler = new HandlerThread("Sandbox detection connector"); + mHandler.start(); + } + private boolean mRespectServiceConnectionStatusChanged = true; private boolean mIsBound = false; private boolean mIsLoggedFirstConnect = false; @@ -883,6 +892,11 @@ final class HotwordDetectionConnection { } } + @Override // from ServiceConnector.Impl + protected Handler getJobHandler() { + return mHandler.getThreadHandler(); + } + @Override protected long getAutoDisconnectTimeoutMs() { return -1; @@ -1151,14 +1165,12 @@ final class HotwordDetectionConnection { } private void updateServiceIdentity(ServiceConnection connection) { - connection.run(service -> service.ping(new IRemoteCallback.Stub() { + connection.run(service -> service.ping(new ISandboxedDetectionService.IPingMe.Stub() { @Override - public void sendResult(Bundle bundle) throws RemoteException { + public void onPing() throws RemoteException { // TODO: Exit if the service has been unbound already (though there's a very low // chance this happens). - if (DEBUG) { - Slog.d(TAG, "updating hotword UID " + Binder.getCallingUid()); - } + Slog.d(TAG, "updating hotword UID " + Binder.getCallingUid()); // TODO: Have the provider point to the current state stored in // VoiceInteractionManagerServiceImpl. final int uid = Binder.getCallingUid(); diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java index 29d394201f39..ebe00782319a 100644 --- a/telecomm/java/android/telecom/Connection.java +++ b/telecomm/java/android/telecom/Connection.java @@ -1237,6 +1237,10 @@ public abstract class Connection extends Conferenceable { builder.append(isLong ? " PROPERTY_IS_DOWNGRADED_CONFERENCE" : " dngrd_conf"); } + if ((properties & PROPERTY_CROSS_SIM) == PROPERTY_CROSS_SIM) { + builder.append(isLong ? " PROPERTY_CROSS_SIM" : " xsim"); + } + builder.append("]"); return builder.toString(); } diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index fbba999bfb36..14d567d141cb 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -3371,14 +3371,13 @@ public class TelephonyManager { return telephony.getDataNetworkTypeForSubscriber(subId, getOpPackageName(), getAttributionTag()); } else { - // This can happen when the ITelephony interface is not up yet. + Log.e(TAG, "getDataNetworkType: ITelephony interface is not up yet"); return NETWORK_TYPE_UNKNOWN; } - } catch(RemoteException ex) { - // This shouldn't happen in the normal case - return NETWORK_TYPE_UNKNOWN; - } catch (NullPointerException ex) { - // This could happen before phone restarts due to crashing + } catch (RemoteException // Shouldn't happen in the normal case + | NullPointerException ex // Could happen before phone restarts due to crashing + ) { + Log.e(TAG, "getDataNetworkType: " + ex.getMessage()); return NETWORK_TYPE_UNKNOWN; } } diff --git a/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml b/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml index 685ae9a5fef2..c5e7188e6efe 100644 --- a/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml +++ b/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml @@ -47,6 +47,8 @@ <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/> <!-- Disable AOD --> <option name="run-command" value="settings put secure doze_always_on 0"/> + <!-- Disable explore hub mode --> + <option name="run-command" value="settings put secure glanceable_hub_enabled 0"/> <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/> <option name="run-command" value="settings put system show_touches 1"/> <option name="run-command" value="settings put system pointer_location 1"/> diff --git a/tests/FlickerTests/AppClose/AndroidTestTemplate.xml b/tests/FlickerTests/AppClose/AndroidTestTemplate.xml index 5f92d7fe830b..22bd458e2751 100644 --- a/tests/FlickerTests/AppClose/AndroidTestTemplate.xml +++ b/tests/FlickerTests/AppClose/AndroidTestTemplate.xml @@ -47,6 +47,8 @@ <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/> <!-- Disable AOD --> <option name="run-command" value="settings put secure doze_always_on 0"/> + <!-- Disable explore hub mode --> + <option name="run-command" value="settings put secure glanceable_hub_enabled 0"/> <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/> <option name="run-command" value="settings put system show_touches 1"/> <option name="run-command" value="settings put system pointer_location 1"/> diff --git a/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml b/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml index 1b90e99a8ba2..541ce26d1435 100644 --- a/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml +++ b/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml @@ -47,6 +47,8 @@ <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/> <!-- Disable AOD --> <option name="run-command" value="settings put secure doze_always_on 0"/> + <!-- Disable explore hub mode --> + <option name="run-command" value="settings put secure glanceable_hub_enabled 0"/> <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/> <option name="run-command" value="settings put system show_touches 1"/> <option name="run-command" value="settings put system pointer_location 1"/> diff --git a/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml b/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml index ffdbb02984a7..d2e02193f0fb 100644 --- a/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml +++ b/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml @@ -47,6 +47,8 @@ <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/> <!-- Disable AOD --> <option name="run-command" value="settings put secure doze_always_on 0"/> + <!-- Disable explore hub mode --> + <option name="run-command" value="settings put secure glanceable_hub_enabled 0"/> <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/> <option name="run-command" value="settings put system show_touches 1"/> <option name="run-command" value="settings put system pointer_location 1"/> diff --git a/tests/FlickerTests/IME/AndroidTestTemplate.xml b/tests/FlickerTests/IME/AndroidTestTemplate.xml index ac704e5e7c39..e112c82f0661 100644 --- a/tests/FlickerTests/IME/AndroidTestTemplate.xml +++ b/tests/FlickerTests/IME/AndroidTestTemplate.xml @@ -49,6 +49,8 @@ <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/> <!-- Disable AOD --> <option name="run-command" value="settings put secure doze_always_on 0"/> + <!-- Disable explore hub mode --> + <option name="run-command" value="settings put secure glanceable_hub_enabled 0"/> <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/> <option name="run-command" value="settings put system show_touches 1"/> <option name="run-command" value="settings put system pointer_location 1"/> diff --git a/tests/FlickerTests/Notification/AndroidTestTemplate.xml b/tests/FlickerTests/Notification/AndroidTestTemplate.xml index e2ac5a9579ae..e5700c03cf77 100644 --- a/tests/FlickerTests/Notification/AndroidTestTemplate.xml +++ b/tests/FlickerTests/Notification/AndroidTestTemplate.xml @@ -47,6 +47,8 @@ <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/> <!-- Disable AOD --> <option name="run-command" value="settings put secure doze_always_on 0"/> + <!-- Disable explore hub mode --> + <option name="run-command" value="settings put secure glanceable_hub_enabled 0"/> <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/> <option name="run-command" value="settings put system show_touches 1"/> <option name="run-command" value="settings put system pointer_location 1"/> diff --git a/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml b/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml index 1a4feb6e9eca..4c41a4c01180 100644 --- a/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml +++ b/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml @@ -47,6 +47,8 @@ <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/> <!-- Disable AOD --> <option name="run-command" value="settings put secure doze_always_on 0"/> + <!-- Disable explore hub mode --> + <option name="run-command" value="settings put secure glanceable_hub_enabled 0"/> <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/> <option name="run-command" value="settings put system show_touches 1"/> <option name="run-command" value="settings put system pointer_location 1"/> diff --git a/tests/FlickerTests/Rotation/AndroidTestTemplate.xml b/tests/FlickerTests/Rotation/AndroidTestTemplate.xml index 1b2007deae27..27fc249e36b9 100644 --- a/tests/FlickerTests/Rotation/AndroidTestTemplate.xml +++ b/tests/FlickerTests/Rotation/AndroidTestTemplate.xml @@ -47,6 +47,8 @@ <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/> <!-- Disable AOD --> <option name="run-command" value="settings put secure doze_always_on 0"/> + <!-- Disable explore hub mode --> + <option name="run-command" value="settings put secure glanceable_hub_enabled 0"/> <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/> <option name="run-command" value="settings put system show_touches 1"/> <option name="run-command" value="settings put system pointer_location 1"/> diff --git a/tests/Input/src/android/hardware/input/KeyGestureEventHandlerTest.kt b/tests/Input/src/android/hardware/input/KeyGestureEventHandlerTest.kt index e99c81493394..794fd0255726 100644 --- a/tests/Input/src/android/hardware/input/KeyGestureEventHandlerTest.kt +++ b/tests/Input/src/android/hardware/input/KeyGestureEventHandlerTest.kt @@ -214,9 +214,5 @@ class KeyGestureEventHandlerTest { ): Boolean { return handler(event, focusedToken) } - - override fun isKeyGestureSupported(gestureType: Int): Boolean { - return true - } } } diff --git a/tests/Input/src/com/android/server/input/BatteryControllerTests.kt b/tests/Input/src/com/android/server/input/BatteryControllerTests.kt index 044f11d6904c..890c346ea015 100644 --- a/tests/Input/src/com/android/server/input/BatteryControllerTests.kt +++ b/tests/Input/src/com/android/server/input/BatteryControllerTests.kt @@ -184,6 +184,8 @@ class BatteryControllerTests { @get:Rule val rule = MockitoJUnit.rule()!! @get:Rule + val context = TestableContext(ApplicationProvider.getApplicationContext()) + @get:Rule val inputManagerRule = MockInputManagerRule() @Mock @@ -194,7 +196,6 @@ class BatteryControllerTests { private lateinit var bluetoothBatteryManager: BluetoothBatteryManager private lateinit var batteryController: BatteryController - private lateinit var context: TestableContext private lateinit var testLooper: TestLooper private lateinit var devicesChangedListener: IInputDevicesChangedListener private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession @@ -202,7 +203,6 @@ class BatteryControllerTests { @Before fun setup() { - context = TestableContext(ApplicationProvider.getApplicationContext()) testLooper = TestLooper() val inputManager = InputManager(context) context.addMockSystemService(InputManager::class.java, inputManager) diff --git a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt index 2799f6c79215..4f1fb6487b19 100644 --- a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt +++ b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt @@ -32,6 +32,7 @@ import android.hardware.input.InputGestureData import android.hardware.input.InputManager import android.hardware.input.InputManagerGlobal import android.hardware.input.KeyGestureEvent +import android.os.Handler import android.os.IBinder import android.os.Process import android.os.SystemClock @@ -48,9 +49,11 @@ import android.view.WindowManagerPolicyConstants.FLAG_INTERACTIVE import androidx.test.core.app.ApplicationProvider import com.android.dx.mockito.inline.extended.ExtendedMockito import com.android.internal.R +import com.android.internal.accessibility.AccessibilityShortcutController import com.android.internal.annotations.Keep import com.android.internal.util.FrameworkStatsLog import com.android.modules.utils.testing.ExtendedMockitoRule +import com.android.server.input.InputManagerService.WindowManagerCallbacks import java.io.File import java.io.FileOutputStream import java.io.InputStream @@ -67,6 +70,8 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito +import org.mockito.kotlin.never +import org.mockito.kotlin.times /** * Tests for {@link KeyGestureController}. @@ -102,6 +107,7 @@ class KeyGestureControllerTests { const val SETTINGS_KEY_BEHAVIOR_SETTINGS_ACTIVITY = 0 const val SETTINGS_KEY_BEHAVIOR_NOTIFICATION_PANEL = 1 const val SETTINGS_KEY_BEHAVIOR_NOTHING = 2 + const val TEST_PID = 10 } @JvmField @@ -116,11 +122,10 @@ class KeyGestureControllerTests { @Rule val rule = SetFlagsRule() - @Mock - private lateinit var iInputManager: IInputManager - - @Mock - private lateinit var packageManager: PackageManager + @Mock private lateinit var iInputManager: IInputManager + @Mock private lateinit var packageManager: PackageManager + @Mock private lateinit var wmCallbacks: WindowManagerCallbacks + @Mock private lateinit var accessibilityShortcutController: AccessibilityShortcutController private var currentPid = 0 private lateinit var context: Context @@ -207,8 +212,34 @@ class KeyGestureControllerTests { private fun setupKeyGestureController() { keyGestureController = - KeyGestureController(context, testLooper.looper, testLooper.looper, inputDataStore) - Mockito.`when`(iInputManager.getAppLaunchBookmarks()) + KeyGestureController( + context, + testLooper.looper, + testLooper.looper, + inputDataStore, + object : KeyGestureController.Injector() { + override fun getAccessibilityShortcutController( + context: Context?, + handler: Handler? + ): AccessibilityShortcutController { + return accessibilityShortcutController + } + }) + Mockito.`when`(iInputManager.registerKeyGestureHandler(Mockito.any())) + .thenAnswer { + val args = it.arguments + if (args[0] != null) { + keyGestureController.registerKeyGestureHandler( + args[0] as IKeyGestureHandler, + TEST_PID + ) + } + } + keyGestureController.setWindowManagerCallbacks(wmCallbacks) + Mockito.`when`(wmCallbacks.isKeyguardLocked(Mockito.anyInt())).thenReturn(false) + Mockito.`when`(accessibilityShortcutController + .isAccessibilityShortcutAvailable(Mockito.anyBoolean())).thenReturn(true) + Mockito.`when`(iInputManager.appLaunchBookmarks) .thenReturn(keyGestureController.appLaunchBookmarks) keyGestureController.systemRunning() testLooper.dispatchAll() @@ -1270,9 +1301,9 @@ class KeyGestureControllerTests { ) ), TestData( - "BACK + DPAD_DOWN -> TV Accessibility Chord", + "BACK + DPAD_DOWN -> Accessibility Chord(for TV)", intArrayOf(KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_DPAD_DOWN), - KeyGestureEvent.KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD, + KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD, intArrayOf(KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_DPAD_DOWN), 0, intArrayOf( @@ -1622,6 +1653,52 @@ class KeyGestureControllerTests { ) } + @Test + fun testAccessibilityShortcutChordPressed() { + setupKeyGestureController() + + sendKeys( + intArrayOf(KeyEvent.KEYCODE_VOLUME_UP, KeyEvent.KEYCODE_VOLUME_DOWN), + // Assuming this value is always greater than the accessibility shortcut timeout, which + // currently defaults to 3000ms + timeDelayMs = 10000 + ) + Mockito.verify(accessibilityShortcutController, times(1)).performAccessibilityShortcut() + } + + @Test + fun testAccessibilityTvShortcutChordPressed() { + setupKeyGestureController() + + sendKeys( + intArrayOf(KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_DPAD_DOWN), + timeDelayMs = 10000 + ) + Mockito.verify(accessibilityShortcutController, times(1)).performAccessibilityShortcut() + } + + @Test + fun testAccessibilityShortcutChordPressedForLessThanTimeout() { + setupKeyGestureController() + + sendKeys( + intArrayOf(KeyEvent.KEYCODE_VOLUME_UP, KeyEvent.KEYCODE_VOLUME_DOWN), + timeDelayMs = 0 + ) + Mockito.verify(accessibilityShortcutController, never()).performAccessibilityShortcut() + } + + @Test + fun testAccessibilityTvShortcutChordPressedForLessThanTimeout() { + setupKeyGestureController() + + sendKeys( + intArrayOf(KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_DPAD_DOWN), + timeDelayMs = 0 + ) + Mockito.verify(accessibilityShortcutController, never()).performAccessibilityShortcut() + } + private fun testKeyGestureInternal(test: TestData) { val handledEvents = mutableListOf<KeyGestureEvent>() val handler = KeyGestureHandler { event, _ -> @@ -1683,7 +1760,11 @@ class KeyGestureControllerTests { assertEquals("Test: $testName should not produce Key gesture", 0, handledEvents.size) } - private fun sendKeys(testKeys: IntArray, assertNotSentToApps: Boolean = false) { + private fun sendKeys( + testKeys: IntArray, + assertNotSentToApps: Boolean = false, + timeDelayMs: Long = 0 + ) { var metaState = 0 val now = SystemClock.uptimeMillis() for (key in testKeys) { @@ -1699,6 +1780,11 @@ class KeyGestureControllerTests { testLooper.dispatchAll() } + if (timeDelayMs > 0) { + testLooper.moveTimeForward(timeDelayMs) + testLooper.dispatchAll() + } + for (key in testKeys.reversed()) { val upEvent = KeyEvent( now, now, KeyEvent.ACTION_UP, key, 0 /*repeat*/, metaState, @@ -1742,9 +1828,5 @@ class KeyGestureControllerTests { override fun handleKeyGesture(event: AidlKeyGestureEvent, token: IBinder?): Boolean { return handler(event, token) } - - override fun isKeyGestureSupported(gestureType: Int): Boolean { - return true - } } } diff --git a/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewControllerTests.java b/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewControllerTests.java index 5875520cd259..20528f23cc8c 100644 --- a/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewControllerTests.java +++ b/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewControllerTests.java @@ -23,7 +23,6 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import android.content.Context; import android.graphics.Rect; import android.hardware.input.InputManager; import android.testing.AndroidTestingRunner; @@ -60,9 +59,12 @@ public class TouchpadDebugViewControllerTests { private static final String TAG = "TouchpadDebugViewController"; @Rule + public final TestableContext mTestableContext = + new TestableContext(InstrumentationRegistry.getInstrumentation().getContext()); + + @Rule public final MockitoRule mockito = MockitoJUnit.rule(); - private Context mContext; private TouchpadDebugViewController mTouchpadDebugViewController; @Mock private InputManager mInputManagerMock; @@ -74,8 +76,6 @@ public class TouchpadDebugViewControllerTests { @Before public void setup() throws Exception { - mContext = InstrumentationRegistry.getInstrumentation().getContext(); - TestableContext mTestableContext = new TestableContext(mContext); mTestableContext.addMockSystemService(WindowManager.class, mWindowManagerMock); Rect bounds = new Rect(0, 0, 2560, 1600); diff --git a/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java b/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java index 60fa52f85e34..1c366a134300 100644 --- a/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java +++ b/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java @@ -26,7 +26,6 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import android.content.Context; import android.graphics.Color; import android.graphics.Rect; import android.graphics.drawable.ColorDrawable; @@ -51,6 +50,7 @@ import com.android.server.input.TouchpadHardwareProperties; import com.android.server.input.TouchpadHardwareState; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -70,6 +70,10 @@ public class TouchpadDebugViewTest { private TouchpadDebugView mTouchpadDebugView; private WindowManager.LayoutParams mWindowLayoutParams; + @Rule + public final TestableContext mTestableContext = + new TestableContext(InstrumentationRegistry.getInstrumentation().getContext()); + @Mock WindowManager mWindowManager; @Mock @@ -77,14 +81,10 @@ public class TouchpadDebugViewTest { Rect mWindowBounds; WindowMetrics mWindowMetrics; - TestableContext mTestableContext; @Before public void setUp() { MockitoAnnotations.initMocks(this); - Context context = InstrumentationRegistry.getInstrumentation().getContext(); - mTestableContext = new TestableContext(context); - mTestableContext.addMockSystemService(WindowManager.class, mWindowManager); mTestableContext.addMockSystemService(InputManager.class, mInputManager); diff --git a/tests/utils/testutils/java/android/os/test/TestLooper.java b/tests/utils/testutils/java/android/os/test/TestLooper.java index 4d379e45a81a..bb54a26036db 100644 --- a/tests/utils/testutils/java/android/os/test/TestLooper.java +++ b/tests/utils/testutils/java/android/os/test/TestLooper.java @@ -68,16 +68,7 @@ public class TestLooper { * Baklava introduces new {@link TestLooperManager} APIs that we can use instead of reflection. */ private static boolean isAtLeastBaklava() { - Method[] methods = TestLooperManager.class.getMethods(); - for (Method method : methods) { - if (method.getName().equals("peekWhen")) { - return true; - } - } - return false; - // TODO(shayba): delete the above, uncomment the below. - // SDK_INT has not yet ramped to Baklava in all 25Q2 builds. - // return Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA; + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA; } static { diff --git a/tools/aapt2/Resource.h b/tools/aapt2/Resource.h index 0d261abd728d..e51477c668dd 100644 --- a/tools/aapt2/Resource.h +++ b/tools/aapt2/Resource.h @@ -249,6 +249,8 @@ struct ResourceFile { // Flag std::optional<FeatureFlagAttribute> flag; + + bool uses_readwrite_feature_flags = false; }; /** diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp index 5435cba290fc..db7dddc49a99 100644 --- a/tools/aapt2/ResourceTable.cpp +++ b/tools/aapt2/ResourceTable.cpp @@ -664,6 +664,7 @@ bool ResourceTable::AddResource(NewResource&& res, android::IDiagnostics* diag) if (!config_value->value) { // Resource does not exist, add it now. config_value->value = std::move(res.value); + config_value->uses_readwrite_feature_flags = res.uses_readwrite_feature_flags; } else { // When validation is enabled, ensure that a resource cannot have multiple values defined for // the same configuration unless protected by flags. @@ -681,12 +682,14 @@ bool ResourceTable::AddResource(NewResource&& res, android::IDiagnostics* diag) ConfigKey{&res.config, res.product}, lt_config_key_ref()), util::make_unique<ResourceConfigValue>(res.config, res.product)); (*it)->value = std::move(res.value); + (*it)->uses_readwrite_feature_flags = res.uses_readwrite_feature_flags; break; } case CollisionResult::kTakeNew: // Take the incoming value. config_value->value = std::move(res.value); + config_value->uses_readwrite_feature_flags = res.uses_readwrite_feature_flags; break; case CollisionResult::kConflict: @@ -843,6 +846,12 @@ NewResourceBuilder& NewResourceBuilder::SetAllowMangled(bool allow_mangled) { return *this; } +NewResourceBuilder& NewResourceBuilder::SetUsesReadWriteFeatureFlags( + bool uses_readwrite_feature_flags) { + res_.uses_readwrite_feature_flags = uses_readwrite_feature_flags; + return *this; +} + NewResource NewResourceBuilder::Build() { return std::move(res_); } diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h index b0e185536d16..778b43adb50b 100644 --- a/tools/aapt2/ResourceTable.h +++ b/tools/aapt2/ResourceTable.h @@ -104,6 +104,9 @@ class ResourceConfigValue { // The actual Value. std::unique_ptr<Value> value; + // Whether the value uses read/write feature flags + bool uses_readwrite_feature_flags = false; + ResourceConfigValue(const android::ConfigDescription& config, android::StringPiece product) : config(config), product(product) { } @@ -284,6 +287,7 @@ struct NewResource { std::optional<AllowNew> allow_new; std::optional<StagedId> staged_id; bool allow_mangled = false; + bool uses_readwrite_feature_flags = false; }; struct NewResourceBuilder { @@ -297,6 +301,7 @@ struct NewResourceBuilder { NewResourceBuilder& SetAllowNew(AllowNew allow_new); NewResourceBuilder& SetStagedId(StagedId id); NewResourceBuilder& SetAllowMangled(bool allow_mangled); + NewResourceBuilder& SetUsesReadWriteFeatureFlags(bool uses_feature_flags); NewResource Build(); private: diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp index 2a7921600477..755dbb6f8e42 100644 --- a/tools/aapt2/cmd/Link.cpp +++ b/tools/aapt2/cmd/Link.cpp @@ -673,11 +673,13 @@ bool ResourceFileFlattener::Flatten(ResourceTable* table, IArchiveWriter* archiv // Update the output format of this XML file. file_ref->type = XmlFileTypeForOutputFormat(options_.output_format); - bool result = table->AddResource(NewResourceBuilder(file.name) - .SetValue(std::move(file_ref), file.config) - .SetAllowMangled(true) - .Build(), - context_->GetDiagnostics()); + bool result = table->AddResource( + NewResourceBuilder(file.name) + .SetValue(std::move(file_ref), file.config) + .SetAllowMangled(true) + .SetUsesReadWriteFeatureFlags(doc->file.uses_readwrite_feature_flags) + .Build(), + context_->GetDiagnostics()); if (!result) { return false; } diff --git a/tools/aapt2/format/binary/BinaryResourceParser.cpp b/tools/aapt2/format/binary/BinaryResourceParser.cpp index 2e20e8175213..bac871b8bdc3 100644 --- a/tools/aapt2/format/binary/BinaryResourceParser.cpp +++ b/tools/aapt2/format/binary/BinaryResourceParser.cpp @@ -414,6 +414,8 @@ bool BinaryResourceParser::ParseType(const ResourceTablePackage* package, .SetId(res_id, OnIdConflict::CREATE_ENTRY) .SetAllowMangled(true); + res_builder.SetUsesReadWriteFeatureFlags(entry->uses_feature_flags()); + if (entry->flags() & ResTable_entry::FLAG_PUBLIC) { Visibility visibility{Visibility::Level::kPublic}; diff --git a/tools/aapt2/format/binary/ResEntryWriter.cpp b/tools/aapt2/format/binary/ResEntryWriter.cpp index 9dc205f4c1ba..0be392164453 100644 --- a/tools/aapt2/format/binary/ResEntryWriter.cpp +++ b/tools/aapt2/format/binary/ResEntryWriter.cpp @@ -199,6 +199,10 @@ void WriteEntry(const FlatEntry* entry, T* out_result, bool compact = false) { flags |= ResTable_entry::FLAG_WEAK; } + if (entry->uses_readwrite_feature_flags) { + flags |= ResTable_entry::FLAG_USES_FEATURE_FLAGS; + } + if constexpr (std::is_same_v<ResTable_entry_ext, T>) { flags |= ResTable_entry::FLAG_COMPLEX; } diff --git a/tools/aapt2/format/binary/ResEntryWriter.h b/tools/aapt2/format/binary/ResEntryWriter.h index c11598ec12f7..f54b29aa8f2a 100644 --- a/tools/aapt2/format/binary/ResEntryWriter.h +++ b/tools/aapt2/format/binary/ResEntryWriter.h @@ -38,6 +38,8 @@ struct FlatEntry { // The entry string pool index to the entry's name. uint32_t entry_key; + + bool uses_readwrite_feature_flags; }; // Pair of ResTable_entry and Res_value. These pairs are stored sequentially in values buffer. diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp index 1a82021bce71..50144ae816b6 100644 --- a/tools/aapt2/format/binary/TableFlattener.cpp +++ b/tools/aapt2/format/binary/TableFlattener.cpp @@ -502,7 +502,8 @@ class PackageFlattener { // Group values by configuration. for (auto& config_value : entry.values) { config_to_entry_list_map[config_value->config].push_back( - FlatEntry{&entry, config_value->value.get(), local_key_index}); + FlatEntry{&entry, config_value->value.get(), local_key_index, + config_value->uses_readwrite_feature_flags}); } } diff --git a/tools/aapt2/format/binary/TableFlattener_test.cpp b/tools/aapt2/format/binary/TableFlattener_test.cpp index 0f1168514c4a..9156b96b67ec 100644 --- a/tools/aapt2/format/binary/TableFlattener_test.cpp +++ b/tools/aapt2/format/binary/TableFlattener_test.cpp @@ -1069,4 +1069,23 @@ TEST_F(TableFlattenerTest, FlattenTypeEntryWithNameCollapseInExemption) { testing::IsTrue()); } +TEST_F(TableFlattenerTest, UsesReadWriteFeatureFlagSerializesCorrectly) { + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .Add(NewResourceBuilder("com.app.a:color/foo") + .SetValue(util::make_unique<BinaryPrimitive>( + uint8_t(Res_value::TYPE_INT_COLOR_ARGB8), 0xffaabbcc)) + .SetUsesReadWriteFeatureFlags(true) + .SetId(0x7f020000) + .Build()) + .Build(); + ResTable res_table; + TableFlattenerOptions options; + ASSERT_TRUE(Flatten(context_.get(), options, table.get(), &res_table)); + + uint32_t flags; + ASSERT_TRUE(res_table.getResourceEntryFlags(0x7f020000, &flags)); + ASSERT_EQ(flags, ResTable_entry::FLAG_USES_FEATURE_FLAGS); +} + } // namespace aapt diff --git a/tools/aapt2/link/FlaggedResources_test.cpp b/tools/aapt2/link/FlaggedResources_test.cpp index dbef77615515..47a71fe36e9f 100644 --- a/tools/aapt2/link/FlaggedResources_test.cpp +++ b/tools/aapt2/link/FlaggedResources_test.cpp @@ -14,6 +14,9 @@ * limitations under the License. */ +#include <regex> +#include <string> + #include "LoadedApk.h" #include "cmd/Dump.h" #include "io/StringStream.h" @@ -183,4 +186,49 @@ TEST_F(FlaggedResourcesTest, ReadWriteFlagInPathFails) { "Only read only flags may be used with resources: test.package.rwFlag")); } +TEST_F(FlaggedResourcesTest, ReadWriteFlagInXmlGetsFlagged) { + auto apk_path = file::BuildPath({android::base::GetExecutableDirectory(), "resapp.apk"}); + auto loaded_apk = LoadedApk::LoadApkFromPath(apk_path, &noop_diag); + + std::string output; + DumpChunksToString(loaded_apk.get(), &output); + + // The actual line looks something like: + // [ResTable_entry] id: 0x0000 name: layout1 keyIndex: 14 size: 8 flags: 0x0010 + // + // This regex matches that line and captures the name and the flag value for checking. + std::regex regex("[0-9a-zA-Z:_\\]\\[ ]+name: ([0-9a-zA-Z]+)[0-9a-zA-Z: ]+flags: (0x\\d{4})"); + std::smatch match; + + std::stringstream ss(output); + std::string line; + bool found = false; + int fields_flagged = 0; + while (std::getline(ss, line)) { + bool first_line = false; + if (line.contains("config: v36")) { + std::getline(ss, line); + first_line = true; + } + if (!line.contains("flags")) { + continue; + } + if (std::regex_search(line, match, regex) && (match.size() == 3)) { + unsigned int hex_value; + std::stringstream hex_ss; + hex_ss << std::hex << match[2]; + hex_ss >> hex_value; + if (hex_value & android::ResTable_entry::FLAG_USES_FEATURE_FLAGS) { + fields_flagged++; + if (first_line && match[1] == "layout1") { + found = true; + } + } + } + } + ASSERT_TRUE(found) << "No entry for layout1 at v36 with FLAG_USES_FEATURE_FLAGS bit set"; + // There should only be 1 entry that has the FLAG_USES_FEATURE_FLAGS bit of flags set to 1 + ASSERT_EQ(fields_flagged, 1); +} + } // namespace aapt diff --git a/tools/aapt2/link/FlaggedXmlVersioner.cpp b/tools/aapt2/link/FlaggedXmlVersioner.cpp index 75c6f17dcb51..8a3337c446cb 100644 --- a/tools/aapt2/link/FlaggedXmlVersioner.cpp +++ b/tools/aapt2/link/FlaggedXmlVersioner.cpp @@ -66,6 +66,28 @@ class AllDisabledFlagsVisitor : public xml::Visitor { bool had_flags_ = false; }; +// An xml visitor that goes through the a doc and determines if any elements are behind a flag. +class FindFlagsVisitor : public xml::Visitor { + public: + void Visit(xml::Element* node) override { + if (had_flags_) { + return; + } + auto* attr = node->FindAttribute(xml::kSchemaAndroid, xml::kAttrFeatureFlag); + if (attr != nullptr) { + had_flags_ = true; + return; + } + VisitChildren(node); + } + + bool HadFlags() const { + return had_flags_; + } + + bool had_flags_ = false; +}; + std::vector<std::unique_ptr<xml::XmlResource>> FlaggedXmlVersioner::Process(IAaptContext* context, xml::XmlResource* doc) { std::vector<std::unique_ptr<xml::XmlResource>> docs; @@ -74,15 +96,20 @@ std::vector<std::unique_ptr<xml::XmlResource>> FlaggedXmlVersioner::Process(IAap // Support for read/write flags was added in baklava so if the doc will only get used on // baklava or later we can just return the original doc. docs.push_back(doc->Clone()); + FindFlagsVisitor visitor; + doc->root->Accept(&visitor); + docs.back()->file.uses_readwrite_feature_flags = visitor.HadFlags(); } else { auto preBaklavaVersion = doc->Clone(); AllDisabledFlagsVisitor visitor; preBaklavaVersion->root->Accept(&visitor); + preBaklavaVersion->file.uses_readwrite_feature_flags = false; docs.push_back(std::move(preBaklavaVersion)); if (visitor.HadFlags()) { auto baklavaVersion = doc->Clone(); baklavaVersion->file.config.sdkVersion = SDK_BAKLAVA; + baklavaVersion->file.uses_readwrite_feature_flags = true; docs.push_back(std::move(baklavaVersion)); } } diff --git a/tools/processors/intdef_mappings/src/android/processor/IntDefProcessor.kt b/tools/processors/intdef_mappings/src/android/processor/IntDefProcessor.kt index 4995eebdd79e..fe72dae9ff34 100644 --- a/tools/processors/intdef_mappings/src/android/processor/IntDefProcessor.kt +++ b/tools/processors/intdef_mappings/src/android/processor/IntDefProcessor.kt @@ -155,35 +155,35 @@ class IntDefProcessor : AbstractProcessor() { ) { val indent = " " - writer.appendln("{") + writer.appendLine("{") val intDefTypesCount = annotationTypeToIntDefMapping.size var currentIntDefTypesCount = 0 for ((field, intDefMapping) in annotationTypeToIntDefMapping) { - writer.appendln("""$indent"$field": {""") + writer.appendLine("""$indent"$field": {""") // Start IntDef - writer.appendln("""$indent$indent"flag": ${intDefMapping.flag},""") + writer.appendLine("""$indent$indent"flag": ${intDefMapping.flag},""") - writer.appendln("""$indent$indent"values": {""") + writer.appendLine("""$indent$indent"values": {""") intDefMapping.entries.joinTo(writer, separator = ",\n") { (value, identifier) -> """$indent$indent$indent"$value": "$identifier"""" } - writer.appendln() - writer.appendln("$indent$indent}") + writer.appendLine() + writer.appendLine("$indent$indent}") // End IntDef writer.append("$indent}") if (++currentIntDefTypesCount < intDefTypesCount) { - writer.appendln(",") + writer.appendLine(",") } else { - writer.appendln("") + writer.appendLine("") } } - writer.appendln("}") + writer.appendLine("}") } } } |